@atlashub/smartstack-cli 3.21.0 → 3.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/index.js +17 -5
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +155 -162
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/skills/apex/SKILL.md +21 -0
  7. package/templates/skills/apex/references/smartstack-api.md +481 -0
  8. package/templates/skills/apex/references/smartstack-layers.md +85 -15
  9. package/templates/skills/apex/steps/step-00-init.md +27 -14
  10. package/templates/skills/apex/steps/step-01-analyze.md +18 -0
  11. package/templates/skills/apex/steps/step-03-execute.md +8 -6
  12. package/templates/skills/apex/steps/step-04-validate.md +92 -0
  13. package/templates/skills/apex/steps/step-07-tests.md +29 -5
  14. package/templates/skills/application/references/application-roles-template.md +2 -2
  15. package/templates/skills/application/steps/step-05-frontend.md +40 -35
  16. package/templates/skills/application/templates-frontend.md +64 -36
  17. package/templates/skills/business-analyse/html/ba-interactive.html +80 -6
  18. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +38 -6
  19. package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
  20. package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
  21. package/templates/skills/business-analyse/references/deploy-data-build.md +5 -3
  22. package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
  23. package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
  24. package/templates/skills/business-analyse/references/validate-incremental-html.md +26 -4
  25. package/templates/skills/business-analyse/references/validation-checklist.md +31 -11
  26. package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -0
  27. package/templates/skills/business-analyse/steps/step-03b-ui.md +59 -0
  28. package/templates/skills/business-analyse/steps/step-03c-compile.md +114 -0
  29. package/templates/skills/business-analyse/steps/step-03d-validate.md +144 -22
  30. package/templates/skills/business-analyse/steps/step-05a-handoff.md +114 -2
  31. package/templates/skills/business-analyse/steps/step-05b-deploy.md +28 -0
  32. package/templates/skills/ralph-loop/references/category-rules.md +5 -2
  33. package/templates/skills/ralph-loop/references/compact-loop.md +52 -1
  34. package/templates/skills/ralph-loop/references/core-seed-data.md +232 -21
  35. package/templates/skills/ralph-loop/steps/step-01-task.md +36 -4
  36. package/templates/skills/ralph-loop/steps/step-02-execute.md +81 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "3.21.0",
3
+ "version": "3.23.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -91,6 +91,7 @@ Execute incremental SmartStack development using the APEX methodology. This skil
91
91
 
92
92
  | File | Purpose | Loaded by |
93
93
  |------|---------|-----------|
94
+ | `references/smartstack-api.md` | BaseEntity, interfaces, entity/config/controller patterns | step-01, step-03 |
94
95
  | `references/smartstack-layers.md` | Layer execution rules, skill/MCP mapping, seed data | step-02, step-03 |
95
96
  | `references/agent-teams-protocol.md` | TeamCreate, coordination, shutdown protocol | step-01, step-03 |
96
97
  </reference_files>
@@ -108,6 +109,26 @@ Execute incremental SmartStack development using the APEX methodology. This skil
108
109
 
109
110
  </execution_rules>
110
111
 
112
+ <error_handling>
113
+
114
+ ## Error Handling Strategy
115
+
116
+ | Error | Action | Max Retries |
117
+ |-------|--------|-------------|
118
+ | `dotnet build` fails | Identify error, fix via skill/MCP, rebuild | 3 |
119
+ | `npm run typecheck` fails | Fix TypeScript error, retry | 3 |
120
+ | MCP unavailable | Degraded mode: use `smartstack-api.md` as sole reference, no MCP validation | — |
121
+ | File lock (MSB3021) | Auto-use `--output /tmp/{project}_build` for build verification | — |
122
+ | NuGet restore required | Run `dotnet restore` before first `dotnet build` | 1 |
123
+ | Migration fails | Rollback migration (`dotnet ef migrations remove`), fix entity/config, retry | 2 |
124
+ | MCP scaffold output wrong | Verify against `smartstack-api.md` patterns, fix manually | — |
125
+ | POST-CHECK fails | Return to step-03, fix the issue, re-validate | 2 |
126
+ | Stuck after max retries | AskUserQuestion with options: "Try alternative", "Skip", "Discuss" | — |
127
+
128
+ **Principle:** Always fix CODE, never bypass checks. If stuck, escalate to user.
129
+
130
+ </error_handling>
131
+
111
132
  <success_criteria>
