@atlashub/smartstack-cli 4.17.1 → 4.19.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 (167) hide show
  1. package/package.json +1 -1
  2. package/templates/agents/ba-reader.md +86 -80
  3. package/templates/agents/ba-writer.md +321 -413
  4. package/templates/agents/docs-context-reader.md +3 -3
  5. package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +133 -0
  6. package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +126 -0
  7. package/templates/skills/apex/SKILL.md +29 -16
  8. package/templates/skills/apex/_shared.md +62 -9
  9. package/templates/skills/apex/references/analysis-methods.md +8 -6
  10. package/templates/skills/apex/references/challenge-questions.md +5 -5
  11. package/templates/skills/apex/references/core-seed-data.md +68 -45
  12. package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +26 -21
  13. package/templates/skills/apex/references/parallel-execution.md +156 -0
  14. package/templates/skills/apex/references/person-extension-pattern.md +12 -12
  15. package/templates/skills/apex/references/post-checks.md +1748 -1726
  16. package/templates/skills/apex/references/smartstack-api.md +63 -57
  17. package/templates/skills/apex/references/smartstack-frontend-compliance.md +594 -0
  18. package/templates/skills/apex/references/smartstack-frontend.md +1246 -1842
  19. package/templates/skills/apex/references/smartstack-layers.md +98 -145
  20. package/templates/skills/apex/steps/step-00-init.md +30 -6
  21. package/templates/skills/apex/steps/step-01-analyze.md +27 -23
  22. package/templates/skills/apex/steps/step-02-plan.md +12 -12
  23. package/templates/skills/apex/steps/step-03-execute.md +198 -143
  24. package/templates/skills/apex/steps/step-04-examine.md +24 -93
  25. package/templates/skills/apex/steps/step-05-deep-review.md +16 -16
  26. package/templates/skills/apex/steps/step-06-resolve.md +9 -9
  27. package/templates/skills/apex/steps/step-07-tests.md +3 -1
  28. package/templates/skills/apex/steps/step-08-run-tests.md +1 -1
  29. package/templates/skills/business-analyse/SKILL.md +182 -301
  30. package/templates/skills/business-analyse/_shared.md +119 -336
  31. package/templates/skills/business-analyse/html/ba-interactive.html +703 -82
  32. package/templates/skills/business-analyse/html/build-html.js +41 -3
  33. package/templates/skills/business-analyse/html/src/partials/cadrage-context.html +34 -0
  34. package/templates/skills/business-analyse/html/src/partials/cadrage-risks.html +48 -0
  35. package/templates/skills/business-analyse/html/src/partials/cadrage-scope.html +49 -0
  36. package/templates/skills/business-analyse/html/src/partials/cadrage-stakeholders.html +55 -0
  37. package/templates/skills/business-analyse/html/src/partials/cadrage-success.html +34 -0
  38. package/templates/skills/business-analyse/html/src/partials/consol-datamodel.html +8 -0
  39. package/templates/skills/business-analyse/html/src/partials/consol-flows.html +29 -0
  40. package/templates/skills/business-analyse/html/src/partials/consol-interactions.html +8 -0
  41. package/templates/skills/business-analyse/html/src/partials/consol-permissions.html +8 -0
  42. package/templates/skills/business-analyse/html/src/partials/decomp-dependencies.html +38 -0
  43. package/templates/skills/business-analyse/html/src/partials/decomp-modules.html +51 -0
  44. package/templates/skills/business-analyse/html/src/partials/handoff-summary.html +24 -0
  45. package/templates/skills/business-analyse/html/src/partials/module-spec-container.html +4 -0
  46. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +17 -1
  47. package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +31 -5
  48. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +100 -63
  49. package/templates/skills/business-analyse/html/src/scripts/06-render-mockups.js +372 -0
  50. package/templates/skills/business-analyse/html/src/scripts/10-comments.js +41 -13
  51. package/templates/skills/business-analyse/html/src/styles/09-mockups-html.css +136 -0
  52. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +7 -5
  53. package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +142 -0
  54. package/templates/skills/business-analyse/questionnaire/03-data-ui.md +94 -0
  55. package/templates/skills/business-analyse/questionnaire/04-risks-metrics.md +150 -0
  56. package/templates/skills/business-analyse/questionnaire/05-cross-module.md +69 -0
  57. package/templates/skills/business-analyse/questionnaire.md +23 -280
  58. package/templates/skills/business-analyse/react/application-viewer.md +2 -2
  59. package/templates/skills/business-analyse/react/components.md +4 -4
  60. package/templates/skills/business-analyse/react/i18n-template.md +1 -1
  61. package/templates/skills/business-analyse/react/schema.md +14 -14
  62. package/templates/skills/business-analyse/references/acceptance-criteria.md +25 -25
  63. package/templates/skills/business-analyse/references/analysis-semantic-checks.md +3 -3
  64. package/templates/skills/business-analyse/references/compilation-structure-cards.md +1 -1
  65. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +7 -7
  66. package/templates/skills/business-analyse/references/deploy-data-build.md +14 -12
  67. package/templates/skills/business-analyse/references/deploy-modes.md +10 -10
  68. package/templates/skills/business-analyse/references/detection-strategies.md +6 -6
  69. package/templates/skills/business-analyse/references/html-data-mapping.md +15 -15
  70. package/templates/skills/business-analyse/references/naming-conventions.md +4 -4
  71. package/templates/skills/business-analyse/references/review-data-mapping.md +29 -29
  72. package/templates/skills/business-analyse/references/robustness-checks.md +36 -33
  73. package/templates/skills/business-analyse/references/spec-auto-inference.md +2 -2
  74. package/templates/skills/business-analyse/references/ui-dashboard-spec.md +1 -1
  75. package/templates/skills/business-analyse/references/ui-resource-cards.md +1 -1
  76. package/templates/skills/business-analyse/references/validation-checklist.md +9 -6
  77. package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +2 -2
  78. package/templates/skills/business-analyse/schemas/application-schema.json +8 -8
  79. package/templates/skills/business-analyse/schemas/feature-schema.json +3 -3
  80. package/templates/skills/business-analyse/schemas/index-schema.json +47 -0
  81. package/templates/skills/business-analyse/schemas/project-schema.json +6 -6
  82. package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +1 -1
  83. package/templates/skills/business-analyse/schemas/sections/handoff-schema.json +5 -3
  84. package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +4 -4
  85. package/templates/skills/business-analyse/schemas/sections/specification-schema.json +1 -1
  86. package/templates/skills/business-analyse/schemas/shared/common-defs.json +4 -3
  87. package/templates/skills/business-analyse/steps/step-00-init.md +93 -134
  88. package/templates/skills/business-analyse/steps/step-01-cadrage.md +136 -172
  89. package/templates/skills/business-analyse/steps/step-02-structure.md +175 -0
  90. package/templates/skills/business-analyse/steps/step-03-specify.md +198 -0
  91. package/templates/skills/business-analyse/steps/step-04-consolidate.md +478 -0
  92. package/templates/skills/business-analyse/steps/step-05-deploy.md +220 -0
  93. package/templates/skills/business-analyse/steps/step-06-review.md +51 -69
  94. package/templates/skills/business-analyse/templates/tpl-frd.md +1 -1
  95. package/templates/skills/business-analyse/templates/tpl-handoff.md +20 -17
  96. package/templates/skills/business-analyse/templates/tpl-launch-displays.md +2 -2
  97. package/templates/skills/business-analyse/templates-react.md +2 -2
  98. package/templates/skills/derive-prd/SKILL.md +92 -0
  99. package/templates/skills/derive-prd/references/acceptance-criteria.md +169 -0
  100. package/templates/skills/derive-prd/references/entity-domain-mapping.md +115 -0
  101. package/templates/skills/{business-analyse → derive-prd}/references/handoff-file-templates.md +131 -120
  102. package/templates/skills/{business-analyse → derive-prd}/references/handoff-mappings.md +95 -95
  103. package/templates/skills/{business-analyse → derive-prd}/references/handoff-seeddata-generation.md +312 -312
  104. package/templates/skills/{business-analyse → derive-prd}/references/prd-generation.md +262 -258
  105. package/templates/skills/derive-prd/references/readiness-scoring.md +104 -0
  106. package/templates/skills/derive-prd/schemas/handoff-schema.json +95 -0
  107. package/templates/skills/derive-prd/steps/step-00-validate.md +130 -0
  108. package/templates/skills/derive-prd/steps/step-01-transform.md +206 -0
  109. package/templates/skills/derive-prd/steps/step-02-export.md +181 -0
  110. package/templates/skills/{business-analyse → derive-prd}/templates/tpl-progress.md +172 -172
  111. package/templates/skills/ralph-loop/SKILL.md +10 -4
  112. package/templates/skills/ralph-loop/references/category-completeness.md +20 -4
  113. package/templates/skills/ralph-loop/references/compact-loop.md +80 -48
  114. package/templates/skills/ralph-loop/references/init-resume-recovery.md +4 -2
  115. package/templates/skills/ralph-loop/references/parallel-execution.md +27 -27
  116. package/templates/skills/ralph-loop/steps/step-00-init.md +19 -9
  117. package/templates/skills/ralph-loop/steps/step-01-task.md +12 -4
  118. package/templates/skills/ralph-loop/steps/step-02-execute.md +9 -4
  119. package/templates/skills/ralph-loop/steps/step-03-commit.md +1 -1
  120. package/templates/skills/ralph-loop/steps/step-04-check.md +5 -21
  121. package/templates/skills/ralph-loop/steps/step-05-report.md +6 -1
  122. package/templates/skills/apex/references/agent-teams-protocol.md +0 -203
  123. package/templates/skills/business-analyse/_architecture.md +0 -124
  124. package/templates/skills/business-analyse/_elicitation.md +0 -206
  125. package/templates/skills/business-analyse/_module-loop.md +0 -115
  126. package/templates/skills/business-analyse/_suggestions.md +0 -34
  127. package/templates/skills/business-analyse/questionnaire/00-application.md +0 -160
  128. package/templates/skills/business-analyse/questionnaire/00b-project.md +0 -85
  129. package/templates/skills/business-analyse/questionnaire/02-stakeholders.md +0 -189
  130. package/templates/skills/business-analyse/questionnaire/03-scope.md +0 -164
  131. package/templates/skills/business-analyse/questionnaire/04-data.md +0 -88
  132. package/templates/skills/business-analyse/questionnaire/05-integrations.md +0 -58
  133. package/templates/skills/business-analyse/questionnaire/06-security.md +0 -68
  134. package/templates/skills/business-analyse/questionnaire/07-ui.md +0 -76
  135. package/templates/skills/business-analyse/questionnaire/08-performance.md +0 -42
  136. package/templates/skills/business-analyse/questionnaire/09-constraints.md +0 -45
  137. package/templates/skills/business-analyse/questionnaire/10-documentation.md +0 -43
  138. package/templates/skills/business-analyse/questionnaire/11-data-lifecycle.md +0 -59
  139. package/templates/skills/business-analyse/questionnaire/12-migration.md +0 -58
  140. package/templates/skills/business-analyse/questionnaire/13-cross-module.md +0 -69
  141. package/templates/skills/business-analyse/questionnaire/14-risk-assumptions.md +0 -135
  142. package/templates/skills/business-analyse/questionnaire/15-success-metrics.md +0 -136
  143. package/templates/skills/business-analyse/references/agent-module-prompt.md +0 -362
  144. package/templates/skills/business-analyse/references/agent-pooling-best-practices.md +0 -557
  145. package/templates/skills/business-analyse/references/cache-warming-strategy.md +0 -566
  146. package/templates/skills/business-analyse/references/cadrage-challenge-patterns.md +0 -41
  147. package/templates/skills/business-analyse/references/cadrage-coverage-matrix.md +0 -74
  148. package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +0 -115
  149. package/templates/skills/business-analyse/references/cadrage-shared-modules.md +0 -68
  150. package/templates/skills/business-analyse/references/cadrage-structure-cards.md +0 -85
  151. package/templates/skills/business-analyse/references/team-orchestration.md +0 -1022
  152. package/templates/skills/business-analyse/references/validate-incremental-html.md +0 -121
  153. package/templates/skills/business-analyse/steps/step-01b-applications.md +0 -419
  154. package/templates/skills/business-analyse/steps/step-02-decomposition.md +0 -387
  155. package/templates/skills/business-analyse/steps/step-03a-data.md +0 -16
  156. package/templates/skills/business-analyse/steps/step-03a1-setup.md +0 -506
  157. package/templates/skills/business-analyse/steps/step-03a2-analysis.md +0 -301
  158. package/templates/skills/business-analyse/steps/step-03b-ui.md +0 -425
  159. package/templates/skills/business-analyse/steps/step-03c-compile.md +0 -611
  160. package/templates/skills/business-analyse/steps/step-03d-validate.md +0 -783
  161. package/templates/skills/business-analyse/steps/step-04-consolidation.md +0 -17
  162. package/templates/skills/business-analyse/steps/step-04a-collect.md +0 -415
  163. package/templates/skills/business-analyse/steps/step-04b-analyze.md +0 -163
  164. package/templates/skills/business-analyse/steps/step-04c-decide.md +0 -186
  165. package/templates/skills/business-analyse/steps/step-05a-handoff.md +0 -840
  166. package/templates/skills/business-analyse/steps/step-05b-deploy.md +0 -522
  167. package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +0 -703
