@atlashub/smartstack-cli 3.43.0 → 3.44.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/dist/mcp-entry.mjs +201 -22
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/efcore/migration.md +11 -0
- package/templates/agents/efcore/rebase-snapshot.md +7 -0
- package/templates/agents/efcore/squash.md +7 -0
- package/templates/skills/apex/SKILL.md +14 -9
- package/templates/skills/apex/_shared.md +3 -0
- package/templates/skills/apex/references/analysis-methods.md +1 -1
- package/templates/skills/apex/references/challenge-questions.md +21 -0
- package/templates/skills/apex/references/core-seed-data.md +59 -104
- package/templates/skills/apex/references/post-checks.md +289 -225
- package/templates/skills/apex/references/smartstack-api.md +33 -35
- package/templates/skills/apex/references/smartstack-frontend.md +99 -3
- package/templates/skills/apex/references/smartstack-layers.md +145 -23
- package/templates/skills/apex/steps/step-00-init.md +2 -2
- package/templates/skills/apex/steps/step-01-analyze.md +1 -0
- package/templates/skills/apex/steps/step-02-plan.md +4 -3
- package/templates/skills/apex/steps/step-03-execute.md +24 -24
- package/templates/skills/apex/steps/step-04-examine.md +64 -24
- package/templates/skills/apex/steps/step-05-deep-review.md +1 -1
- package/templates/skills/apex/steps/step-08-run-tests.md +21 -13
- package/templates/skills/application/references/application-roles-template.md +10 -15
- package/templates/skills/application/references/backend-entity-seeding.md +6 -5
- package/templates/skills/application/references/backend-seeding-and-dto-output.md +1 -1
- package/templates/skills/application/references/nav-fallback-procedure.md +14 -17
- package/templates/skills/application/references/provider-template.md +5 -5
- package/templates/skills/application/references/roles-client-project-handling.md +1 -1
- package/templates/skills/application/references/roles-fallback-procedure.md +10 -15
- package/templates/skills/application/steps/step-01-navigation.md +1 -1
- package/templates/skills/application/steps/step-02-permissions.md +3 -3
- package/templates/skills/application/steps/step-03b-provider.md +1 -0
- package/templates/skills/application/templates-seed.md +41 -47
- package/templates/skills/business-analyse/references/team-orchestration.md +2 -2
- package/templates/skills/controller/steps/step-04-perms.md +1 -1
- package/templates/skills/efcore/references/troubleshooting.md +2 -2
- package/templates/skills/apex/references/examine-build-validation.md +0 -82
- package/templates/skills/apex/references/execution-frontend-gates.md +0 -177
- package/templates/skills/apex/references/execution-frontend-patterns.md +0 -105
- package/templates/skills/apex/references/execution-layer1-rules.md +0 -96
- package/templates/skills/apex/references/initialization-challenge-flow.md +0 -110
- package/templates/skills/apex/references/planning-layer-mapping.md +0 -151
|
@@ -3,43 +3,34 @@
|
|
|
3
3
|
> **Referenced by:** step-04-examine.md (section 6b)
|
|
4
4
|
> These checks run on the actual generated files. Model-interpreted checks are unreliable.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
## Run MCP Tools FIRST
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
echo "Expected: \"/human-resources\" NOT \"humanresources\""
|
|
18
|
-
exit 1
|
|
19
|
-
fi
|
|
20
|
-
fi
|
|
21
|
-
```
|
|
8
|
+
Before running these bash checks, call these 3 MCP tools (they cover ~10 additional checks automatically):
|
|
9
|
+
|
|
10
|
+
1. `mcp__smartstack__validate_conventions()` — covers: controller routes, NavRoute kebab-case, naming conventions
|
|
11
|
+
2. `mcp__smartstack__validate_security()` — covers: tenant isolation, TenantId filters, authorization, Guid.Empty, TenantId!.Value patterns
|
|
12
|
+
3. `mcp__smartstack__validate_frontend_routes()` — covers: lazy loading in routes, route alignment
|
|
13
|
+
|
|
14
|
+
**The checks below provide defense-in-depth** — they catch patterns the MCP tools may miss and serve as a safety net.
|
|
15
|
+
|
|
16
|
+
---
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
## Security — Tenant Isolation & Authorization (defense-in-depth with MCP)
|
|
19
|
+
|
|
20
|
+
### POST-CHECK S1: All services must filter by TenantId (OWASP A01)
|
|
24
21
|
|
|
25
22
|
```bash
|
|
26
|
-
# Find all service implementation files
|
|
27
23
|
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
28
24
|
if [ -n "$SERVICE_FILES" ]; then
|
|
29
|
-
# Check each service file has TenantId reference (either _currentUser.TenantId or TenantId filter)
|
|
30
25
|
for f in $SERVICE_FILES; do
|
|
31
|
-
# Accept either _currentTenant.TenantId (strict guard clause or nullable usage)
|
|
32
|
-
# or entities with IOptionalTenantEntity/IScopedTenantEntity (optional tenant pattern)
|
|
33
26
|
HAS_TENANT_FILTER=$(grep -c "TenantId" "$f")
|
|
34
27
|
HAS_OPTIONAL_ENTITY=false
|
|
35
28
|
if grep -q "IOptionalTenantEntity\|IScopedTenantEntity" "$f"; then
|
|
36
29
|
HAS_OPTIONAL_ENTITY=true
|
|
37
30
|
fi
|
|
38
|
-
|
|
39
31
|
if [ "$HAS_TENANT_FILTER" -eq 0 ] && [ "$HAS_OPTIONAL_ENTITY" = false ]; then
|
|
40
32
|
echo "BLOCKING (OWASP A01): Service missing TenantId filter or optional tenant entity: $f"
|
|
41
33
|
echo "Every service query MUST filter by _currentTenant.TenantId"
|
|
42
|
-
echo "OR work with entities that implement IOptionalTenantEntity/IScopedTenantEntity"
|
|
43
34
|
exit 1
|
|
44
35
|
fi
|
|
45
36
|
if grep -q "Guid.Empty" "$f"; then
|
|
@@ -50,14 +41,12 @@ if [ -n "$SERVICE_FILES" ]; then
|
|
|
50
41
|
fi
|
|
51
42
|
```
|
|
52
43
|
|
|
53
|
-
### POST-CHECK
|
|
44
|
+
### POST-CHECK S2: Controllers must use [RequirePermission], not just [Authorize] (BLOCKING)
|
|
54
45
|
|
|
55
46
|
```bash
|
|
56
|
-
# Find all controller files
|
|
57
47
|
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
58
48
|
if [ -n "$CTRL_FILES" ]; then
|
|
59
49
|
for f in $CTRL_FILES; do
|
|
60
|
-
# Check controller has at least one RequirePermission attribute
|
|
61
50
|
if grep -q "\[Authorize\]" "$f" && ! grep -q "\[RequirePermission" "$f"; then
|
|
62
51
|
echo "BLOCKING: Controller uses [Authorize] without [RequirePermission]: $f"
|
|
63
52
|
echo "[Authorize] alone provides NO RBAC enforcement — any authenticated user has access"
|
|
@@ -68,38 +57,104 @@ if [ -n "$CTRL_FILES" ]; then
|
|
|
68
57
|
fi
|
|
69
58
|
```
|
|
70
59
|
|
|
71
|
-
### POST-CHECK
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
|
|
75
|
-
if [ -n "$SEED_FILES" ]; then
|
|
76
|
-
BAD_GUIDS=$(grep -n "Guid.NewGuid()" $SEED_FILES 2>/dev/null)
|
|
77
|
-
if [ -n "$BAD_GUIDS" ]; then
|
|
78
|
-
echo "BLOCKING: Seed data must use deterministic GUIDs (SHA256), not Guid.NewGuid()"
|
|
79
|
-
echo "$BAD_GUIDS"
|
|
80
|
-
exit 1
|
|
81
|
-
fi
|
|
82
|
-
fi
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### POST-CHECK 5: Services must inject ICurrentTenantService (tenant isolation)
|
|
60
|
+
### POST-CHECK S3: Services must inject ICurrentTenantService (tenant isolation)
|
|
86
61
|
|
|
87
62
|
```bash
|
|
88
63
|
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
89
64
|
if [ -n "$SERVICE_FILES" ]; then
|
|
90
65
|
for f in $SERVICE_FILES; do
|
|
91
|
-
# Accept either ICurrentTenantService or ICurrentUser (legacy) for tenant context
|
|
92
66
|
if ! grep -qE "ICurrentTenantService|ICurrentUser" "$f"; then
|
|
93
67
|
echo "BLOCKING: Service missing tenant context injection: $f"
|
|
94
68
|
echo "All services MUST inject ICurrentTenantService for tenant isolation"
|
|
95
|
-
echo "Pattern: private readonly ICurrentTenantService _currentTenant;"
|
|
96
69
|
exit 1
|
|
97
70
|
fi
|
|
98
71
|
done
|
|
99
72
|
fi
|
|
100
73
|
```
|
|
101
74
|
|
|
102
|
-
### POST-CHECK
|
|
75
|
+
### POST-CHECK S4: HasQueryFilter must not use Guid.Empty (OWASP A01)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
CONFIG_FILES=$(find src/ -path "*/Configurations/*" -name "*Configuration.cs" 2>/dev/null)
|
|
79
|
+
if [ -n "$CONFIG_FILES" ]; then
|
|
80
|
+
BAD_FILTER=$(grep -Pn 'HasQueryFilter.*Guid\.Empty' $CONFIG_FILES 2>/dev/null)
|
|
81
|
+
if [ -n "$BAD_FILTER" ]; then
|
|
82
|
+
echo "BLOCKING (OWASP A01): HasQueryFilter uses Guid.Empty — bypasses tenant isolation: $BAD_FILTER"
|
|
83
|
+
echo "The EF global query filter MUST use the injected TenantId, not Guid.Empty"
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### POST-CHECK S5: Services must NOT use TenantId!.Value (null-forgiving crash pattern)
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
93
|
+
if [ -n "$SERVICE_FILES" ]; then
|
|
94
|
+
BAD_PATTERN=$(grep -Pn 'TenantId!\.' $SERVICE_FILES 2>/dev/null)
|
|
95
|
+
if [ -n "$BAD_PATTERN" ]; then
|
|
96
|
+
echo "BLOCKING: TenantId!.Value (null-forgiving) detected — NullReferenceException risk: $BAD_PATTERN"
|
|
97
|
+
echo "Fix: Use guard clause: var tenantId = _currentTenant.TenantId ?? throw new TenantContextRequiredException();"
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### POST-CHECK S6: Pages must use lazy loading (no static page imports in routes)
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
107
|
+
ROUTE_FILES=$(find src/routes/ -name "*.tsx" -o -name "*.ts" 2>/dev/null)
|
|
108
|
+
if [ -n "$APP_TSX" ]; then
|
|
109
|
+
STATIC_IMPORTS=$(grep -Pn "^import .+ from '@/pages/" "$APP_TSX" $ROUTE_FILES 2>/dev/null)
|
|
110
|
+
if [ -n "$STATIC_IMPORTS" ]; then
|
|
111
|
+
echo "BLOCKING: Static page imports in route files — MUST use React.lazy()"
|
|
112
|
+
echo "Static imports load ALL pages upfront, killing initial load performance"
|
|
113
|
+
echo "$STATIC_IMPORTS"
|
|
114
|
+
echo "Fix: const Page = lazy(() => import('@/pages/...').then(m => ({ default: m.Page })))"
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
fi
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Backend — Entity, Service & Controller Checks
|
|
123
|
+
|
|
124
|
+
### POST-CHECK 1: Navigation routes must be full paths starting with /
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Find all seed data files and check route values
|
|
128
|
+
SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
|
|
129
|
+
if [ -n "$SEED_FILES" ]; then
|
|
130
|
+
# Check for short routes (no leading /) in Create() calls for navigation entities
|
|
131
|
+
BAD_ROUTES=$(grep -Pn 'NavigationApplication\.Create\(|NavigationModule\.Create\(|NavigationSection\.Create\(|NavigationResource\.Create\(' $SEED_FILES | grep -v '"/[a-z]')
|
|
132
|
+
if [ -n "$BAD_ROUTES" ]; then
|
|
133
|
+
echo "BLOCKING: Navigation routes must be full paths starting with /"
|
|
134
|
+
echo "$BAD_ROUTES"
|
|
135
|
+
echo "Expected: \"/human-resources\" NOT \"humanresources\""
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### POST-CHECK 2: Seed data must not use deterministic/sequential/fixed GUIDs
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
|
|
145
|
+
if [ -n "$SEED_FILES" ]; then
|
|
146
|
+
BAD_GUIDS=$(grep -Pn 'GenerateDeterministicGuid|GenerateGuid\(int|11111111-1111-1111-1111-' $SEED_FILES 2>/dev/null)
|
|
147
|
+
if [ -n "$BAD_GUIDS" ]; then
|
|
148
|
+
echo "BLOCKING: Seed data must use Guid.NewGuid(), not deterministic/sequential/fixed GUIDs"
|
|
149
|
+
echo "$BAD_GUIDS"
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Frontend — CSS, Forms, Components, I18n
|
|
156
|
+
|
|
157
|
+
### POST-CHECK 3: Translation files must exist for all 4 languages (if frontend)
|
|
103
158
|
|
|
104
159
|
```bash
|
|
105
160
|
# Find all i18n namespaces used in tsx files
|
|
@@ -117,22 +172,7 @@ if [ -n "$TSX_FILES" ]; then
|
|
|
117
172
|
fi
|
|
118
173
|
```
|
|
119
174
|
|
|
120
|
-
### POST-CHECK
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
ROUTE_FILES=$(find src/routes/ -name "*.tsx" -o -name "*.ts" 2>/dev/null)
|
|
124
|
-
if [ -n "$ROUTE_FILES" ]; then
|
|
125
|
-
STATIC_PAGE_IMPORTS=$(grep -Pn "^import .+ from '@/pages/" $ROUTE_FILES 2>/dev/null)
|
|
126
|
-
if [ -n "$STATIC_PAGE_IMPORTS" ]; then
|
|
127
|
-
echo "BLOCKING: Route files must use React.lazy() for page imports, not static imports"
|
|
128
|
-
echo "$STATIC_PAGE_IMPORTS"
|
|
129
|
-
echo "Fix: const Page = lazy(() => import('@/pages/...').then(m => ({ default: m.Page })));"
|
|
130
|
-
exit 1
|
|
131
|
-
fi
|
|
132
|
-
fi
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### POST-CHECK 8: Forms must be full pages with routes — ZERO modals/popups/drawers/slide-overs
|
|
175
|
+
### POST-CHECK 4: Forms must be full pages with routes — ZERO modals/popups/drawers/slide-overs
|
|
136
176
|
|
|
137
177
|
```bash
|
|
138
178
|
# Check for modal/dialog/drawer/slide-over imports AND inline form state in page files
|
|
@@ -166,7 +206,7 @@ if [ -n "$PAGE_FILES" ]; then
|
|
|
166
206
|
fi
|
|
167
207
|
```
|
|
168
208
|
|
|
169
|
-
### POST-CHECK
|
|
209
|
+
### POST-CHECK 5: Create/Edit pages must exist as separate route pages (BLOCKING)
|
|
170
210
|
|
|
171
211
|
```bash
|
|
172
212
|
# For each module with a list page, verify create and edit pages exist
|
|
@@ -219,7 +259,7 @@ if [ "$FAIL" = true ]; then
|
|
|
219
259
|
fi
|
|
220
260
|
```
|
|
221
261
|
|
|
222
|
-
### POST-CHECK
|
|
262
|
+
### POST-CHECK 6: Form pages must have companion test files
|
|
223
263
|
|
|
224
264
|
```bash
|
|
225
265
|
# Minimum requirement: if frontend pages exist, at least 1 test file must be present
|
|
@@ -248,7 +288,7 @@ if [ -n "$FORM_PAGES" ]; then
|
|
|
248
288
|
fi
|
|
249
289
|
```
|
|
250
290
|
|
|
251
|
-
### POST-CHECK
|
|
291
|
+
### POST-CHECK 7: FK fields must use EntityLookup — NO `<input>`, NO `<select>` (BLOCKING)
|
|
252
292
|
|
|
253
293
|
```bash
|
|
254
294
|
# Check ALL page files for FK fields rendered as <input> or <select> instead of EntityLookup
|
|
@@ -311,7 +351,7 @@ if [ -n "$ALL_PAGES" ]; then
|
|
|
311
351
|
fi
|
|
312
352
|
```
|
|
313
353
|
|
|
314
|
-
### POST-CHECK
|
|
354
|
+
### POST-CHECK 8: Backend APIs must support search parameter for EntityLookup
|
|
315
355
|
|
|
316
356
|
```bash
|
|
317
357
|
# Check that controller GetAll methods accept search parameter
|
|
@@ -330,7 +370,7 @@ if [ -n "$CTRL_FILES" ]; then
|
|
|
330
370
|
fi
|
|
331
371
|
```
|
|
332
372
|
|
|
333
|
-
### POST-CHECK
|
|
373
|
+
### POST-CHECK 9: No hardcoded Tailwind colors in generated pages (BLOCKING)
|
|
334
374
|
|
|
335
375
|
```bash
|
|
336
376
|
# Scan all page and component files directly (works for uncommitted/untracked files, Windows/WSL compatible)
|
|
@@ -360,7 +400,9 @@ if [ -n "$ALL_PAGES" ]; then
|
|
|
360
400
|
fi
|
|
361
401
|
```
|
|
362
402
|
|
|
363
|
-
|
|
403
|
+
## Seed Data — Navigation, Roles, Permissions
|
|
404
|
+
|
|
405
|
+
### POST-CHECK 10: Routes seed data must match frontend application routes
|
|
364
406
|
|
|
365
407
|
```bash
|
|
366
408
|
SEED_ROUTES=$(grep -Poh 'Route\s*=\s*"([^"]+)"' $(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" 2>/dev/null) 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' | sort -u)
|
|
@@ -431,24 +473,7 @@ if [ -n "$APP_TSX" ]; then
|
|
|
431
473
|
fi
|
|
432
474
|
```
|
|
433
475
|
|
|
434
|
-
### POST-CHECK
|
|
435
|
-
|
|
436
|
-
```bash
|
|
437
|
-
CONFIG_FILES=$(find src/ -path "*/Configurations/*" -name "*Configuration.cs" 2>/dev/null)
|
|
438
|
-
if [ -n "$CONFIG_FILES" ]; then
|
|
439
|
-
BAD_FILTERS=$(grep -Pn 'HasQueryFilter.*Guid\.Empty' $CONFIG_FILES 2>/dev/null)
|
|
440
|
-
if [ -n "$BAD_FILTERS" ]; then
|
|
441
|
-
echo "BLOCKING (OWASP A01): HasQueryFilter uses Guid.Empty instead of runtime tenant isolation"
|
|
442
|
-
echo "$BAD_FILTERS"
|
|
443
|
-
echo ""
|
|
444
|
-
echo "Anti-pattern: .HasQueryFilter(e => e.TenantId != Guid.Empty)"
|
|
445
|
-
echo "Fix: Remove HasQueryFilter. Tenant isolation is handled by SmartStack base DbContext"
|
|
446
|
-
exit 1
|
|
447
|
-
fi
|
|
448
|
-
fi
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
### POST-CHECK 16: GetAll methods must return PaginatedResult<T>
|
|
476
|
+
### POST-CHECK 11: GetAll methods must return PaginatedResult<T>
|
|
452
477
|
|
|
453
478
|
```bash
|
|
454
479
|
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
@@ -463,7 +488,7 @@ if [ -n "$SERVICE_FILES" ]; then
|
|
|
463
488
|
fi
|
|
464
489
|
```
|
|
465
490
|
|
|
466
|
-
### POST-CHECK
|
|
491
|
+
### POST-CHECK 12: i18n files must contain required structural keys
|
|
467
492
|
|
|
468
493
|
```bash
|
|
469
494
|
I18N_DIR="src/i18n/locales/fr"
|
|
@@ -484,7 +509,7 @@ if [ -d "$I18N_DIR" ]; then
|
|
|
484
509
|
fi
|
|
485
510
|
```
|
|
486
511
|
|
|
487
|
-
### POST-CHECK
|
|
512
|
+
### POST-CHECK 13: Entities must implement IAuditableEntity + Validators must have Create/Update pairs
|
|
488
513
|
|
|
489
514
|
```bash
|
|
490
515
|
ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null)
|
|
@@ -512,9 +537,7 @@ if [ -n "$CREATE_VALIDATORS" ]; then
|
|
|
512
537
|
fi
|
|
513
538
|
```
|
|
514
539
|
|
|
515
|
-
### POST-CHECK
|
|
516
|
-
|
|
517
|
-
### POST-CHECK 20: RolePermission seed data must NOT use deterministic role GUIDs
|
|
540
|
+
### POST-CHECK 14: RolePermission seed data must NOT use deterministic role GUIDs
|
|
518
541
|
|
|
519
542
|
```bash
|
|
520
543
|
# System roles (admin, manager, contributor, viewer) are pre-seeded by SmartStack core.
|
|
@@ -545,46 +568,7 @@ if [ -n "$ROLE_PERM_FILES" ]; then
|
|
|
545
568
|
fi
|
|
546
569
|
```
|
|
547
570
|
|
|
548
|
-
### POST-CHECK
|
|
549
|
-
|
|
550
|
-
```bash
|
|
551
|
-
# The !.Value pattern on Guid? throws InvalidOperationException (500) instead of clean 401
|
|
552
|
-
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
553
|
-
if [ -n "$SERVICE_FILES" ]; then
|
|
554
|
-
BAD_PATTERN=$(grep -Pn 'TenantId!\s*\.Value|TenantId!\s*\.ToString|\.TenantId!' $SERVICE_FILES 2>/dev/null)
|
|
555
|
-
if [ -n "$BAD_PATTERN" ]; then
|
|
556
|
-
echo "BLOCKING: Services use TenantId!.Value — causes 500 instead of 400 when tenant context is missing"
|
|
557
|
-
echo "$BAD_PATTERN"
|
|
558
|
-
echo ""
|
|
559
|
-
echo "Fix: Replace with guard clause at the start of every method:"
|
|
560
|
-
echo " var tenantId = _currentTenant.TenantId"
|
|
561
|
-
echo " ?? throw new TenantContextRequiredException();"
|
|
562
|
-
echo ""
|
|
563
|
-
echo "This produces a clean 400 Bad Request via GlobalExceptionHandlerMiddleware."
|
|
564
|
-
echo "NEVER use UnauthorizedAccessException for tenant context — it returns 401 which clears the frontend token."
|
|
565
|
-
exit 1
|
|
566
|
-
fi
|
|
567
|
-
fi
|
|
568
|
-
|
|
569
|
-
# POST-CHECK: Services must NOT use UnauthorizedAccessException for tenant context (causes token clearing)
|
|
570
|
-
if [ -n "$SERVICE_FILES" ]; then
|
|
571
|
-
BAD_UNAUTH=$(grep -Pn 'UnauthorizedAccessException.*[Tt]enant' $SERVICE_FILES 2>/dev/null)
|
|
572
|
-
if [ -n "$BAD_UNAUTH" ]; then
|
|
573
|
-
echo "BLOCKING: Services use UnauthorizedAccessException for tenant context — causes 401 which clears the frontend token"
|
|
574
|
-
echo "$BAD_UNAUTH"
|
|
575
|
-
echo ""
|
|
576
|
-
echo "Fix: Replace with:"
|
|
577
|
-
echo " var tenantId = _currentTenant.TenantId"
|
|
578
|
-
echo " ?? throw new TenantContextRequiredException();"
|
|
579
|
-
echo ""
|
|
580
|
-
echo "TenantContextRequiredException returns 400 Bad Request (does not clear token)."
|
|
581
|
-
echo "UnauthorizedAccessException returns 401 Unauthorized (clears token + redirects to login)."
|
|
582
|
-
exit 1
|
|
583
|
-
fi
|
|
584
|
-
fi
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
### POST-CHECK 22: Cross-tenant entities must use Guid? TenantId
|
|
571
|
+
### POST-CHECK 15: Cross-tenant entities must use Guid? TenantId
|
|
588
572
|
|
|
589
573
|
```bash
|
|
590
574
|
for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev/null); do
|
|
@@ -595,10 +579,10 @@ for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev
|
|
|
595
579
|
fi
|
|
596
580
|
fi
|
|
597
581
|
done
|
|
598
|
-
echo "POST-CHECK
|
|
582
|
+
echo "POST-CHECK 15: OK"
|
|
599
583
|
```
|
|
600
584
|
|
|
601
|
-
### POST-CHECK
|
|
585
|
+
### POST-CHECK 16: Scoped entities must have EntityScope property
|
|
602
586
|
|
|
603
587
|
```bash
|
|
604
588
|
for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev/null); do
|
|
@@ -609,10 +593,10 @@ for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev
|
|
|
609
593
|
fi
|
|
610
594
|
fi
|
|
611
595
|
done
|
|
612
|
-
echo "POST-CHECK
|
|
596
|
+
echo "POST-CHECK 16: OK"
|
|
613
597
|
```
|
|
614
598
|
|
|
615
|
-
### POST-CHECK
|
|
599
|
+
### POST-CHECK 17: Permissions.cs static constants must exist (BLOCKING)
|
|
616
600
|
|
|
617
601
|
```bash
|
|
618
602
|
# Every module with controllers MUST have a Permissions.cs with static constants
|
|
@@ -630,7 +614,7 @@ if [ -n "$CTRL_FILES" ]; then
|
|
|
630
614
|
fi
|
|
631
615
|
```
|
|
632
616
|
|
|
633
|
-
### POST-CHECK
|
|
617
|
+
### POST-CHECK 18: ApplicationRolesSeedData.cs must exist (BLOCKING)
|
|
634
618
|
|
|
635
619
|
```bash
|
|
636
620
|
# If any RolesSeedData exists, ApplicationRolesSeedData MUST also exist
|
|
@@ -668,14 +652,14 @@ if [ -n "$SECTION_SEED_FILES" ] && [ -n "$APP_TSX" ]; then
|
|
|
668
652
|
done
|
|
669
653
|
fi
|
|
670
654
|
|
|
671
|
-
#
|
|
655
|
+
# All controllers with [NavRoute] must have matching [RequirePermission]
|
|
672
656
|
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
673
657
|
if [ -n "$CTRL_FILES" ]; then
|
|
674
658
|
for f in $CTRL_FILES; do
|
|
675
|
-
# Match NavRoute with
|
|
676
|
-
SECTION_NAVROUTE=$(grep -oP 'NavRoute\("[a-z
|
|
659
|
+
# Match NavRoute with 2+ dot-separated segments (minimum: app.module)
|
|
660
|
+
SECTION_NAVROUTE=$(grep -oP 'NavRoute\("[a-z-]+\.[a-z-]+' "$f" 2>/dev/null)
|
|
677
661
|
if [ -n "$SECTION_NAVROUTE" ] && ! grep -q "\[RequirePermission" "$f"; then
|
|
678
|
-
echo "BLOCKING:
|
|
662
|
+
echo "BLOCKING: Controller has [NavRoute] but no [RequirePermission]: $f"
|
|
679
663
|
echo "Fix: Add [RequirePermission(Permissions.{Section}.{Action})] on each endpoint"
|
|
680
664
|
exit 1
|
|
681
665
|
fi
|
|
@@ -690,13 +674,13 @@ if [ -n "$SECTION_SEED_FILES" ] && [ -n "$PERM_FILE" ]; then
|
|
|
690
674
|
PASCAL=$(echo "$CODE" | sed 's/^./\U&/')
|
|
691
675
|
if ! grep -q "static class $PASCAL" "$PERM_FILE" 2>/dev/null; then
|
|
692
676
|
echo "WARNING: Section '$CODE' in seed data has no matching Permissions.$PASCAL static class"
|
|
693
|
-
echo "Fix: Add section-level permissions via MCP generate_permissions with
|
|
677
|
+
echo "Fix: Add section-level permissions via MCP generate_permissions with 3-segment navRoute (app.module.section)"
|
|
694
678
|
fi
|
|
695
679
|
done
|
|
696
680
|
fi
|
|
697
681
|
```
|
|
698
682
|
|
|
699
|
-
### POST-CHECK
|
|
683
|
+
### POST-CHECK 19: FORBIDDEN route patterns — /list and /detail/:id (BLOCKING)
|
|
700
684
|
|
|
701
685
|
```bash
|
|
702
686
|
# 1. Check seed data for FORBIDDEN suffixes
|
|
@@ -727,7 +711,7 @@ fi
|
|
|
727
711
|
echo "OK: No forbidden /list or /detail route patterns found"
|
|
728
712
|
```
|
|
729
713
|
|
|
730
|
-
### POST-CHECK
|
|
714
|
+
### POST-CHECK 20: Permission path segment count (WARNING)
|
|
731
715
|
|
|
732
716
|
```bash
|
|
733
717
|
PERM_FILES=$(find src/ -path "*/Seeding/Data/*" -name "PermissionsSeedData.cs" 2>/dev/null)
|
|
@@ -749,7 +733,7 @@ if [ -n "$PERM_FILES" ]; then
|
|
|
749
733
|
fi
|
|
750
734
|
```
|
|
751
735
|
|
|
752
|
-
### POST-CHECK
|
|
736
|
+
### POST-CHECK 21: IClientSeedDataProvider must have 4 methods + DI registration (BLOCKING)
|
|
753
737
|
|
|
754
738
|
```bash
|
|
755
739
|
PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
|
|
@@ -779,7 +763,7 @@ if [ -n "$PROVIDER" ]; then
|
|
|
779
763
|
fi
|
|
780
764
|
```
|
|
781
765
|
|
|
782
|
-
### POST-CHECK
|
|
766
|
+
### POST-CHECK 22: i18n must use separate JSON files per language (not embedded in index.ts)
|
|
783
767
|
|
|
784
768
|
```bash
|
|
785
769
|
# Translations MUST be in src/i18n/locales/{lang}/{module}.json, NOT embedded in a single .ts file
|
|
@@ -829,7 +813,7 @@ if [ -n "$TSX_FILES" ]; then
|
|
|
829
813
|
fi
|
|
830
814
|
```
|
|
831
815
|
|
|
832
|
-
### POST-CHECK
|
|
816
|
+
### POST-CHECK 23: Pages must use useTranslation hook (no hardcoded user-facing strings)
|
|
833
817
|
|
|
834
818
|
```bash
|
|
835
819
|
# Verify that page components use i18n — detect hardcoded strings in JSX
|
|
@@ -858,7 +842,7 @@ if [ -n "$PAGE_FILES" ]; then
|
|
|
858
842
|
fi
|
|
859
843
|
```
|
|
860
844
|
|
|
861
|
-
### POST-CHECK
|
|
845
|
+
### POST-CHECK 24: List/Detail pages must include DocToggleButton (documentation panel)
|
|
862
846
|
|
|
863
847
|
```bash
|
|
864
848
|
# Every list and detail page MUST have DocToggleButton for inline documentation access
|
|
@@ -881,7 +865,7 @@ if [ -n "$LIST_PAGES" ]; then
|
|
|
881
865
|
fi
|
|
882
866
|
```
|
|
883
867
|
|
|
884
|
-
### POST-CHECK
|
|
868
|
+
### POST-CHECK 25: Module documentation must be generated (doc-data.ts)
|
|
885
869
|
|
|
886
870
|
```bash
|
|
887
871
|
# After frontend pages exist, /documentation should have been called
|
|
@@ -894,7 +878,7 @@ if [ -n "$TSX_PAGES" ] && [ -z "$DOC_DATA" ]; then
|
|
|
894
878
|
fi
|
|
895
879
|
```
|
|
896
880
|
|
|
897
|
-
### POST-CHECK
|
|
881
|
+
### POST-CHECK 26: Pagination type must be PaginatedResult<T> — no aliases (BLOCKING)
|
|
898
882
|
|
|
899
883
|
```bash
|
|
900
884
|
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
@@ -912,7 +896,9 @@ if [ -n "$(echo $ALL_FILES | tr -d ' ')" ]; then
|
|
|
912
896
|
fi
|
|
913
897
|
```
|
|
914
898
|
|
|
915
|
-
|
|
899
|
+
## Infrastructure — Migration & Build
|
|
900
|
+
|
|
901
|
+
### POST-CHECK 27: Code generation — ICodeGenerator must be registered for auto-generated entities (BLOCKING)
|
|
916
902
|
|
|
917
903
|
```bash
|
|
918
904
|
# If feature.json has entities with codePattern.strategy != "manual",
|
|
@@ -943,7 +929,7 @@ except: pass
|
|
|
943
929
|
fi
|
|
944
930
|
```
|
|
945
931
|
|
|
946
|
-
### POST-CHECK
|
|
932
|
+
### POST-CHECK 28: Code regex must support hyphens (BLOCKING)
|
|
947
933
|
|
|
948
934
|
```bash
|
|
949
935
|
VALIDATOR_FILES=$(find src/ -path "*/Validators/*" -name "*Validator.cs" 2>/dev/null)
|
|
@@ -958,7 +944,7 @@ if [ -n "$VALIDATOR_FILES" ]; then
|
|
|
958
944
|
fi
|
|
959
945
|
```
|
|
960
946
|
|
|
961
|
-
### POST-CHECK
|
|
947
|
+
### POST-CHECK 29: CreateDto must NOT have Code field when service uses ICodeGenerator (WARNING)
|
|
962
948
|
|
|
963
949
|
```bash
|
|
964
950
|
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
@@ -976,7 +962,7 @@ if [ -n "$SERVICE_FILES" ]; then
|
|
|
976
962
|
fi
|
|
977
963
|
```
|
|
978
964
|
|
|
979
|
-
### POST-CHECK
|
|
965
|
+
### POST-CHECK 30: Translation seed data must have idempotency guard (BLOCKING)
|
|
980
966
|
|
|
981
967
|
```bash
|
|
982
968
|
PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
|
|
@@ -999,16 +985,16 @@ if [ -n "$PROVIDER" ]; then
|
|
|
999
985
|
fi
|
|
1000
986
|
```
|
|
1001
987
|
|
|
1002
|
-
### POST-CHECK
|
|
988
|
+
### POST-CHECK 31: Resource seed data must use actual section IDs from DB (BLOCKING)
|
|
1003
989
|
|
|
1004
990
|
```bash
|
|
1005
991
|
PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
|
|
1006
992
|
if [ -n "$PROVIDER" ]; then
|
|
1007
|
-
# Check if NavigationResource.Create uses secEntry.Id or resEntry.SectionId (
|
|
993
|
+
# Check if NavigationResource.Create uses secEntry.Id or resEntry.SectionId (seed-time GUIDs)
|
|
1008
994
|
# instead of actualSection.Id (real DB ID). This causes FK_nav_Resources_nav_Sections_SectionId violation.
|
|
1009
995
|
if grep -Pn 'NavigationResource\.Create\(' "$PROVIDER" | grep -q 'resEntry\.SectionId\|secEntry\.Id'; then
|
|
1010
|
-
echo "BLOCKING: Resource seed data uses
|
|
1011
|
-
echo "NavigationSection.Create() generates its own ID —
|
|
996
|
+
echo "BLOCKING: Resource seed data uses seed-time GUID as SectionId in $PROVIDER"
|
|
997
|
+
echo "NavigationSection.Create() generates its own ID — seed-time GUIDs do NOT exist in nav_Sections."
|
|
1012
998
|
echo "Fix: Query actual section from DB before creating resources:"
|
|
1013
999
|
echo " var actualSection = await context.NavigationSections"
|
|
1014
1000
|
echo " .FirstAsync(s => s.Code == secEntry.Code && s.ModuleId == modEntity.Id, ct);"
|
|
@@ -1018,28 +1004,7 @@ if [ -n "$PROVIDER" ]; then
|
|
|
1018
1004
|
fi
|
|
1019
1005
|
```
|
|
1020
1006
|
|
|
1021
|
-
### POST-CHECK
|
|
1022
|
-
|
|
1023
|
-
```bash
|
|
1024
|
-
# [NavRoute] REPLACES [Route] — it resolves HTTP routes from the navigation DB at startup.
|
|
1025
|
-
# Having both is redundant and may cause route conflicts.
|
|
1026
|
-
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
1027
|
-
if [ -n "$CTRL_FILES" ]; then
|
|
1028
|
-
for f in $CTRL_FILES; do
|
|
1029
|
-
HAS_NAVROUTE=$(grep -P '\[NavRoute\(' "$f" 2>/dev/null)
|
|
1030
|
-
HAS_ROUTE=$(grep -P '\[Route\(' "$f" 2>/dev/null)
|
|
1031
|
-
if [ -n "$HAS_NAVROUTE" ] && [ -n "$HAS_ROUTE" ]; then
|
|
1032
|
-
echo "BLOCKING: Controller has both [Route] and [NavRoute] — [NavRoute] replaces [Route]: $f"
|
|
1033
|
-
echo " Found [NavRoute]: $HAS_NAVROUTE"
|
|
1034
|
-
echo " Found [Route]: $HAS_ROUTE"
|
|
1035
|
-
echo "Fix: Remove the [Route(\"api/...\")] attribute. [NavRoute] resolves routes from navigation DB at startup."
|
|
1036
|
-
exit 1
|
|
1037
|
-
fi
|
|
1038
|
-
done
|
|
1039
|
-
fi
|
|
1040
|
-
```
|
|
1041
|
-
|
|
1042
|
-
### POST-CHECK 40: NavRoute segments must use kebab-case for multi-word codes (BLOCKING)
|
|
1007
|
+
### POST-CHECK 32: NavRoute segments must use kebab-case for multi-word codes (BLOCKING)
|
|
1043
1008
|
|
|
1044
1009
|
```bash
|
|
1045
1010
|
# NavRoute segments are navigation entity Codes joined by dots.
|
|
@@ -1078,7 +1043,7 @@ if [ -n "$SEED_FILES" ]; then
|
|
|
1078
1043
|
fi
|
|
1079
1044
|
```
|
|
1080
1045
|
|
|
1081
|
-
### POST-CHECK
|
|
1046
|
+
### POST-CHECK 33: Permission codes must use kebab-case matching NavRoute codes (BLOCKING)
|
|
1082
1047
|
|
|
1083
1048
|
```bash
|
|
1084
1049
|
# Permission codes in [RequirePermission] and Permissions.cs MUST use kebab-case for multi-word segments.
|
|
@@ -1143,7 +1108,7 @@ if [ -n "$SEED_PERM_FILES" ]; then
|
|
|
1143
1108
|
fi
|
|
1144
1109
|
```
|
|
1145
1110
|
|
|
1146
|
-
### POST-CHECK
|
|
1111
|
+
### POST-CHECK 34: Frontend navigate() calls must have matching route definitions (BLOCKING)
|
|
1147
1112
|
|
|
1148
1113
|
```bash
|
|
1149
1114
|
# Detect dead links: navigate() calls to paths that don't have corresponding page components.
|
|
@@ -1182,7 +1147,7 @@ if [ -n "$PAGE_FILES" ]; then
|
|
|
1182
1147
|
fi
|
|
1183
1148
|
```
|
|
1184
1149
|
|
|
1185
|
-
### POST-CHECK
|
|
1150
|
+
### POST-CHECK 35: Detail page tabs must NOT navigate() — content switches locally (BLOCKING)
|
|
1186
1151
|
|
|
1187
1152
|
```bash
|
|
1188
1153
|
# Tabs on detail pages MUST use local state (setActiveTab) — NEVER navigate() to other pages.
|
|
@@ -1223,7 +1188,7 @@ if [ -n "$DETAIL_PAGES" ]; then
|
|
|
1223
1188
|
fi
|
|
1224
1189
|
```
|
|
1225
1190
|
|
|
1226
|
-
### POST-CHECK
|
|
1191
|
+
### POST-CHECK 36: Migration ModelSnapshot must contain ALL entities registered in DbContext (BLOCKING)
|
|
1227
1192
|
|
|
1228
1193
|
```bash
|
|
1229
1194
|
# Root cause (test-apex-007): 7 entities registered in DbContext but migration only covered 3.
|
|
@@ -1258,7 +1223,7 @@ if [ -n "$SNAPSHOT" ] && [ -n "$DBCONTEXT" ]; then
|
|
|
1258
1223
|
fi
|
|
1259
1224
|
```
|
|
1260
1225
|
|
|
1261
|
-
### POST-CHECK
|
|
1226
|
+
### POST-CHECK 37: I18n namespace files must be registered in i18n config (BLOCKING)
|
|
1262
1227
|
|
|
1263
1228
|
```bash
|
|
1264
1229
|
# Root cause (test-apex-007): i18n JSON files existed in src/i18n/locales/ but were never
|
|
@@ -1287,7 +1252,7 @@ if [ -n "$I18N_CONFIG" ]; then
|
|
|
1287
1252
|
fi
|
|
1288
1253
|
```
|
|
1289
1254
|
|
|
1290
|
-
### POST-CHECK
|
|
1255
|
+
### POST-CHECK 38: FluentValidation validators must be registered via DI (BLOCKING)
|
|
1291
1256
|
|
|
1292
1257
|
```bash
|
|
1293
1258
|
# Root cause (test-apex-007): Validators existed but were never registered in DI.
|
|
@@ -1322,7 +1287,7 @@ if [ -n "$VALIDATOR_FILES" ]; then
|
|
|
1322
1287
|
fi
|
|
1323
1288
|
```
|
|
1324
1289
|
|
|
1325
|
-
### POST-CHECK
|
|
1290
|
+
### POST-CHECK 39: Date/date properties in DTOs must use DateOnly, not string (BLOCKING)
|
|
1326
1291
|
|
|
1327
1292
|
```bash
|
|
1328
1293
|
# Root cause (test-apex-007): WorkLog DTO had Date property typed as string instead of DateOnly.
|
|
@@ -1347,45 +1312,10 @@ if [ -n "$DTO_FILES" ]; then
|
|
|
1347
1312
|
fi
|
|
1348
1313
|
```
|
|
1349
1314
|
|
|
1350
|
-
### POST-CHECK
|
|
1315
|
+
### POST-CHECK 40: Every module with entities must have a migration covering them (BLOCKING)
|
|
1351
1316
|
|
|
1352
1317
|
```bash
|
|
1353
|
-
#
|
|
1354
|
-
# instead of [NavRoute("human-resources.employees")]. This causes route mismatch with
|
|
1355
|
-
# seed data and permission codes, resulting in 404s at runtime.
|
|
1356
|
-
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
1357
|
-
if [ -n "$CTRL_FILES" ]; then
|
|
1358
|
-
FAIL=false
|
|
1359
|
-
for f in $CTRL_FILES; do
|
|
1360
|
-
NAVROUTE_VALS=$(grep -oP 'NavRoute\("([^"]+)"' "$f" 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"')
|
|
1361
|
-
for NR in $NAVROUTE_VALS; do
|
|
1362
|
-
# Check each segment for concatenated multi-word without hyphens
|
|
1363
|
-
SEGMENTS=$(echo "$NR" | tr '.' '\n')
|
|
1364
|
-
for SEG in $SEGMENTS; do
|
|
1365
|
-
# Detect segments that look like concatenated words (lowercase, 8+ chars, no hyphens)
|
|
1366
|
-
# Use a simpler heuristic: lowercase-only segment with known multi-word patterns
|
|
1367
|
-
if echo "$SEG" | grep -qP '^[a-z]{8,}$'; then
|
|
1368
|
-
# Additional check: does it contain a known multi-word pattern?
|
|
1369
|
-
if echo "$SEG" | grep -qP '(human|project|leave|client|support|email|time|work|resource)'; then
|
|
1370
|
-
echo "BLOCKING: NavRoute segment '$SEG' in $f appears to be concatenated multi-word without hyphens"
|
|
1371
|
-
echo " Full NavRoute: $NR"
|
|
1372
|
-
echo " Fix: Use kebab-case: e.g., 'humanresources' → 'human-resources', 'projectmanagement' → 'project-management'"
|
|
1373
|
-
FAIL=true
|
|
1374
|
-
fi
|
|
1375
|
-
fi
|
|
1376
|
-
done
|
|
1377
|
-
done
|
|
1378
|
-
done
|
|
1379
|
-
if [ "$FAIL" = true ]; then
|
|
1380
|
-
exit 1
|
|
1381
|
-
fi
|
|
1382
|
-
fi
|
|
1383
|
-
```
|
|
1384
|
-
|
|
1385
|
-
### POST-CHECK 49: Every module with entities must have a migration covering them (BLOCKING)
|
|
1386
|
-
|
|
1387
|
-
```bash
|
|
1388
|
-
# Complementary to POST-CHECK 44 — checks from the entity side.
|
|
1318
|
+
# Complementary to POST-CHECK 36 — checks from the entity side.
|
|
1389
1319
|
# Finds entity .cs files in Domain/ and verifies they appear in at least one migration file.
|
|
1390
1320
|
ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null | grep -v test)
|
|
1391
1321
|
MIGRATION_DIR=$(find src/ -path "*/Migrations" -type d 2>/dev/null | head -1)
|
|
@@ -1415,7 +1345,7 @@ if [ -n "$ENTITY_FILES" ] && [ -n "$MIGRATION_DIR" ]; then
|
|
|
1415
1345
|
fi
|
|
1416
1346
|
```
|
|
1417
1347
|
|
|
1418
|
-
### POST-CHECK
|
|
1348
|
+
### POST-CHECK 41: Controllers must NOT have both [Route] and [NavRoute] attributes (BLOCKING)
|
|
1419
1349
|
|
|
1420
1350
|
```bash
|
|
1421
1351
|
# Root cause (test-apex-007): All 7 controllers had BOTH [Route("api/...")] and [NavRoute("...")].
|
|
@@ -1448,7 +1378,7 @@ if [ -n "$CTRL_FILES" ]; then
|
|
|
1448
1378
|
fi
|
|
1449
1379
|
```
|
|
1450
1380
|
|
|
1451
|
-
### POST-CHECK
|
|
1381
|
+
### POST-CHECK 42: RolesSeedData must map standard role-permission matrix (BLOCKING)
|
|
1452
1382
|
|
|
1453
1383
|
```bash
|
|
1454
1384
|
# SmartStack standard role-permission matrix:
|
|
@@ -1498,7 +1428,7 @@ if [ -n "$ROLE_SEED_FILES" ]; then
|
|
|
1498
1428
|
fi
|
|
1499
1429
|
```
|
|
1500
1430
|
|
|
1501
|
-
### POST-CHECK
|
|
1431
|
+
### POST-CHECK 43: PermissionAction enum must use valid typed values only (BLOCKING)
|
|
1502
1432
|
|
|
1503
1433
|
```bash
|
|
1504
1434
|
# Valid PermissionAction enum values: Access(0), Read(1), Create(2), Update(3), Delete(4),
|
|
@@ -1533,7 +1463,7 @@ if [ -n "$SEED_FILES" ]; then
|
|
|
1533
1463
|
fi
|
|
1534
1464
|
```
|
|
1535
1465
|
|
|
1536
|
-
### POST-CHECK
|
|
1466
|
+
### POST-CHECK 44: Navigation translation completeness — 4 languages per level (BLOCKING)
|
|
1537
1467
|
|
|
1538
1468
|
```bash
|
|
1539
1469
|
# Every navigation seed data file must provide translations for ALL 4 languages (fr, en, it, de).
|
|
@@ -1581,4 +1511,138 @@ if [ -n "$NAV_SEED_FILES" ]; then
|
|
|
1581
1511
|
fi
|
|
1582
1512
|
```
|
|
1583
1513
|
|
|
1584
|
-
|
|
1514
|
+
---
|
|
1515
|
+
|
|
1516
|
+
## Architecture — Clean Architecture Layer Isolation
|
|
1517
|
+
|
|
1518
|
+
### POST-CHECK A1: Domain must not import other layers (BLOCKING)
|
|
1519
|
+
|
|
1520
|
+
```bash
|
|
1521
|
+
DOMAIN_FILES=$(find src/ -path "*/Domain/*" -name "*.cs" 2>/dev/null)
|
|
1522
|
+
if [ -n "$DOMAIN_FILES" ]; then
|
|
1523
|
+
BAD_IMPORTS=$(grep -Pn 'using\s+[\w.]*\.(Application|Infrastructure|Api)[\w.]*;' $DOMAIN_FILES 2>/dev/null)
|
|
1524
|
+
if [ -n "$BAD_IMPORTS" ]; then
|
|
1525
|
+
echo "BLOCKING: Domain layer imports Application/Infrastructure/Api — violates Clean Architecture"
|
|
1526
|
+
echo "Domain is the core, it must not depend on any other layer"
|
|
1527
|
+
echo "$BAD_IMPORTS"
|
|
1528
|
+
echo "Fix: Move shared types to Domain or remove the dependency"
|
|
1529
|
+
exit 1
|
|
1530
|
+
fi
|
|
1531
|
+
fi
|
|
1532
|
+
```
|
|
1533
|
+
|
|
1534
|
+
### POST-CHECK A2: Application must not import Infrastructure or Api (BLOCKING)
|
|
1535
|
+
|
|
1536
|
+
```bash
|
|
1537
|
+
APP_FILES=$(find src/ -path "*/Application/*" -name "*.cs" 2>/dev/null)
|
|
1538
|
+
if [ -n "$APP_FILES" ]; then
|
|
1539
|
+
BAD_IMPORTS=$(grep -Pn 'using\s+[\w.]*\.(Infrastructure|Api)[\w.]*;' $APP_FILES 2>/dev/null)
|
|
1540
|
+
if [ -n "$BAD_IMPORTS" ]; then
|
|
1541
|
+
echo "BLOCKING: Application layer imports Infrastructure/Api — violates Clean Architecture"
|
|
1542
|
+
echo "Application defines interfaces, Infrastructure implements them"
|
|
1543
|
+
echo "$BAD_IMPORTS"
|
|
1544
|
+
echo "Fix: Define an interface in Application and implement it in Infrastructure"
|
|
1545
|
+
exit 1
|
|
1546
|
+
fi
|
|
1547
|
+
fi
|
|
1548
|
+
```
|
|
1549
|
+
|
|
1550
|
+
### POST-CHECK A3: Controllers must not inject DbContext (BLOCKING)
|
|
1551
|
+
|
|
1552
|
+
```bash
|
|
1553
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
1554
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
1555
|
+
BAD_DBCONTEXT=$(grep -Pn 'private\s+readonly\s+\w*DbContext|DbContext\s+\w+[,)]' $CTRL_FILES 2>/dev/null)
|
|
1556
|
+
if [ -n "$BAD_DBCONTEXT" ]; then
|
|
1557
|
+
echo "BLOCKING: Controller injects DbContext directly — violates Clean Architecture"
|
|
1558
|
+
echo "Controllers must use Application services, not access the database directly"
|
|
1559
|
+
echo "$BAD_DBCONTEXT"
|
|
1560
|
+
echo "Fix: Create an Application service with the required business logic and inject it instead"
|
|
1561
|
+
exit 1
|
|
1562
|
+
fi
|
|
1563
|
+
fi
|
|
1564
|
+
```
|
|
1565
|
+
|
|
1566
|
+
### POST-CHECK A4: API must return DTOs, not Domain entities (WARNING)
|
|
1567
|
+
|
|
1568
|
+
```bash
|
|
1569
|
+
# Scan controller return types for Domain entity names without Dto/Response/ViewModel suffix
|
|
1570
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
1571
|
+
ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null)
|
|
1572
|
+
if [ -n "$CTRL_FILES" ] && [ -n "$ENTITY_FILES" ]; then
|
|
1573
|
+
ENTITY_NAMES=$(grep -ohP 'public\s+class\s+(\w+)\s*:' $ENTITY_FILES 2>/dev/null | grep -oP '\w+(?=\s*:)' | grep -v '^public$' | sort -u)
|
|
1574
|
+
for ENTITY in $ENTITY_NAMES; do
|
|
1575
|
+
BAD_RETURN=$(grep -Pn "ActionResult<$ENTITY>|ActionResult<IEnumerable<$ENTITY>>|ActionResult<List<$ENTITY>>" $CTRL_FILES 2>/dev/null)
|
|
1576
|
+
if [ -n "$BAD_RETURN" ]; then
|
|
1577
|
+
echo "WARNING: Controller returns Domain entity '$ENTITY' instead of a DTO"
|
|
1578
|
+
echo "$BAD_RETURN"
|
|
1579
|
+
echo "Fix: Return ${ENTITY}ResponseDto instead of $ENTITY"
|
|
1580
|
+
fi
|
|
1581
|
+
done
|
|
1582
|
+
fi
|
|
1583
|
+
```
|
|
1584
|
+
|
|
1585
|
+
### POST-CHECK A5: Service interfaces in Application, implementations in Infrastructure (WARNING)
|
|
1586
|
+
|
|
1587
|
+
```bash
|
|
1588
|
+
# Check for service implementations (non-interface classes) in Application or Api layers
|
|
1589
|
+
APP_SERVICES=$(find src/ -path "*/Application/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
1590
|
+
if [ -n "$APP_SERVICES" ]; then
|
|
1591
|
+
for f in $APP_SERVICES; do
|
|
1592
|
+
if grep -q 'public class.*Service' "$f" 2>/dev/null; then
|
|
1593
|
+
echo "WARNING: Service implementation found in Application layer: $f"
|
|
1594
|
+
echo "Fix: Move implementation to Infrastructure/Services/. Application should only contain interfaces."
|
|
1595
|
+
fi
|
|
1596
|
+
done
|
|
1597
|
+
fi
|
|
1598
|
+
|
|
1599
|
+
# Check for service interfaces (I*Service) in Domain or Api layers
|
|
1600
|
+
DOMAIN_INTERFACES=$(find src/ -path "*/Domain/*" -name "I*Service.cs" 2>/dev/null)
|
|
1601
|
+
API_INTERFACES=$(find src/ -path "*/Api/*" -name "I*Service.cs" 2>/dev/null)
|
|
1602
|
+
for f in $DOMAIN_INTERFACES $API_INTERFACES; do
|
|
1603
|
+
if [ -n "$f" ] && grep -q 'public interface.*Service' "$f" 2>/dev/null; then
|
|
1604
|
+
echo "WARNING: Service interface found outside Application layer: $f"
|
|
1605
|
+
echo "Fix: Move to Application/Interfaces/"
|
|
1606
|
+
fi
|
|
1607
|
+
done
|
|
1608
|
+
```
|
|
1609
|
+
|
|
1610
|
+
### POST-CHECK A6: No EF Core attributes in Domain entities (BLOCKING)
|
|
1611
|
+
|
|
1612
|
+
```bash
|
|
1613
|
+
DOMAIN_FILES=$(find src/ -path "*/Domain/*" -name "*.cs" 2>/dev/null)
|
|
1614
|
+
if [ -n "$DOMAIN_FILES" ]; then
|
|
1615
|
+
BAD_EF=$(grep -Pn '\[Table\(|\[Column\(|\[Index\(|using\s+Microsoft\.EntityFrameworkCore' $DOMAIN_FILES 2>/dev/null)
|
|
1616
|
+
if [ -n "$BAD_EF" ]; then
|
|
1617
|
+
echo "BLOCKING: EF Core attributes or using directives found in Domain layer"
|
|
1618
|
+
echo "Domain entities must be persistence-ignorant — EF configuration belongs in Infrastructure"
|
|
1619
|
+
echo "$BAD_EF"
|
|
1620
|
+
echo "Fix: Move [Table], [Column], [Index] to IEntityTypeConfiguration<T> in Infrastructure/Persistence/Configurations/"
|
|
1621
|
+
exit 1
|
|
1622
|
+
fi
|
|
1623
|
+
fi
|
|
1624
|
+
```
|
|
1625
|
+
|
|
1626
|
+
### POST-CHECK A7: No direct repository usage in controllers (WARNING)
|
|
1627
|
+
|
|
1628
|
+
```bash
|
|
1629
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
1630
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
1631
|
+
BAD_REPO=$(grep -Pn 'IRepository<|IGenericRepository<|private\s+readonly\s+IRepository|private\s+readonly\s+IGenericRepository' $CTRL_FILES 2>/dev/null)
|
|
1632
|
+
if [ -n "$BAD_REPO" ]; then
|
|
1633
|
+
echo "WARNING: Controller injects repository directly — should use Application services"
|
|
1634
|
+
echo "$BAD_REPO"
|
|
1635
|
+
echo "Fix: Controllers should depend on Application services (I*Service), not repositories"
|
|
1636
|
+
fi
|
|
1637
|
+
fi
|
|
1638
|
+
```
|
|
1639
|
+
|
|
1640
|
+
---
|
|
1641
|
+
|
|
1642
|
+
## Summary
|
|
1643
|
+
|
|
1644
|
+
After running all checks above, report the total count:
|
|
1645
|
+
- **N BLOCKING** issues must be fixed before committing
|
|
1646
|
+
- **M WARNING** issues should be reviewed but are non-blocking
|
|
1647
|
+
|
|
1648
|
+
**If ANY POST-CHECK fails → fix in step-06, re-validate.**
|