112
133
  - SmartStack context detected (context/app/module)
113
134
  - Plan validated with skill/MCP mapped for each file
@@ -0,0 +1,481 @@
1
+ # SmartStack Domain API Reference
2
+
3
+ > **Source of truth:** `SmartStack.app/src/SmartStack.Domain/Common/`
4
+ > **Loaded by:** step-01 (analyze), step-03 (execute)
5
+
6
+ ---
7
+
8
+ ## BaseEntity
9
+
10
+ ```csharp
11
+ namespace SmartStack.Domain.Common;
12
+
13
+ public abstract class BaseEntity
14
+ {
15
+ public Guid Id { get; set; }
16
+ public DateTime CreatedAt { get; set; }
17
+ public DateTime? UpdatedAt { get; set; }
18
+ }
19
+ ```
20
+
21
+ **ONLY 3 properties.** No Code, no IsDeleted, no RowVersion, no SoftDelete, no CreatedBy/UpdatedBy.
22
+
23
+ ---
24
+
25
+ ## Interfaces
26
+
27
+ ### ITenantEntity (mandatory tenant isolation)
28
+
29
+ ```csharp
30
+ public interface ITenantEntity
31
+ {
32
+ Guid TenantId { get; }
33
+ }
34
+ ```
35
+
36
+ ### IAuditableEntity (audit trail)
37
+
38
+ ```csharp
39
+ public interface IAuditableEntity
40
+ {
41
+ string? CreatedBy { get; set; }
42
+ string? UpdatedBy { get; set; }
43
+ }
44
+ ```
45
+
46
+ ### IOptionalTenantEntity (nullable tenant)
47
+
48
+ ```csharp
49
+ public interface IOptionalTenantEntity
50
+ {
51
+ Guid? TenantId { get; }
52
+ }
53
+ ```
54
+
55
+ ### IScopedTenantEntity (tenant + scope visibility)
56
+
57
+ ```csharp
58
+ public interface IScopedTenantEntity : IOptionalTenantEntity
59
+ {
60
+ EntityScope Scope { get; }
61
+ }
62
+ ```
63
+
64
+ ### EntityScope enum
65
+
66
+ ```csharp
67
+ public enum EntityScope
68
+ {
69
+ Tenant = 0, // Visible only to specific tenant (TenantId required)
70
+ Shared = 1, // Visible to all tenants (TenantId null)
71
+ Platform = 2 // Visible only to platform admins (HasGlobalAccess)
72
+ }
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Entity Pattern (tenant-scoped, most common)
78
+
79
+ ```csharp
80
+ using SmartStack.Domain.Common;
81
+
82
+ namespace {ProjectName}.Domain.Entities.{Context}.{App}.{Module};
83
+
84
+ public class {Name} : BaseEntity, ITenantEntity, IAuditableEntity
85
+ {
86
+ // ITenantEntity
87
+ public Guid TenantId { get; private set; }
88
+
89
+ // IAuditableEntity
90
+ public string? CreatedBy { get; set; }
91
+ public string? UpdatedBy { get; set; }
92
+
93
+ // Business properties (add your own)
94
+ public string Code { get; private set; } = null!;
95
+ public string Name { get; private set; } = null!;
96
+ public string? Description { get; private set; }
97
+ public bool IsActive { get; private set; } = true;
98
+
99
+ private {Name}() { }
100
+
101
+ public static {Name} Create(Guid tenantId, string code, string name)
102
+ {
103
+ if (tenantId == Guid.Empty)
104
+ throw new ArgumentException("TenantId is required", nameof(tenantId));
105
+
106
+ return new {Name}
107
+ {
108
+ Id = Guid.NewGuid(),
109
+ TenantId = tenantId,
110
+ Code = code.ToLowerInvariant(),
111
+ Name = name,
112
+ CreatedAt = DateTime.UtcNow
113
+ };
114
+ }
115
+
116
+ public void Update(string name, string? description)
117
+ {
118
+ Name = name;
119
+ Description = description;
120
+ UpdatedAt = DateTime.UtcNow;
121
+ }
122
+ }
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Entity Pattern (platform-level, no tenant)
128
+
129
+ ```csharp
130
+ public class {Name} : BaseEntity, IAuditableEntity
131
+ {
132
+ public string? CreatedBy { get; set; }
133
+ public string? UpdatedBy { get; set; }
134
+
135
+ // Business properties
136
+ public string Code { get; private set; } = null!;
137
+ public string Name { get; private set; } = null!;
138
+
139
+ private {Name}() { }
140
+
141
+ public static {Name} Create(string code, string name)
142
+ {
143
+ return new {Name}
144
+ {
145
+ Id = Guid.NewGuid(),
146
+ Code = code.ToLowerInvariant(),
147
+ Name = name,
148
+ CreatedAt = DateTime.UtcNow
149
+ };
150
+ }
151
+ }
152
+ ```
153
+
154
+ ---
155
+
156
+ ## EF Configuration Pattern
157
+
158
+ ```csharp
159
+ using Microsoft.EntityFrameworkCore;
160
+ using Microsoft.EntityFrameworkCore.Metadata.Builders;
161
+
162
+ public class {Name}Configuration : IEntityTypeConfiguration<{Name}>
163
+ {
164
+ public void Configure(EntityTypeBuilder<{Name}> builder)
165
+ {
166
+ builder.ToTable("{prefix}{Name}s", "{schema}");
167
+
168
+ builder.HasKey(x => x.Id);
169
+
170
+ // Tenant (if ITenantEntity)
171
+ builder.Property(x => x.TenantId).IsRequired();
172
+ builder.HasIndex(x => x.TenantId)
173
+ .HasDatabaseName("IX_{prefix}{Name}s_TenantId");
174
+
175
+ // Business properties
176
+ builder.Property(x => x.Code).HasMaxLength(50).IsRequired();
177
+ builder.Property(x => x.Name).HasMaxLength(100).IsRequired();
178
+ builder.Property(x => x.Description).HasMaxLength(500);
179
+
180
+ // Audit (from IAuditableEntity)
181
+ builder.Property(x => x.CreatedBy).HasMaxLength(256);
182
+ builder.Property(x => x.UpdatedBy).HasMaxLength(256);
183
+
184
+ // Unique indexes
185
+ builder.HasIndex(x => new { x.TenantId, x.Code })
186
+ .IsUnique()
187
+ .HasDatabaseName("IX_{prefix}{Name}s_Tenant_Code");
188
+
189
+ // Relationships
190
+ // builder.HasMany(x => x.Children)
191
+ // .WithOne(x => x.Parent)
192
+ // .HasForeignKey(x => x.ParentId)
193
+ // .OnDelete(DeleteBehavior.Restrict);
194
+
195
+ // Seed data (if applicable)
196
+ // builder.HasData({Name}SeedData.GetSeedData());
197
+ }
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Service Pattern (tenant-scoped, MANDATORY)
204
+
205
+ > **CRITICAL:** ALL services MUST inject `ICurrentUser` and filter by `TenantId`. Missing TenantId = OWASP A01 vulnerability.
206
+
207
+ ```csharp
208
+ using Microsoft.EntityFrameworkCore;
209
+ using Microsoft.Extensions.Logging;
210
+ using SmartStack.Application.Common.Interfaces.Identity;
211
+ using SmartStack.Application.Common.Interfaces.Persistence;
212
+
213
+ namespace {ProjectName}.Infrastructure.Services.{Context}.{App}.{Module};
214
+
215
+ public class {Name}Service : I{Name}Service
216
+ {
217
+ private readonly IExtensionsDbContext _db;
218
+ private readonly ICurrentUser _currentUser;
219
+ private readonly ILogger<{Name}Service> _logger;
220
+
221
+ public {Name}Service(
222
+ IExtensionsDbContext db,
223
+ ICurrentUser currentUser,
224
+ ILogger<{Name}Service> logger)
225
+ {
226
+ _db = db;
227
+ _currentUser = currentUser;
228
+ _logger = logger;
229
+ }
230
+
231
+ public async Task<List<{Name}ResponseDto>> GetAllAsync(CancellationToken ct)
232
+ {
233
+ return await _db.{Name}s
234
+ .Where(x => x.TenantId == _currentUser.TenantId) // MANDATORY tenant filter
235
+ .AsNoTracking()
236
+ .Select(x => new {Name}ResponseDto(x.Id, x.Code, x.Name, x.CreatedAt))
237
+ .ToListAsync(ct);
238
+ }
239
+
240
+ public async Task<{Name}ResponseDto?> GetByIdAsync(Guid id, CancellationToken ct)
241
+ {
242
+ return await _db.{Name}s
243
+ .Where(x => x.Id == id && x.TenantId == _currentUser.TenantId) // MANDATORY
244
+ .AsNoTracking()
245
+ .Select(x => new {Name}ResponseDto(x.Id, x.Code, x.Name, x.CreatedAt))
246
+ .FirstOrDefaultAsync(ct);
247
+ }
248
+
249
+ public async Task<{Name}ResponseDto> CreateAsync(Create{Name}Dto dto, CancellationToken ct)
250
+ {
251
+ var entity = {Name}.Create(
252
+ tenantId: _currentUser.TenantId, // MANDATORY — never Guid.Empty
253
+ code: dto.Code,
254
+ name: dto.Name);
255
+
256
+ entity.CreatedBy = _currentUser.UserId?.ToString();
257
+
258
+ _db.{Name}s.Add(entity);
259
+ await _db.SaveChangesAsync(ct);
260
+
261
+ _logger.LogInformation("Created {Entity} {Id} for tenant {TenantId}",
262
+ nameof({Name}), entity.Id, _currentUser.TenantId);
263
+
264
+ return new {Name}ResponseDto(entity.Id, entity.Code, entity.Name, entity.CreatedAt);
265
+ }
266
+
267
+ public async Task DeleteAsync(Guid id, CancellationToken ct)
268
+ {
269
+ var entity = await _db.{Name}s
270
+ .FirstOrDefaultAsync(x => x.Id == id && x.TenantId == _currentUser.TenantId, ct)
271
+ ?? throw new KeyNotFoundException($"{Name} {id} not found");
272
+
273
+ _db.{Name}s.Remove(entity);
274
+ await _db.SaveChangesAsync(ct);
275
+ }
276
+ }
277
+ ```
278
+
279
+ **Key interfaces:**
280
+ - `ICurrentUser` (from `SmartStack.Application.Common.Interfaces.Identity`): provides `TenantId`, `UserId`, `Email`
281
+ - `IExtensionsDbContext` (for client extensions) or `ICoreDbContext` (for platform)
282
+
283
+ **FORBIDDEN in services:**
284
+ - `tenantId: Guid.Empty` — always use `_currentUser.TenantId`
285
+ - Queries WITHOUT `.Where(x => x.TenantId == _currentUser.TenantId)` — data leak
286
+ - Missing `ILogger<T>` — undiagnosable in production
287
+
288
+ ---
289
+
290
+ ## Controller Pattern (NavRoute)
291
+
292
+ ```csharp
293
+ using Microsoft.AspNetCore.Authorization;
294
+ using Microsoft.AspNetCore.Mvc;
295
+ using SmartStack.Api.Routing;
296
+ using SmartStack.Api.Authorization;
297
+
298
+ namespace {ProjectName}.Api.Controllers.{Context}.{App};
299
+
300
+ [ApiController]
301
+ [NavRoute("{context}.{app}.{module}")]
302
+ [Authorize]
303
+ public class {Name}Controller : ControllerBase
304
+ {
305
+ private readonly I{Name}Service _service;
306
+ private readonly ILogger<{Name}Controller> _logger;
307
+
308
+ public {Name}Controller(I{Name}Service service, ILogger<{Name}Controller> logger)
309
+ {
310
+ _service = service;
311
+ _logger = logger;
312
+ }
313
+
314
+ [HttpGet]
315
+ [RequirePermission(Permissions.{Module}.Read)]
316
+ public async Task<ActionResult<List<{Name}ResponseDto>>> GetAll(CancellationToken ct)
317
+ => Ok(await _service.GetAllAsync(ct));
318
+
319
+ [HttpGet("{id:guid}")]
320
+ [RequirePermission(Permissions.{Module}.Read)]
321
+ public async Task<ActionResult<{Name}ResponseDto>> GetById(Guid id, CancellationToken ct)
322
+ {
323
+ var result = await _service.GetByIdAsync(id, ct);
324
+ return result is null ? NotFound() : Ok(result);
325
+ }
326
+
327
+ [HttpPost]
328
+ [RequirePermission(Permissions.{Module}.Create)]
329
+ public async Task<ActionResult<{Name}ResponseDto>> Create([FromBody] Create{Name}Dto dto, CancellationToken ct)
330
+ {
331
+ var result = await _service.CreateAsync(dto, ct);
332
+ return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
333
+ }
334
+
335
+ [HttpDelete("{id:guid}")]
336
+ [RequirePermission(Permissions.{Module}.Delete)]
337
+ public async Task<ActionResult> Delete(Guid id, CancellationToken ct)
338
+ {
339
+ await _service.DeleteAsync(id, ct);
340
+ return NoContent();
341
+ }
342
+ }
343
+ ```
344
+
345
+ **CRITICAL:** Use `[RequirePermission(Permissions.{Module}.{Action})]` on EVERY endpoint — NEVER `[Authorize]` alone (no RBAC enforcement).
346
+
347
+ **Namespace:** `SmartStack.Api.Routing` (NOT `SmartStack.Api.Core.Routing`)
348
+
349
+ **NavRoute resolves at startup from DB:** `platform.administration.users` → `api/platform/administration/users`
350
+
351
+ ---
352
+
353
+ ## Navigation Seed Data Pattern (CRITICAL — routes must be full paths)
354
+
355
+ > **The navigation seed data defines menu routes stored in DB. These routes MUST be full paths starting with `/`.**
356
+ > Short routes (e.g., `humanresources`) cause 400 Bad Request on application-tracking.
357
+
358
+ ### Route Convention
359
+
360
+ | Level | Route Format | Example |
361
+ |-------|-------------|---------|
362
+ | Application | `/{context}/{app-kebab}` | `/business/human-resources` |
363
+ | Module | `/{context}/{app-kebab}/{module-kebab}` | `/business/human-resources/employees` |
364
+ | Section | `/{context}/{app-kebab}/{module-kebab}/{section-kebab}` | `/business/human-resources/employees/list` |
365
+ | Resource | `/{context}/{app-kebab}/{module-kebab}/{section-kebab}/{resource-kebab}` | `/business/human-resources/employees/list/export` |
366
+
367
+ **Rules:**
368
+ - Routes ALWAYS start with `/`
369
+ - Routes ALWAYS include the full hierarchy from context to current level
370
+ - Routes ALWAYS use kebab-case (NOT PascalCase, NOT camelCase)
371
+ - Code identifiers stay PascalCase in C# (`HumanResources`) but routes are kebab-case (`human-resources`)
372
+
373
+ ### ToKebabCase Helper (include in SeedConstants or SeedDataProvider)
374
+
375
+ ```csharp
376
+ private static string ToKebabCase(string value)
377
+ => System.Text.RegularExpressions.Regex
378
+ .Replace(value, "(?<!^)([A-Z])", "-$1")
379
+ .ToLowerInvariant();
380
+ ```
381
+
382
+ ### SeedConstants Pattern
383
+
384
+ ```csharp
385
+ public static class SeedConstants
386
+ {
387
+ // Deterministic GUIDs (SHA256-based, reproducible across environments)
388
+ public static readonly Guid ApplicationId = DeterministicGuid("nav:business.humanresources");
389
+ public static readonly Guid ModuleId = DeterministicGuid("nav:business.humanresources.employees");
390
+ public static readonly Guid SectionId = DeterministicGuid("nav:business.humanresources.employees.list");
391
+
392
+ private static Guid DeterministicGuid(string input)
393
+ {
394
+ var hash = System.Security.Cryptography.SHA256.HashData(
395
+ System.Text.Encoding.UTF8.GetBytes(input));
396
+ var bytes = new byte[16];
397
+ Array.Copy(hash, bytes, 16);
398
+ bytes[6] = (byte)((bytes[6] & 0x0F) | 0x50); // version 5
399
+ bytes[8] = (byte)((bytes[8] & 0x3F) | 0x80); // variant
400
+ return new Guid(bytes);
401
+ }
402
+ }
403
+ ```
404
+
405
+ ### Navigation Seed Data Example
406
+
407
+ ```csharp
408
+ // Application: /business/human-resources
409
+ var app = NavigationApplication.Create(
410
+ businessCtx.Id, "humanresources", "Human Resources", "HR Management",
411
+ "Users", IconType.Lucide,
412
+ "/business/human-resources", // FULL PATH — starts with /, kebab-case
413
+ 10);
414
+
415
+ // Module: /business/human-resources/employees
416
+ var module = NavigationModule.Create(
417
+ app.Id, "employees", "Employees", "Employee management",
418
+ "UserCheck", IconType.Lucide,
419
+ "/business/human-resources/employees", // FULL PATH — includes parent
420
+ 10);
421
+
422
+ // Section: /business/human-resources/employees/departments
423
+ var section = NavigationSection.Create(
424
+ module.Id, "departments", "Departments", "Manage departments",
425
+ "Building2", IconType.Lucide,
426
+ "/business/human-resources/employees/departments", // FULL PATH
427
+ 10);
428
+ ```
429
+
430
+ ### FORBIDDEN in Seed Data
431
+
432
+ | Mistake | Reality |
433
+ |---------|---------|
434
+ | `"humanresources"` as route | Must be `"/business/human-resources"` (full path, kebab-case) |
435
+ | `"employees"` as route | Must be `"/business/human-resources/employees"` (includes parent) |
436
+ | `Guid.NewGuid()` in seed data | Must use deterministic GUIDs (SHA256) |
437
+ | Missing translations | Must have 4 languages: fr, en, it, de |
438
+ | Missing NavigationApplicationSeedData | Menu invisible without Application level |
439
+
440
+ ---
441
+
442
+ ## DbContext Pattern (extensions)
443
+
444
+ ```csharp
445
+ // In IExtensionsDbContext.cs:
446
+ public DbSet<{Name}> {Name}s => Set<{Name}>();
447
+
448
+ // In ExtensionsDbContext.cs (same line):
449
+ public DbSet<{Name}> {Name}s => Set<{Name}>();
450
+ ```
451
+
452
+ ---
453
+
454
+ ## DI Registration Pattern
455
+
456
+ ```csharp
457
+ // In DependencyInjection.cs or ServiceCollectionExtensions.cs:
458
+ services.AddScoped<I{Name}Service, {Name}Service>();
459
+ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
460
+ ```
461
+
462
+ ---
463
+
464
+ ## Common Mistakes to Avoid
465
+
466
+ | Mistake | Reality |
467
+ |---------|---------|
468
+ | `entity.SoftDelete()` | Does NOT exist — no soft delete in BaseEntity |
469
+ | `entity.Code` inherited | Code is a business property — add it yourself |
470
+ | `e.RowVersion` in config | Does NOT exist in BaseEntity |
471
+ | `e.IsDeleted` filter | Does NOT exist — no soft delete |
472
+ | `SmartStack.Api.Core.Routing` | Wrong — use `SmartStack.Api.Routing` |
473
+ | `SystemEntity` base class | Does NOT exist — use `BaseEntity` for all |
474
+ | `[Route] + [NavRoute]` | Only `[NavRoute]` needed (resolves route from DB) |
475
+ | `SmartStack.Domain.Common.Interfaces` | Wrong — interfaces are in `SmartStack.Domain.Common` directly |
476
+ | `[Authorize]` without `[RequirePermission]` | No RBAC enforcement — always use `[RequirePermission]` |
477
+ | `tenantId: Guid.Empty` in services | OWASP A01 — always use `_currentUser.TenantId` |
478
+ | Service without `ICurrentUser` | All tenant data leaks — inject `ICurrentUser` |
479
+ | Route `"humanresources"` in seed data | Must be full path `"/business/human-resources"` |
480
+ | Route without leading `/` | All routes must start with `/` |
481
+ | `Permission.Create()` | Does NOT exist — use `CreateForModule()`, `CreateForSection()`, etc. |
@@ -17,8 +17,9 @@
17
17
  | Validate | MCP `validate_conventions` |
18
18
 
19
19
  **Rules:**
20
- - Inherit `AuditableEntity`, implement `IHasData` for multi-tenant
21
- - Audit fields: CreatedAt, CreatedBy, ModifiedAt, ModifiedBy
20
+ - Inherit `BaseEntity`, implement `ITenantEntity` + `IAuditableEntity`
21
+ - See `references/smartstack-api.md` for exact BaseEntity API (Id, CreatedAt, UpdatedAt only)
22
+ - No Code/IsDeleted/RowVersion in BaseEntity — add business properties yourself
22
23
  - Domain events for state changes
23
24
  - Value objects for composite values
24
25
 
@@ -49,7 +50,8 @@
49
50
 
50
51
  ## Layer 1 — Application (parallel with API)
51
52
 
52
- **Services:** `Application/Services/{ContextPascal}/{App}/{Module}/`
53
+ **Services:** `Application/Services/{ContextPascal}/{App}/{Module}/` (interface)
54
+ **Service impls:** `Infrastructure/Services/{ContextPascal}/{App}/{Module}/` (implementation)
53
55
  **DTOs:** `Application/DTOs/{ContextPascal}/{App}/{Module}/`
54
56
  **Validators:** `Application/Validators/{ContextPascal}/{App}/{Module}/`
55
57
 
@@ -60,10 +62,13 @@
60
62
  | Validate | MCP `validate_conventions` |
61
63
 
62
64
  **Rules:**
65
+ - **ALL services MUST inject `ICurrentUser` and filter by `TenantId`** (see `smartstack-api.md` Service Pattern)
66
+ - **ALL services MUST inject `ILogger<T>`** for production diagnostics
63
67
  - CQRS with MediatR
64
68
  - FluentValidation for all commands
65
69
  - DTOs separate from domain entities
66
70
  - Service interfaces in Application, implementations in Infrastructure
71
+ - **FORBIDDEN:** `tenantId: Guid.Empty`, queries without TenantId filter, services without ICurrentUser
67
72
 
68
73
  ---
69
74
 
@@ -96,27 +101,92 @@
96
101
 
97
102
  **Folder:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/`