@@ -62,9 +62,9 @@ if (!navRoute) {
62
62
 
63
63
  **File:** `Infrastructure/Persistence/Seeding/Data/NavigationApplicationSeedData.cs`
64
64
 
65
- > **MANDATORY:** This file MUST be created BEFORE any module seed data.
65
+ > Create this file BEFORE any module seed data.
66
66
  > Without it, modules have no parent ApplicationId and ApplicationRolesSeedData has no GUID reference.
67
- > This file is created **ONCE per application** (not per module).
67
+ > This file is created once per application (not per module).
68
68
 
69
69
  ### Data Source
70
70
 
@@ -220,10 +220,30 @@ public class NavigationApplicationSeedEntry
220
220
  ### GUID Generation Rule
221
221
 
222
222
  ```csharp
223
- // ALWAYS use Guid.NewGuid() — avoids conflicts between projects/tenants/environments
223
+ // Use Guid.NewGuid() — avoids conflicts between projects/tenants/environments
224
224
  public static readonly Guid ModuleId = Guid.NewGuid();
225
225
  ```
226
226
 
227
+ ### FK Resolution Safety Rule
228
+
229
+ 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:
230
+
231
+ ```csharp
232
+ // CORRECT: null-guard with clear error message
233
+ var parentModule = await context.NavigationModules
234
+ .FirstOrDefaultAsync(m => m.Code == "target-module" && m.ApplicationId == app.Id, ct)
235
+ ?? throw new InvalidOperationException(
236
+ $"Seed data dependency missing: NavigationModule 'target-module' not found. " +
237
+ $"Run the target module's seed data provider first.");
238
+
239
+ // WRONG: FirstAsync with generic error
240
+ var parentModule = await context.NavigationModules
241
+ .FirstAsync(m => m.Code == "target-module", ct);
242
+ // → Throws "Sequence contains no elements" — hard to debug
243
+ ```
244
+
245
+ This applies to ALL cross-entity FK lookups in seed data: modules, sections, resources, roles, permissions.
246
+
227
247
  ### Template
228
248
 
229
249
  ```csharp
@@ -386,7 +406,7 @@ public static readonly Guid {ResourcePascal}ResourceId = Guid.NewGuid();
386
406
  > The `list` and `detail` sections are view modes of the module, NOT functional sub-areas.
387
407
  > - `list` section route = module route (e.g., `/human-resources/employees`) — NO `/list` suffix
388
408
  > - `detail` section route = module route + `/:id` (e.g., `/human-resources/employees/:id`) — NOT `/detail/:id`
389
- > - FORBIDDEN: `/{module}/list`, `/{module}/detail/:id`
409
+ > - Do not use: `/{module}/list`, `/{module}/detail/:id`
390
410
  > - Other sections (dashboard, approve, import) = module route + `/{section-kebab}` (normal)
391
411
 
392
412
  ```csharp
@@ -416,7 +436,7 @@ public static IEnumerable<NavigationSectionSeedEntry> GetSectionEntries(Guid mod
416
436
  // - "list" section → same as module route (no extra segment)
417
437
  // - "detail" section → module route + "/:id"
418
438
  // - Other sections → module route + "/{section-kebab}"
419
- // FORBIDDEN: "/employees/list", "/employees/detail/:id"
439
+ // Do not use: "/employees/list", "/employees/detail/:id"
420
440
  Route = "{section_route}", // From seedDataCore.navigationSections[].route
421
441
  DisplayOrder = {section1_sort},
422
442
  IsActive = true
@@ -579,9 +599,9 @@ public class NavigationResourceSeedEntry
579
599
 
580
600
  ## 3. PermissionsSeedData.cs — MCP-First
581
601
 
582
- ### CRITICAL: PermissionAction Safety Rules
602
+ ### PermissionAction Safety Rules
583
603
 
584
- > **NEVER use `Enum.Parse<PermissionAction>("...")` — this causes runtime crashes if the string is invalid.**
604
+ > Do not use `Enum.Parse<PermissionAction>("...")` — this causes runtime crashes if the string is invalid.
585
605
  > The error only manifests at application startup, not at compile time.
586
606
 
587
607
  **Valid PermissionAction values (from SmartStack.Domain.Authorization):**
@@ -600,18 +620,18 @@ public class NavigationResourceSeedEntry
600
620
  | `PermissionAction.Assign` | 9 | Assign items to users |
601
621
  | `PermissionAction.Execute` | 10 | Execute actions (sync, run, etc.) |
602
622
 
603
- **Anti-patterns (FORBIDDEN):**
623
+ **Anti-patterns:**
604
624
 
605
625
  ```csharp
606
- // FORBIDDEN — Runtime crash if string is not a valid enum value
626
+ // Runtime crash if string is not a valid enum value
607
627
  Enum.Parse<PermissionAction>("Validate"); // ArgumentException at startup
608
628
  (PermissionAction)Enum.Parse(typeof(PermissionAction), "Validate"); // Same crash
609
629
 
610
- // FORBIDDEN — String-based action in anonymous objects
630
+ // String-based action in anonymous objects
611
631
  new { Action = "read" }; // Not type-safe, silent mismatch possible
612
632
  ```
613
633
 
614
- **Correct patterns (MANDATORY):**
634
+ **Correct patterns:**
615
635
 
616
636
  ```csharp
617
637
  // ALWAYS use the typed enum directly — compile-time safe
@@ -641,10 +661,10 @@ MCP returns:
641
661
 
642
662
  ### Step B: Write Permissions.cs (Application layer)
643
663
 
644
- > **CRITICAL — Permission paths use the SAME kebab-case as NavRoute codes.**
664
+ > Permission paths use the SAME kebab-case as NavRoute codes.
645
665
  > `{navRoute}` is already kebab-case (e.g., `human-resources.employees`).
646
- > NEVER strip hyphens or derive codes from C# class names.
647
- > FORBIDDEN: `humanresources.employees.read` CORRECT: `human-resources.employees.read`
666
+ > Do not strip hyphens or derive codes from C# class names.
667
+ > Use `human-resources.employees.read` not `humanresources.employees.read`.
648
668
  > SmartStack.app reference: `support-client.my-tickets.read`
649
669
 
650
670
  ```csharp
@@ -769,14 +789,14 @@ If MCP `generate_permissions` fails, use the template above directly with values
769
789
 
770
790
  Creates the 4 standard application-scoped roles: Admin, Manager, Contributor, Viewer.
771
791
 
772
- > **CRITICAL — SmartStack core MAY already provide system roles (admin, manager, contributor, viewer).**
773
- > If system roles already exist in `auth_Roles`, do NOT create duplicates.
774
- > `SeedRolesAsync()` MUST check existence by Code, not just by ApplicationId.
775
- > **For RolePermission mappings:** ALWAYS look up roles by Code at runtime (in `SeedRolePermissionsAsync()`).
776
- > **FORBIDDEN:** Using `GenerateRoleGuid()`, `DeterministicGuid("role:admin")`, or any hardcoded role GUID
792
+ > SmartStack core MAY already provide system roles (admin, manager, contributor, viewer).
793
+ > If system roles already exist in `auth_Roles`, do not create duplicates.
794
+ > `SeedRolesAsync()` must check existence by Code, not just by ApplicationId.
795
+ > For RolePermission mappings: always look up roles by Code at runtime (in `SeedRolePermissionsAsync()`).
796
+ > Do not use `GenerateRoleGuid()`, `DeterministicGuid("role:admin")`, or any hardcoded role GUID
777
797
  > when creating RolePermission entries. The role GUIDs may differ from what's in the database.
778
798
 
779
- **CRITICAL:** This file is created **ONCE per application** (not per module).
799
+ This file is created once per application (not per module).
780
800
 
781
801
  ### GUID Generation Rule
782
802
 
@@ -784,9 +804,9 @@ Creates the 4 standard application-scoped roles: Admin, Manager, Contributor, Vi
784
804
  > RolePermission mapping MUST look up roles by Code, not by GUID.
785
805
 
786
806
  ```csharp
787
- // All role GUIDs are random — ALWAYS resolve roles by Code at runtime
788
- // FORBIDDEN in RolePermission mapping: hardcoded GUIDs of any kind
789
- // CORRECT: var role = roles.FirstOrDefault(r => r.Code == "admin");
807
+ // All role GUIDs are random — always resolve roles by Code at runtime
808
+ // Do not use hardcoded GUIDs in RolePermission mapping
809
+ var role = roles.FirstOrDefault(r => r.Code == "admin");
790
810
  ```
791
811
 
792
812
  ### Template
@@ -887,10 +907,10 @@ public class ApplicationRoleSeedEntry
887
907
 
888
908
  **File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/RolesSeedData.cs`
889
909
 
890
- > **CRITICAL:** This file uses `RoleCode` (string), NOT role GUIDs.
910
+ > This file uses `RoleCode` (string), not role GUIDs.
891
911
  > Roles are resolved by Code at runtime in `SeedRolePermissionsAsync()`.
892
- > **FORBIDDEN:** `DeterministicGuid("role:admin")`, `GenerateRoleGuid("admin")`, or any hardcoded Guid for roles.
893
- > SmartStack core pre-seeds system roles — their IDs are NOT deterministic from the client perspective.
912
+ > Do not use `DeterministicGuid("role:admin")`, `GenerateRoleGuid("admin")`, or any hardcoded Guid for roles.
913
+ > SmartStack core pre-seeds system roles — their IDs are not deterministic from the client perspective.
894
914
 
895
915
  ### Context-Based Role Mapping
896
916
 
@@ -967,7 +987,7 @@ public class RolePermissionSeedEntry
967
987
 
968
988
  | Rule | Description |
969
989
  |------|-------------|
970
- | Factory methods | `NavigationModule.Create(...)`, `Role.Create(...)`, `Permission.CreateForModule(...)`, `RolePermission.Create(...)` — NEVER `new Entity()` |
990
+ | Factory methods | `NavigationModule.Create(...)`, `Role.Create(...)`, `Permission.CreateForModule(...)`, `RolePermission.Create(...)` — do not use `new Entity()` |
971
991
  | Idempotence | Each Seed method checks existence before inserting |
972
992
  | Execution order | Navigation → Roles → Permissions → RolePermissions (roles MUST exist before mapping) |
973
993
  | SaveChanges per group | Navigation -> save -> Roles -> save -> Permissions -> save -> RolePermissions -> save |
@@ -1048,11 +1068,14 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
1048
1068
 
1049
1069
  // Resolve module entities for section/resource seeding (works for both new AND existing modules)
1050
1070
  var mod1Entity = await context.NavigationModules
1051
- .FirstAsync(m => m.Code == "{module1Code}" && m.ApplicationId == app.Id, ct);
1071
+ .FirstOrDefaultAsync(m => m.Code == "{module1Code}" && m.ApplicationId == app.Id, ct)
1072
+ ?? throw new InvalidOperationException(
1073
+ $"Seed data dependency missing: NavigationModule '{"{module1Code}"}' not found for app '{app.Code}'. " +
1074
+ $"Ensure the module was created in the navigation seeding step above.");
1052
1075
  // Repeat for each module...
1053
1076
 
1054
1077
  // --- Module translations (idempotent — unique index IX_nav_Translations_EntityType_EntityId_LanguageCode) ---
1055
- // CRITICAL: Always check existence before inserting translations to avoid duplicate key errors
1078
+ // Always check existence before inserting translations to avoid duplicate key errors
1056
1079
  // on re-runs, partial failures, or DB reset scenarios.
1057
1080
  if (!await context.NavigationTranslations.AnyAsync(
1058
1081
  t => t.EntityId == {Module1Pascal}NavigationSeedData.{Module1Pascal}ModuleId
@@ -1103,9 +1126,9 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
1103
1126
  await ((DbContext)context).SaveChangesAsync(ct);
1104
1127
 
1105
1128
  // --- Resources (idempotent — use ACTUAL section IDs from DB, not deterministic seed IDs) ---
1106
- // CRITICAL: NavigationSection.Create() generates its own ID in DB.
1107
- // The GUID from GetSectionEntries() is NOT the actual SectionId in DB.
1108
- // We MUST query the real section by Code+ModuleId to get the actual DB ID,
1129
+ // NavigationSection.Create() generates its own ID in DB.
1130
+ // The GUID from GetSectionEntries() is not the actual SectionId in DB.
1131
+ // Query the real section by Code+ModuleId to get the actual DB ID,
1109
1132
  // otherwise FK_nav_Resources_nav_Sections_SectionId will fail.
1110
1133
  foreach (var secEntry in {Module1Pascal}NavigationSeedData.GetSectionEntries(mod1Entity.Id))
1111
1134
  {
@@ -1186,9 +1209,9 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
1186
1209
  .AnyAsync(rp => rp.Permission!.Path.StartsWith("{appCode}."), ct);
1187
1210
  if (exists) return;
1188
1211
 
1189
- // CRITICAL: Resolve roles by Code from DB — NEVER use hardcoded GUIDs.
1212
+ // Resolve roles by Code from DB — do not use hardcoded GUIDs.
1190
1213
  // Application-scoped roles (admin, manager, contributor, viewer) are created by
1191
- // SeedRolesAsync() above. System roles use their own IDs that do NOT match
1214
+ // SeedRolesAsync() above. System roles use their own IDs that do not match
1192
1215
  // DeterministicGuid("role:admin").
1193
1216
  var roles = await context.Roles
1194
1217
  .Where(r => r.ApplicationId != null || r.IsSystem)
@@ -1211,7 +1234,7 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
1211
1234
 
1212
1235
  if (role == null)
1213
1236
  {
1214
- // CRITICAL: Role not found — SeedRolesAsync() may not have run.
1237
+ // Role not found — SeedRolesAsync() may not have run.
1215
1238
  // This causes silent permission failure → 401 on protected pages.
1216
1239
  Console.WriteLine($"[SEED WARNING] Role '{mapping.RoleCode}' not found. Role-permission mapping skipped for '{mapping.PermissionPath}'.");
1217
1240
  continue;
@@ -1281,7 +1304,7 @@ Sections and resources are **optional per module**. When processing a module's f
1281
1304
 
1282
1305
  ---
1283
1306
 
1284
- ## 8. Verification Checklist (BLOCKING)
1307
+ ## 8. Verification Checklist
1285
1308
 
1286
1309
  Before marking the task as completed, verify ALL:
1287
1310
 
@@ -1302,12 +1325,12 @@ Before marking the task as completed, verify ALL:
1302
1325
  - [ ] `Permissions.cs` constants match seed data paths
1303
1326
  - [ ] MCP `generate_permissions` called (or fallback used)
1304
1327
  - [ ] Role-permission mappings assigned (Admin, Manager, Contributor, Viewer)
1305
- - [ ] **RolePermission mappings use Code-based lookup** NEVER `DeterministicGuid("role:admin")` or `GenerateRoleGuid()`
1306
- - [ ] **SeedRolesAsync checks existence by Code** SmartStack core may pre-seed system roles
1328
+ - [ ] RolePermission mappings use Code-based lookup (no `DeterministicGuid("role:admin")` or `GenerateRoleGuid()`)
1329
+ - [ ] SeedRolesAsync checks existence by Code (SmartStack core may pre-seed system roles)
1307
1330
  - [ ] `IClientSeedDataProvider` generated with 4 methods (Navigation, Roles, Permissions, RolePermissions)
1308
1331
  - [ ] Execution order: Navigation (application → modules → sections → resources) → Roles → Permissions → RolePermissions
1309
1332
  - [ ] Each Seed method is idempotent (checks existence before inserting)
1310
- - [ ] Factory methods used throughout (NEVER `new Entity()`)
1333
+ - [ ] Factory methods used throughout (no `new Entity()`)
1311
1334
  - [ ] `SaveChangesAsync` called per group (Navigation → Roles → Permissions → RolePermissions)
1312
1335
  - [ ] DI registration added: `services.AddScoped<IClientSeedDataProvider, ...>()`
1313
1336
  - [ ] NavigationSections seeded (if `seedDataCore.navigationSections` present in feature.json)
@@ -1317,7 +1340,7 @@ Before marking the task as completed, verify ALL:
1317
1340
  - [ ] NO `Enum.Parse<PermissionAction>` usage anywhere in seeding code (use typed enum directly)
1318
1341
  - [ ] ALL PermissionAction values are from the valid enum: Access, Read, Create, Update, Delete, Export, Import, Approve, Reject, Assign, Execute
1319
1342
 
1320
- **Seed Data Integrity (BLOCKING — run AFTER `dotnet build`):**
1343
+ **Seed Data Integrity (run AFTER `dotnet build`):**
1321
1344
  - [ ] Application startup test: `dotnet run --urls http://localhost:0 --environment Development` exits without seed data exceptions
1322
1345
  - [ ] Verify `nav_Applications` has entry for `{appCode}` (query or startup log)
1323
1346
  - [ ] Verify `nav_Modules` has entries for each module (count matches feature.json modules)
@@ -1337,8 +1360,8 @@ Before marking the task as completed, verify ALL:
1337
1360
 
1338
1361
  | Rule | Description |
1339
1362
  |------|-------------|
1340
- | TenantId MANDATORY | ALL business seed entities MUST set `TenantId` |
1341
- | Deterministic TenantId | Use `SeedConstants.DefaultTenantId` (NEVER inline GUID) |
1363
+ | TenantId required | All business seed entities must set `TenantId` |
1364
+ | Deterministic TenantId | Use `SeedConstants.DefaultTenantId` (do not inline GUID) |
1342
1365
  | DevDataSeeder pattern | Implement `IDevDataSeeder` with `SeedAsync()` method |
1343
1366
  | Idempotency | Each seeder MUST check `AnyAsync()` before inserting |
1344
1367
  | Order | DevDataSeeder `Order >= 200` (after core seed data at Order 100) |
@@ -1361,7 +1384,7 @@ public class {Module}DevDataSeeder : IDevDataSeeder
1361
1384
  Id = Guid.NewGuid(),
1362
1385
  Code = "{code}",
1363
1386
  Name = "{name}",
1364
- TenantId = SeedConstants.DefaultTenantId, // MANDATORY
1387
+ TenantId = SeedConstants.DefaultTenantId, // Required for multi-tenant isolation
1365
1388
  IsActive = true
1366
1389
  }
1367
1390
  };
