@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.
- package/package.json +1 -1
- package/templates/agents/ba-reader.md +86 -80
- package/templates/agents/ba-writer.md +321 -413
- package/templates/agents/docs-context-reader.md +3 -3
- package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +133 -0
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +126 -0
- package/templates/skills/apex/SKILL.md +29 -16
- package/templates/skills/apex/_shared.md +62 -9
- package/templates/skills/apex/references/analysis-methods.md +8 -6
- package/templates/skills/apex/references/challenge-questions.md +5 -5
- package/templates/skills/apex/references/core-seed-data.md +68 -45
- package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +26 -21
- package/templates/skills/apex/references/parallel-execution.md +156 -0
- package/templates/skills/apex/references/person-extension-pattern.md +12 -12
- package/templates/skills/apex/references/post-checks.md +1748 -1726
- package/templates/skills/apex/references/smartstack-api.md +63 -57
- package/templates/skills/apex/references/smartstack-frontend-compliance.md +594 -0
- package/templates/skills/apex/references/smartstack-frontend.md +1246 -1842
- package/templates/skills/apex/references/smartstack-layers.md +98 -145
- package/templates/skills/apex/steps/step-00-init.md +30 -6
- package/templates/skills/apex/steps/step-01-analyze.md +27 -23
- package/templates/skills/apex/steps/step-02-plan.md +12 -12
- package/templates/skills/apex/steps/step-03-execute.md +198 -143
- package/templates/skills/apex/steps/step-04-examine.md +24 -93
- package/templates/skills/apex/steps/step-05-deep-review.md +16 -16
- package/templates/skills/apex/steps/step-06-resolve.md +9 -9
- package/templates/skills/apex/steps/step-07-tests.md +3 -1
- package/templates/skills/apex/steps/step-08-run-tests.md +1 -1
- package/templates/skills/business-analyse/SKILL.md +182 -301
- package/templates/skills/business-analyse/_shared.md +119 -336
- package/templates/skills/business-analyse/html/ba-interactive.html +703 -82
- package/templates/skills/business-analyse/html/build-html.js +41 -3
- package/templates/skills/business-analyse/html/src/partials/cadrage-context.html +34 -0
- package/templates/skills/business-analyse/html/src/partials/cadrage-risks.html +48 -0
- package/templates/skills/business-analyse/html/src/partials/cadrage-scope.html +49 -0
- package/templates/skills/business-analyse/html/src/partials/cadrage-stakeholders.html +55 -0
- package/templates/skills/business-analyse/html/src/partials/cadrage-success.html +34 -0
- package/templates/skills/business-analyse/html/src/partials/consol-datamodel.html +8 -0
- package/templates/skills/business-analyse/html/src/partials/consol-flows.html +29 -0
- package/templates/skills/business-analyse/html/src/partials/consol-interactions.html +8 -0
- package/templates/skills/business-analyse/html/src/partials/consol-permissions.html +8 -0
- package/templates/skills/business-analyse/html/src/partials/decomp-dependencies.html +38 -0
- package/templates/skills/business-analyse/html/src/partials/decomp-modules.html +51 -0
- package/templates/skills/business-analyse/html/src/partials/handoff-summary.html +24 -0
- package/templates/skills/business-analyse/html/src/partials/module-spec-container.html +4 -0
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +17 -1
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +31 -5
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +100 -63
- package/templates/skills/business-analyse/html/src/scripts/06-render-mockups.js +372 -0
- package/templates/skills/business-analyse/html/src/scripts/10-comments.js +41 -13
- package/templates/skills/business-analyse/html/src/styles/09-mockups-html.css +136 -0
- package/templates/skills/business-analyse/patterns/suggestion-catalog.md +7 -5
- package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +142 -0
- package/templates/skills/business-analyse/questionnaire/03-data-ui.md +94 -0
- package/templates/skills/business-analyse/questionnaire/04-risks-metrics.md +150 -0
- package/templates/skills/business-analyse/questionnaire/05-cross-module.md +69 -0
- package/templates/skills/business-analyse/questionnaire.md +23 -280
- package/templates/skills/business-analyse/react/application-viewer.md +2 -2
- package/templates/skills/business-analyse/react/components.md +4 -4
- package/templates/skills/business-analyse/react/i18n-template.md +1 -1
- package/templates/skills/business-analyse/react/schema.md +14 -14
- package/templates/skills/business-analyse/references/acceptance-criteria.md +25 -25
- package/templates/skills/business-analyse/references/analysis-semantic-checks.md +3 -3
- package/templates/skills/business-analyse/references/compilation-structure-cards.md +1 -1
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +7 -7
- package/templates/skills/business-analyse/references/deploy-data-build.md +14 -12
- package/templates/skills/business-analyse/references/deploy-modes.md +10 -10
- package/templates/skills/business-analyse/references/detection-strategies.md +6 -6
- package/templates/skills/business-analyse/references/html-data-mapping.md +15 -15
- package/templates/skills/business-analyse/references/naming-conventions.md +4 -4
- package/templates/skills/business-analyse/references/review-data-mapping.md +29 -29
- package/templates/skills/business-analyse/references/robustness-checks.md +36 -33
- package/templates/skills/business-analyse/references/spec-auto-inference.md +2 -2
- package/templates/skills/business-analyse/references/ui-dashboard-spec.md +1 -1
- package/templates/skills/business-analyse/references/ui-resource-cards.md +1 -1
- package/templates/skills/business-analyse/references/validation-checklist.md +9 -6
- package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +2 -2
- package/templates/skills/business-analyse/schemas/application-schema.json +8 -8
- package/templates/skills/business-analyse/schemas/feature-schema.json +3 -3
- package/templates/skills/business-analyse/schemas/index-schema.json +47 -0
- package/templates/skills/business-analyse/schemas/project-schema.json +6 -6
- package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +1 -1
- package/templates/skills/business-analyse/schemas/sections/handoff-schema.json +5 -3
- package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +4 -4
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +1 -1
- package/templates/skills/business-analyse/schemas/shared/common-defs.json +4 -3
- package/templates/skills/business-analyse/steps/step-00-init.md +93 -134
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +136 -172
- package/templates/skills/business-analyse/steps/step-02-structure.md +175 -0
- package/templates/skills/business-analyse/steps/step-03-specify.md +198 -0
- package/templates/skills/business-analyse/steps/step-04-consolidate.md +478 -0
- package/templates/skills/business-analyse/steps/step-05-deploy.md +220 -0
- package/templates/skills/business-analyse/steps/step-06-review.md +51 -69
- package/templates/skills/business-analyse/templates/tpl-frd.md +1 -1
- package/templates/skills/business-analyse/templates/tpl-handoff.md +20 -17
- package/templates/skills/business-analyse/templates/tpl-launch-displays.md +2 -2
- package/templates/skills/business-analyse/templates-react.md +2 -2
- package/templates/skills/derive-prd/SKILL.md +92 -0
- package/templates/skills/derive-prd/references/acceptance-criteria.md +169 -0
- package/templates/skills/derive-prd/references/entity-domain-mapping.md +115 -0
- package/templates/skills/{business-analyse → derive-prd}/references/handoff-file-templates.md +131 -120
- package/templates/skills/{business-analyse → derive-prd}/references/handoff-mappings.md +95 -95
- package/templates/skills/{business-analyse → derive-prd}/references/handoff-seeddata-generation.md +312 -312
- package/templates/skills/{business-analyse → derive-prd}/references/prd-generation.md +262 -258
- package/templates/skills/derive-prd/references/readiness-scoring.md +104 -0
- package/templates/skills/derive-prd/schemas/handoff-schema.json +95 -0
- package/templates/skills/derive-prd/steps/step-00-validate.md +130 -0
- package/templates/skills/derive-prd/steps/step-01-transform.md +206 -0
- package/templates/skills/derive-prd/steps/step-02-export.md +181 -0
- package/templates/skills/{business-analyse → derive-prd}/templates/tpl-progress.md +172 -172
- package/templates/skills/ralph-loop/SKILL.md +10 -4
- package/templates/skills/ralph-loop/references/category-completeness.md +20 -4
- package/templates/skills/ralph-loop/references/compact-loop.md +80 -48
- package/templates/skills/ralph-loop/references/init-resume-recovery.md +4 -2
- package/templates/skills/ralph-loop/references/parallel-execution.md +27 -27
- package/templates/skills/ralph-loop/steps/step-00-init.md +19 -9
- package/templates/skills/ralph-loop/steps/step-01-task.md +12 -4
- package/templates/skills/ralph-loop/steps/step-02-execute.md +9 -4
- package/templates/skills/ralph-loop/steps/step-03-commit.md +1 -1
- package/templates/skills/ralph-loop/steps/step-04-check.md +5 -21
- package/templates/skills/ralph-loop/steps/step-05-report.md +6 -1
- package/templates/skills/apex/references/agent-teams-protocol.md +0 -203
- package/templates/skills/business-analyse/_architecture.md +0 -124
- package/templates/skills/business-analyse/_elicitation.md +0 -206
- package/templates/skills/business-analyse/_module-loop.md +0 -115
- package/templates/skills/business-analyse/_suggestions.md +0 -34
- package/templates/skills/business-analyse/questionnaire/00-application.md +0 -160
- package/templates/skills/business-analyse/questionnaire/00b-project.md +0 -85
- package/templates/skills/business-analyse/questionnaire/02-stakeholders.md +0 -189
- package/templates/skills/business-analyse/questionnaire/03-scope.md +0 -164
- package/templates/skills/business-analyse/questionnaire/04-data.md +0 -88
- package/templates/skills/business-analyse/questionnaire/05-integrations.md +0 -58
- package/templates/skills/business-analyse/questionnaire/06-security.md +0 -68
- package/templates/skills/business-analyse/questionnaire/07-ui.md +0 -76
- package/templates/skills/business-analyse/questionnaire/08-performance.md +0 -42
- package/templates/skills/business-analyse/questionnaire/09-constraints.md +0 -45
- package/templates/skills/business-analyse/questionnaire/10-documentation.md +0 -43
- package/templates/skills/business-analyse/questionnaire/11-data-lifecycle.md +0 -59
- package/templates/skills/business-analyse/questionnaire/12-migration.md +0 -58
- package/templates/skills/business-analyse/questionnaire/13-cross-module.md +0 -69
- package/templates/skills/business-analyse/questionnaire/14-risk-assumptions.md +0 -135
- package/templates/skills/business-analyse/questionnaire/15-success-metrics.md +0 -136
- package/templates/skills/business-analyse/references/agent-module-prompt.md +0 -362
- package/templates/skills/business-analyse/references/agent-pooling-best-practices.md +0 -557
- package/templates/skills/business-analyse/references/cache-warming-strategy.md +0 -566
- package/templates/skills/business-analyse/references/cadrage-challenge-patterns.md +0 -41
- package/templates/skills/business-analyse/references/cadrage-coverage-matrix.md +0 -74
- package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +0 -115
- package/templates/skills/business-analyse/references/cadrage-shared-modules.md +0 -68
- package/templates/skills/business-analyse/references/cadrage-structure-cards.md +0 -85
- package/templates/skills/business-analyse/references/team-orchestration.md +0 -1022
- package/templates/skills/business-analyse/references/validate-incremental-html.md +0 -121
- package/templates/skills/business-analyse/steps/step-01b-applications.md +0 -419
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +0 -387
- package/templates/skills/business-analyse/steps/step-03a-data.md +0 -16
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +0 -506
- package/templates/skills/business-analyse/steps/step-03a2-analysis.md +0 -301
- package/templates/skills/business-analyse/steps/step-03b-ui.md +0 -425
- package/templates/skills/business-analyse/steps/step-03c-compile.md +0 -611
- package/templates/skills/business-analyse/steps/step-03d-validate.md +0 -783
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +0 -17
- package/templates/skills/business-analyse/steps/step-04a-collect.md +0 -415
- package/templates/skills/business-analyse/steps/step-04b-analyze.md +0 -163
- package/templates/skills/business-analyse/steps/step-04c-decide.md +0 -186
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +0 -840
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +0 -522
- 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
|
-
>
|
|
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
|
|
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
|
-
//
|
|
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
|
-
> -
|
|
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
|
-
//
|
|
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
|
-
###
|
|
602
|
+
### PermissionAction Safety Rules
|
|
583
603
|
|
|
584
|
-
>
|
|
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
|
|
623
|
+
**Anti-patterns:**
|
|
604
624
|
|
|
605
625
|
```csharp
|
|
606
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
>
|
|
664
|
+
> Permission paths use the SAME kebab-case as NavRoute codes.
|
|
645
665
|
> `{navRoute}` is already kebab-case (e.g., `human-resources.employees`).
|
|
646
|
-
>
|
|
647
|
-
>
|
|
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
|
-
>
|
|
773
|
-
> If system roles already exist in `auth_Roles`, do
|
|
774
|
-
> `SeedRolesAsync()`
|
|
775
|
-
>
|
|
776
|
-
>
|
|
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
|
-
|
|
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 —
|
|
788
|
-
//
|
|
789
|
-
|
|
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
|
-
>
|
|
910
|
+
> This file uses `RoleCode` (string), not role GUIDs.
|
|
891
911
|
> Roles are resolved by Code at runtime in `SeedRolePermissionsAsync()`.
|
|
892
|
-
>
|
|
893
|
-
> SmartStack core pre-seeds system roles — their IDs are
|
|
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(...)` —
|
|
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
|
-
.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
1107
|
-
// The GUID from GetSectionEntries() is
|
|
1108
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
- [ ]
|
|
1306
|
-
- [ ]
|
|
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 (
|
|
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 (
|
|
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
|
|
1341
|
-
| Deterministic TenantId | Use `SeedConstants.DefaultTenantId` (
|
|
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, //
|
|
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
|
-
###
|
|
1400
|
+
### Anti-patterns
|
|
1378
1401
|
|
|
1379
|
-
- Seeding business entities
|
|
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
|
|
7
|
+
## Step 4: Wire Routes to App.tsx
|
|
8
8
|
|
|
9
|
-
**
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
106
|
+
## Step 4b.5: Verify mergeRoutes() Call
|
|
102
107
|
|
|
103
|
-
**
|
|
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,
|
|
121
|
+
Without it, all application routes are silently ignored → blank page or /login redirect.
|
|
117
122
|
|
|
118
|
-
**
|
|
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
|
|
149
|
+
## Step 4e: Parent Redirect Routes
|
|
145
150
|
|
|
146
|
-
**
|
|
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,
|
|
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
|
-
##
|
|
177
|
+
## Patterns to Avoid (BOTH patterns)
|
|
173
178
|
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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 —
|
|
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 (
|
|
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
|
-
-
|
|
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) //
|
|
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) //
|
|
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, //
|
|
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 (
|
|
496
|
+
// EntityLookup for User selection (required)
|
|
497
497
|
<EntityLookup
|
|
498
498
|
apiEndpoint="/api/administration/users"
|
|
499
499
|
value={formData.userId}
|