98
103
 
104
+ > **Detailed templates:** See ralph-loop `references/core-seed-data.md` for complete C# code templates.
105
+ > Navigation hierarchy: Context → Application → Module → Section → Resource (ALL levels need seed data).
106
+
99
107
  | Action | Tool |
100
108
  |--------|------|
101
109
  | Generate permissions | MCP `generate_permissions` (PRIMARY) |
102
- | Navigation seed | Template below |
103
- | Roles seed | Template below |
104
- | Provider | Template below |
105
-
106
- ### Seed Data Chain (5 files)
107
-
108
- 1. **NavigationModuleSeedData.cs** — Deterministic GUIDs (SHA256), 4 languages
109
- 2. **PermissionsSeedData.cs** — MCP `generate_permissions` first, fallback template
110
- 3. **RolesSeedData.cs** — Context-based: Admin=CRUD, Manager=CRU, Contributor=CR, Viewer=R
111
- 4. **SeedConstants.cs** — Shared deterministic GUIDs
112
- 5. **{App}SeedDataProvider.cs** — Implements IClientSeedDataProvider
113
- - SeedNavigationAsync + SeedPermissionsAsync + SeedRolePermissionsAsync
110
+ | Navigation seed | Templates below |
111
+ | Roles seed | Templates below |
112
+ | Provider | Templates below |
113
+
114
+ ### Seed Data Chain (7 files minimum)
115
+
116
+ 1. **NavigationApplicationSeedData.cs** — Application-level navigation entry (MUST be first)
117
+ 2. **NavigationModuleSeedData.cs** — Deterministic GUIDs (SHA256), 4 languages (fr, en, it, de)
118
+ 3. **NavigationSectionSeedData.cs** — Section-level navigation (if sections defined)
119
+ 4. **NavigationResourceSeedData.cs** — Resource-level navigation (if resources defined)
120
+ 5. **PermissionsSeedData.cs** — MCP `generate_permissions` first, fallback template
121
+ 6. **RolesSeedData.cs** — Context-based: Admin=CRUD, Manager=CRU, Contributor=CR, Viewer=R
122
+ 7. **{App}SeedDataProvider.cs** — Implements IClientSeedDataProvider
123
+ - `SeedNavigationAsync()` — seeds Application → Module → Section → Resource + translations
124
+ - `SeedPermissionsAsync()` + `SeedRolePermissionsAsync()`
114
125
  - DI: `services.AddScoped<IClientSeedDataProvider, {App}SeedDataProvider>()`