@@ -1374,9 +1397,9 @@ public class {Module}DevDataSeeder : IDevDataSeeder
1374
1397
  }
1375
1398
  ```
1376
1399
 
1377
- ### FORBIDDEN
1400
+ ### Anti-patterns
1378
1401
 
1379
- - Seeding business entities WITHOUT `TenantId`
1402
+ - Seeding business entities without `TenantId`
1380
1403
  - Using `Guid.NewGuid()` for TenantId
1381
1404
  - Omitting idempotency check (`AnyAsync`)
1382
1405
  - Hardcoding TenantId inline (use `SeedConstants.DefaultTenantId`)
@@ -4,22 +4,27 @@
4
4
 
5
5
  ---
6
6
 
7
- ## Step 4: Wire Routes to App.tsx (BLOCKING)
7
+ ## Step 4: Wire Routes to App.tsx
8
8
 
9
- **CRITICAL:** This step is MANDATORY. Without it, routes exist as files but are invisible to the React Router. The page will be BLANK.
9
+ **Important:** This step is required. Without it, routes exist as files but are invisible to the React Router. The page will be blank.
10
10
 
11
11
  After `scaffold_routes` generates the route files, you MUST manually insert the routes into `App.tsx`.
12
12
 
13
13
  ---
14
14
 
15
- ## Step 4a: Import Page Components
15
+ ## Step 4a: Import Page Components (Lazy Loading ONLY)
16
16
 
17
- At the top of App.tsx:
17
+ At the top of App.tsx, ALL page imports MUST use `React.lazy()`:
18
18
 
19
19
  ```tsx
20
- import { {EntityName}Page } from '@/pages/{Application}/{Module}/{EntityName}Page';
21
- // Or lazy-loaded:
22
- const {EntityName}Page = lazy(() => import('@/pages/{Application}/{Module}/{EntityName}Page'));
20
+ // CORRECT Lazy loading (MANDATORY per POST-CHECK S6)
21
+ const {EntityName}Page = lazy(() =>
22
+ import('@/pages/{Application}/{Module}/{EntityName}Page')
23
+ .then(m => ({ default: m.{EntityName}Page }))
24
+ );
25
+
26
+ // WRONG — Static import is BLOCKING per POST-CHECK S6
27
+ // import { {EntityName}Page } from '@/pages/{Application}/{Module}/{EntityName}Page';
23
28
  ```
24
29
 
25
30
  ---
@@ -98,9 +103,9 @@ Find `<Route path="/t/:slug">` and add the **same route entries** there.
98
103
 
99
104
  ---
100
105
 
101
- ## Step 4b.5: Verify mergeRoutes() Call (BLOCKING)
106
+ ## Step 4b.5: Verify mergeRoutes() Call
102
107
 
103
- **BEFORE modifying routes, READ App.tsx and verify the `mergeRoutes()` call has 2 parameters.**
108
+ **Before modifying routes, READ App.tsx and verify the `mergeRoutes()` call has 2 parameters.**
104
109
 
105
110
  ✅ Correct:
106
111
  ```tsx