115
126
 
127
+ ### Deterministic GUID Pattern
128
+
129
+ ```csharp
130
+ // Use SHA256 for deterministic GUIDs (reproducible across environments)
131
+ public static readonly Guid ModuleId = GenerateDeterministicGuid("nav-module-{app}-{module}");
132
+
133
+ private static Guid GenerateDeterministicGuid(string input)
134
+ {
135
+ var hash = System.Security.Cryptography.SHA256.HashData(
136
+ System.Text.Encoding.UTF8.GetBytes(input));
137
+ var bytes = new byte[16];
138
+ Array.Copy(hash, bytes, 16);
139
+ return new Guid(bytes);
140
+ }
141
+ ```
142
+
143
+ ### Route Convention (CRITICAL — Full Paths Required)
144
+
145
+ > **Routes stored in DB drive the platform menu AND application-tracking.**
146
+ > Short routes (e.g., `humanresources`) cause **400 Bad Request** on every page navigation.
147
+
148
+ **Route format: `/{context}/{app-kebab}/{module-kebab}/{section-kebab}`**
149
+
150
+ | Level | Code (C#) | Route (DB) |
151
+ |-------|-----------|-----------|
152
+ | Application | `humanresources` | `/business/human-resources` |
153
+ | Module | `employees` | `/business/human-resources/employees` |
154
+ | Section | `departments` | `/business/human-resources/employees/departments` |
155
+ | Resource | `export` | `/business/human-resources/employees/departments/export` |
156
+
157
+ **Platform examples (verified from SmartStack.app):**
158
+ - `/platform/administration` (not `administration`)
159
+ - `/platform/administration/users` (not `users`)
160
+ - `/personal/myspace/profile` (not `profile`)
161
+
162
+ - PascalCase for C# code identifiers (`HumanResources`)
163
+ - **kebab-case** for ALL URL routes in seed data (`human-resources`)
164
+ - **Routes MUST start with `/`** and include full parent hierarchy
165
+ - Helper: `ToKebabCase()` transforms PascalCase → kebab-case
166
+
167
+ ```csharp
168
+ private static string ToKebabCase(string value)
169
+ => System.Text.RegularExpressions.Regex.Replace(value, "(?<!^)([A-Z])", "-$1").ToLowerInvariant();
170
+
171
+ // Route construction:
172
+ var appRoute = $"/{ToKebabCase(contextCode)}/{ToKebabCase(appCode)}";
173
+ // → "/business/human-resources"
174
+
175
+ var moduleRoute = $"{appRoute}/{ToKebabCase(moduleCode)}";
176
+ // → "/business/human-resources/employees"
177
+
178
+ var sectionRoute = $"{moduleRoute}/{ToKebabCase(sectionCode)}";
179
+ // → "/business/human-resources/employees/departments"
180
+ ```
181
+
116
182
  **FORBIDDEN:**
117
183
  - `Guid.NewGuid()` → use deterministic GUIDs (SHA256)
118
184
  - Missing translations (must have fr, en, it, de)
119
185
  - Empty seed classes with no seeding logic
186
+ - PascalCase in route URLs → always kebab-case
187
+ - Missing NavigationApplicationSeedData → menu invisible
188
+ - **Short routes without `/` prefix** → `"humanresources"` must be `"/business/human-resources"`
189
+ - **Routes without parent hierarchy** → `"employees"` must be `"/business/human-resources/employees"`
120
190
 
121
191
  ---
122
192