@@ -113,9 +118,9 @@ const routes = mergeRoutes(clientRoutes);
113
118
  ```
114
119
 
115
120
  → Add the `applicationRoutes` object and pass it as 2nd parameter.
116
- Without it, ALL application routes are silently ignored → blank page or /login redirect.
121
+ Without it, all application routes are silently ignored → blank page or /login redirect.
117
122
 
118
- **NEVER remove the `applicationRoutes` parameter or the `ApplicationRouteExtensions` import.**
123
+ **Do not remove the `applicationRoutes` parameter or the `ApplicationRouteExtensions` import.**
119
124
 
120
125
  ---
121
126
 
@@ -141,11 +146,11 @@ If `appWiring.issues` is not empty, fix the wiring before proceeding.
141
146
 
142
147
  ---
143
148
 
144
- ## Step 4e: Parent Redirect Routes (MANDATORY)
149
+ ## Step 4e: Parent Redirect Routes
145
150
 
146
- **CRITICAL:** Without parent redirects, navigating to an application or module URL (e.g., `/human-resources` or `/human-resources/employees`) will cause a redirect to `/login` because no route matches.
151
+ **Important:** Without parent redirects, navigating to an application or module URL (e.g., `/human-resources` or `/human-resources/employees`) will cause a redirect to `/login` because no route matches.
147
152
 
148
- For each application, you MUST add:
153
+ For each application, add:
149
154
 
150
155
  1. **Application root redirect** — redirects `/{application}` to the first module/section:
151
156
  ```tsx
@@ -169,19 +174,19 @@ The `to` prop is resolved relative to the **parent route** (`/{application}`), s
169
174
 
170
175
  ---
171
176
 
172
- ## Forbidden Patterns (BOTH patterns)
177
+ ## Patterns to Avoid (BOTH patterns)
173
178
 
174
- - Adding application routes to `clientRoutes[]` with absolute paths — `clientRoutes` is ONLY for routes outside SmartStack applications (e.g., `/about`, `/pricing`)
175
- - Adding routes OUTSIDE the Layout wrapper (shell will not render)
176
- - Using `createBrowserRouter` (SmartStack uses `useRoutes()` + `mergeRoutes()`)
177
- - Adding custom application routes to `clientRoutes[]` with absolute paths:
179
+ - Do not add application routes to `clientRoutes[]` with absolute paths — `clientRoutes` is only for routes outside SmartStack applications (e.g., `/about`, `/pricing`)
180
+ - Do not add routes outside the Layout wrapper (shell will not render)
181
+ - Do not use `createBrowserRouter` (SmartStack uses `useRoutes()` + `mergeRoutes()`)
182
+ - Do not add custom application routes to `clientRoutes[]` with absolute paths:
178
183
  ```tsx
179
184
  // ❌ WRONG — bypasses RouteGuard + TenantLayout + AppLayout → /login redirect
180
185
  const clientRoutes: RouteConfig[] = [
181
186
  { path: '/human-resources/employees/management', element: <EmployeePage /> },
182
187
  ];
183
188
  ```
184
- Custom application routes MUST go in `applicationRoutes` with RELATIVE paths:
189
+ Custom application routes go in `applicationRoutes` with RELATIVE paths:
185
190
  ```tsx
186
191
  // ✅ CORRECT
187
192
  const applicationRoutes: ApplicationRouteExtensions = {
@@ -190,7 +195,7 @@ The `to` prop is resolved relative to the **parent route** (`/{application}`), s
190
195
  ],
191
196
  };
192
197
  ```
193
- - Removing the `applicationRoutes` 2nd parameter from `mergeRoutes()` — this silently breaks ALL custom routes
198
+ - Do not remove the `applicationRoutes` 2nd parameter from `mergeRoutes()` — this silently breaks all custom routes
194
199
 
195
200
  ---
196
201
 
@@ -0,0 +1,156 @@
1
+ # Parallel Execution Protocol — APEX
2
+
3
+ > **Loaded by:** step-01 (analysis scan) and step-03 (parallel execution)
4
+ > **Condition:** NOT economy_mode
5
+ > **Purpose:** Reusable protocol for launching parallel Agent subagents.
6
+
7
+ ---
8
+
9
+ ## Agent Tool Usage
10
+
11
+ APEX uses the `Agent` tool from Claude Code to parallelize work. Agents are launched in a single message to run concurrently.
12
+
13
+ ```yaml
14
+ Agent:
15
+ subagent_type: "Explore" | "Snipper" # See assignment table below
16
+ model: "sonnet" | "opus" # sonnet for scan, opus for dev
17
+ prompt: "{detailed task prompt}"
18
+ ```
19
+
20
+ ### Model & Agent Assignment
21
+
22
+ | Task Type | subagent_type | Model | Justification |
23
+ |-----------|---------------|-------|---------------|
24
+ | Scan/read files | Explore | Sonnet | Read-only (Glob, Grep, Read) |
25
+ | Read context (PRD, feature.json) | Explore | Sonnet | Read-only extraction |
26
+ | Code development (Layer 2/3) | Snipper | Opus | Fast code editing (Read, Edit, Write, Glob, Grep) |
27
+
28
+ ---
29
+
30
+ ## Task List Coordination
31
+
32
+ ### Task Creation (by agent principal, BEFORE launching subagents)
33
+
34
+ Tasks MUST be created upfront for the full layer scope:
35
+
36
+ ```
37
+ FOR EACH entity in layer:
38
+ TaskCreate(
39
+ subject: "Layer {N}: {Entity} {layer_type}",
40
+ description: "{detailed scope: files, skills, MCP tools}",
41
+ activeForm: "{Building/Creating} {Entity} {layer_type}"
42
+ )
43
+ ```
44
+
45
+ ### Task Assignment (via prompt)
46
+
47
+ Include task ID and context in the Agent prompt:
48
+
49
+ ```
50
+ "Your assigned task ID is {task_id}.
51
+ Call TaskUpdate(taskId: '{task_id}', status: 'in_progress') before starting.
52
+ Call TaskUpdate(taskId: '{task_id}', status: 'completed', metadata: { files_created: [...] }) when done."
53
+ ```
54
+
55
+ ### Task Monitoring (by agent principal)
56
+
57
+ ```
58
+ 1. Agent results are returned when each subagent completes
59
+ 2. Agent principal updates TaskList after each result
60
+ 3. All tasks completed → proceed to build gate
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Team Decomposition
66
+
67
+ ### Step-01 Analyze: Split by scan scope
68
+
69
+ Launch 2-3 `Explore` agents in parallel (single message):
70
+
71
+ ```
72
+ Agent(subagent_type='Explore', model='sonnet',
73
+ prompt='Scan backend for module {module_code}: entities, EF configs, services,
74
+ DTOs, validators, controllers in Domain/ + Infrastructure/ + Application/ + Api/')
75
+
76
+ Agent(subagent_type='Explore', model='sonnet',
77
+ prompt='Scan frontend for module {module_code}: pages, components, hooks,
78
+ i18n files, route definitions in src/pages/ + src/components/ + src/locales/')
79
+
80
+ Agent(subagent_type='Explore', model='sonnet',
81
+ prompt='Read context artifacts for module {module_code}: .ralph/prd-{module_code}.json,
82
+ docs/business/**/feature.json → extract sections, entities, rules, permissions, ACs')
83
+ ```
84
+
85
+ Agent principal aggregates all results after all agents complete.
86
+
87
+ ### Step-03 Execute: Split by entity WITHIN a layer
88
+
89
+ > **Cross-layer parallelism is FORBIDDEN.** Layers execute sequentially: 0 → 1 → 2 → 3 → 4.
90
+ > Parallel agents handle **multi-entity work within a single layer.**
91
+
92
+ ```
93
+ Layer 2 (Backend) — if multiple entities:
94
+ Agent(subagent_type='Snipper', model='opus',
95
+ prompt='Execute Layer 2 backend for {Entity1}: service, DTO, controller, validators...')
96
+ Agent(subagent_type='Snipper', model='opus',
97
+ prompt='Execute Layer 2 backend for {Entity2}: service, DTO, controller, validators...')
98
+ # Launched in parallel in a single message
99
+
100
+ Layer 3 (Frontend) — if multiple entities:
101
+ Agent(subagent_type='Snipper', model='opus',
102
+ prompt='Execute Layer 3 frontend for {Entity1}: pages, i18n, routes...')
103
+ Agent(subagent_type='Snipper', model='opus',
104
+ prompt='Execute Layer 3 frontend for {Entity2}: pages, i18n, routes...')
105
+ # Launched in parallel in a single message
106
+ ```
107
+
108
+ Each agent has an **isolated scope**: handles one entity end-to-end within the layer.
109
+
110
+ ---
111
+
112
+ ## Coordination
113
+
114
+ ### Step-01: Agent principal aggregates scan results
115
+
116
+ ```
117
+ 1. Launch 2-3 Explore agents in parallel
118
+ 2. Each returns findings when complete
119
+ 3. Agent principal merges into unified analysis
120
+ ```
121
+
122
+ ### Step-03: Agent principal verifies between ALL 5 layers
123
+
124
+ ```
125
+ 1. Layer 0: agent principal executes (domain + infra + migration)
126
+ 2. Build gate: dotnet build → MUST PASS
127
+ 3. Layer 1: agent principal executes (seed data — sequential, no parallel agents)
128
+ 4. Build gate: dotnet build → MUST PASS
129
+ 5. Layer 2: launch Snipper agents per entity (if multi-entity) OR agent principal (if single)
130
+ 6. Build gate: dotnet build → MUST PASS
131
+ 7. Backend tests inline (scaffold + run + fix max 3)
132
+ 8. Layer 3: launch Snipper agents per entity (if multi-entity) OR agent principal (if single)
133
+ 9. Compliance gate: 5 frontend checks → MUST PASS
134
+ 10. Frontend tests inline (scaffold + run + fix max 3)
135
+ 11. Layer 4 (optional): agent principal executes DevData
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Idle Handling
141
+
142
+ Agents launched via the Agent tool return their results when complete.
143
+ There is no idle state to manage — the agent principal simply waits for results.
144
+
145
+ ---
146
+
147
+ ## Decision Matrix
148
+
149
+ | Condition | Action |
150
+ |-----------|--------|
151
+ | economy_mode = true | NO parallel agents, all sequential |
152
+ | Single entity (any layer) | NO parallel agents, agent principal handles all |
153
+ | Multiple entities, Layer 2 | Parallel: one Snipper agent per entity (service + controller) |
154
+ | Multiple entities, Layer 3 | Parallel: one Snipper agent per entity (pages + i18n) |
155
+ | Layer 0, Layer 1, Layer 4 | NO parallel agents (sequential by nature) |
156
+ | Analysis phase (step-01) | Parallel: 2-3 Explore agents (backend + frontend + context) |
@@ -38,7 +38,7 @@ public class Employee : BaseEntity, ITenantEntity, IAuditableEntity
38
38
  public string? CreatedBy { get; set; }
39
39
  public string? UpdatedBy { get; set; }
40
40
 
41
- // === USER LINK (Person Extension — mandatory) ===
41
+ // === USER LINK (Person Extension — required) ===
42
42
  public Guid UserId { get; private set; }
43
43
  public User? User { get; private set; }
44
44
 
@@ -167,7 +167,7 @@ public class EmployeeConfiguration : IEntityTypeConfiguration<Employee>
167
167
  builder.HasIndex(x => x.TenantId)
168
168
  .HasDatabaseName("IX_{prefix}Employees_TenantId");
169
169
 
170
- // User link (mandatory)
170
+ // User link (required)
171
171
  builder.Property(x => x.UserId).IsRequired();
172
172
  builder.HasOne(e => e.User)
173
173
  .WithMany()
@@ -251,8 +251,8 @@ public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
251
251
  ```
252
252
 
253
253
  **Key differences:**
254
- - Mandatory: `.IsRequired()` on UserId, plain `IsUnique()` on `(TenantId, UserId)`
255
- - Optional: `.IsRequired(false)` on UserId, `IsUnique().HasFilter("[UserId] IS NOT NULL")` on `(TenantId, UserId)`
254
+ - Required variant: `.IsRequired()` on UserId, plain `IsUnique()` on `(TenantId, UserId)`
255
+ - Optional variant: `.IsRequired(false)` on UserId, `IsUnique().HasFilter("[UserId] IS NOT NULL")` on `(TenantId, UserId)`
256
256
  - Optional: `builder.Ignore()` on computed `Display*` properties (not persisted)
257
257
 
258
258
  ---
@@ -277,7 +277,7 @@ public class EmployeeService : IEmployeeService
277
277
  ?? throw new TenantContextRequiredException();
278
278
 
279
279
  var query = _db.Employees
280
- .Include(x => x.User) // MANDATORY — always load User for display
280
+ .Include(x => x.User) // Always load User for display
281
281
  .Where(x => x.TenantId == tenantId)
282
282
  .AsNoTracking();
283
283
 
@@ -350,7 +350,7 @@ public class CustomerService : ICustomerService
350
350
  ?? throw new TenantContextRequiredException();
351
351
 
352
352
  var query = _db.Customers
353
- .Include(x => x.User) // MANDATORY — load User for Display* resolution
353
+ .Include(x => x.User) // Load User for Display* resolution
354
354
  .Where(x => x.TenantId == tenantId)
355
355
  .AsNoTracking();
356
356
 
@@ -409,8 +409,8 @@ public class CustomerService : ICustomerService
409
409
  **Key service rules:**
410
410
  - Both variants: `Include(x => x.User)` on ALL queries
411
411
  - Both variants: inject `ICoreDbContext` for User reads (User is in the core schema)
412
- - Mandatory: search on `User.FirstName`, `User.LastName`, `User.Email` directly
413
- - Optional: search fallback `User.FirstName ?? x.FirstName`
412
+ - Mandatory variant: search on `User.FirstName`, `User.LastName`, `User.Email` directly
413
+ - Optional variant: search fallback `User.FirstName ?? x.FirstName`
414
414
  - Both: CreateAsync verifies User exists + no duplicate `(TenantId, UserId)`
415
415
 
416
416
  ---
@@ -422,7 +422,7 @@ public class CustomerService : ICustomerService
422
422
  ```csharp
423
423
  // CreateDto — only UserId + business fields (no person fields)
424
424
  public record CreateEmployeeDto(
425
- Guid UserId, // mandatory — links to existing User
425
+ Guid UserId, // required — links to existing User
426
426
  string Code,
427
427
  DateOnly HireDate
428
428
  );
@@ -481,8 +481,8 @@ public record CustomerResponseDto(
481
481
  ```
482
482
 
483
483
  **Key DTO rules:**
484
- - Mandatory CreateDto: `Guid UserId` + business fields only (no name/email)
485
- - Optional CreateDto: `Guid? UserId` + person fields (FirstName, LastName, Email)
484
+ - Mandatory variant CreateDto: `Guid UserId` + business fields only (no name/email)
485
+ - Optional variant CreateDto: `Guid? UserId` + person fields (FirstName, LastName, Email)
486
486
  - ResponseDto (both): includes `Display*` fields resolved from User or own fields
487
487
  - UserId is NOT in UpdateDto — the person link is set at creation time
488
488
 
@@ -493,7 +493,7 @@ public record CustomerResponseDto(
493
493
  ### Mandatory — CreatePage
494
494
 
495
495
  ```tsx
496
- // EntityLookup for User selection (mandatory)
496
+ // EntityLookup for User selection (required)
497
497
  <EntityLookup
498
498
  apiEndpoint="/api/administration/users"
499
499
  value={formData.userId}