@atlashub/smartstack-cli 3.9.0 → 3.10.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-writer.md +178 -0
- package/templates/skills/application/references/application-roles-template.md +227 -0
- package/templates/skills/application/references/provider-template.md +30 -6
- package/templates/skills/application/steps/step-03-roles.md +45 -7
- package/templates/skills/application/steps/step-03b-provider.md +13 -6
- package/templates/skills/business-analyse/SKILL.md +56 -4
- package/templates/skills/business-analyse/references/agent-pooling-best-practices.md +477 -0
- package/templates/skills/business-analyse/references/cache-warming-strategy.md +578 -0
- package/templates/skills/business-analyse/references/robustness-checks.md +538 -0
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +33 -1
- package/templates/skills/business-analyse/steps/step-00-init.md +166 -0
- package/templates/skills/business-analyse/steps/step-03a-data.md +36 -0
- package/templates/skills/business-analyse/steps/step-03c-compile.md +71 -2
- package/templates/skills/business-analyse/steps/step-03d-validate.md +274 -0
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +166 -0
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +44 -0
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +21 -2
- package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +526 -0
- package/templates/skills/controller/steps/step-03-generate.md +184 -24
- package/templates/skills/controller/templates.md +11 -2
- package/templates/skills/ralph-loop/references/core-seed-data.md +173 -21
package/package.json
CHANGED
|
@@ -88,6 +88,102 @@ Merge a section into an existing feature.json.
|
|
|
88
88
|
9. **Cross-Reference Validation (for specification and handoff sections):** See CROSS-REFERENCE VALIDATION section below
|
|
89
89
|
10. Return confirmation with section size and status
|
|
90
90
|
|
|
91
|
+
### enrichSectionIncremental
|
|
92
|
+
Incrementally update a section in feature.json using PATCH-style operations instead of full section replacement. Optimized for large files and preventing file size issues.
|
|
93
|
+
|
|
94
|
+
**Input:**
|
|
95
|
+
- featureId: FEAT-NNN or full path to feature.json
|
|
96
|
+
- section: one of [discovery, analysis, specification, validation, handoff, suggestions, cadrage, consolidation, modules, dependencyGraph, metadata.workflow]
|
|
97
|
+
- operation: "merge" | "append" | "update" | "delete"
|
|
98
|
+
- path: JSON path within the section (e.g., "entities[2]", "useCases", "modules[0].status")
|
|
99
|
+
- data: the data to merge/append/update
|
|
100
|
+
|
|
101
|
+
**Operations:**
|
|
102
|
+
|
|
103
|
+
1. **merge** - Deep merge data into existing section
|
|
104
|
+
```javascript
|
|
105
|
+
// Example: Add new entities to analysis.entities without rewriting entire analysis
|
|
106
|
+
enrichSectionIncremental({
|
|
107
|
+
featureId: "FEAT-001",
|
|
108
|
+
section: "analysis",
|
|
109
|
+
operation: "merge",
|
|
110
|
+
path: "entities",
|
|
111
|
+
data: [
|
|
112
|
+
{ name: "NewEntity", description: "...", attributes: [...] }
|
|
113
|
+
]
|
|
114
|
+
})
|
|
115
|
+
// Result: analysis.entities now has existing entities + NewEntity
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
2. **append** - Append item to an array
|
|
119
|
+
```javascript
|
|
120
|
+
// Example: Add a single business rule without rewriting all rules
|
|
121
|
+
enrichSectionIncremental({
|
|
122
|
+
featureId: "FEAT-001",
|
|
123
|
+
section: "analysis",
|
|
124
|
+
operation: "append",
|
|
125
|
+
path: "businessRules",
|
|
126
|
+
data: { id: "BR-VAL-ABC-042", name: "...", statement: "..." }
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
3. **update** - Update specific field in section
|
|
131
|
+
```javascript
|
|
132
|
+
// Example: Update module status without rewriting entire modules array
|
|
133
|
+
enrichSectionIncremental({
|
|
134
|
+
featureId: "FEAT-001",
|
|
135
|
+
section: "modules",
|
|
136
|
+
operation: "update",
|
|
137
|
+
path: "[0].status", // Path to first module's status field
|
|
138
|
+
data: "handed-off"
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
4. **delete** - Remove item from section
|
|
143
|
+
```javascript
|
|
144
|
+
// Example: Remove a specific entity
|
|
145
|
+
enrichSectionIncremental({
|
|
146
|
+
featureId: "FEAT-001",
|
|
147
|
+
section: "analysis",
|
|
148
|
+
operation: "delete",
|
|
149
|
+
path: "entities[2]" // Delete third entity
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Process:**
|
|
154
|
+
1. Find and read feature.json (use findFeature if given ID)
|
|
155
|
+
2. Navigate to the specified section
|
|
156
|
+
3. Apply the incremental operation:
|
|
157
|
+
- **merge**: Deep merge arrays (append unique items), shallow merge objects
|
|
158
|
+
- **append**: Push item to array at path
|
|
159
|
+
- **update**: Set value at path
|
|
160
|
+
- **delete**: Remove item at path
|
|
161
|
+
4. Update metadata.updatedAt with current timestamp
|
|
162
|
+
5. Update metadata.updatedBy with agent name
|
|
163
|
+
6. Write back with pretty-print (2-space indent)
|
|
164
|
+
7. **File Size Check:** If resulting file > 100KB, display WARNING
|
|
165
|
+
8. Validate schema before writing
|
|
166
|
+
9. **Cross-Reference Validation:** Same rules as enrichSection
|
|
167
|
+
10. Return confirmation with operation summary and file size
|
|
168
|
+
|
|
169
|
+
**File Size Management:**
|
|
170
|
+
- Before write: Check if file would exceed 100KB
|
|
171
|
+
- If > 100KB: Display WARNING with recommendation to split into smaller operations
|
|
172
|
+
- If > 500KB: BLOCKING ERROR - file too large, must use smaller chunks
|
|
173
|
+
- Track cumulative file size growth across operations
|
|
174
|
+
|
|
175
|
+
**Advantages over enrichSection:**
|
|
176
|
+
- 50-70% reduction in tokens for large sections
|
|
177
|
+
- Avoids "file too large" errors by updating incrementally
|
|
178
|
+
- Allows progressive enrichment without reading/writing entire sections
|
|
179
|
+
- Better performance for repeated updates (e.g., module loop)
|
|
180
|
+
|
|
181
|
+
**Use Cases:**
|
|
182
|
+
- Adding entities one-by-one during module specification
|
|
183
|
+
- Updating module status in master without rewriting all modules
|
|
184
|
+
- Appending business rules progressively
|
|
185
|
+
- Updating handoff sections module-by-module
|
|
186
|
+
|
|
91
187
|
### enrichModuleHandoff
|
|
92
188
|
Write the handoff section into a module feature.json. Specialized operation for step-05 module loop.
|
|
93
189
|
|
|
@@ -474,6 +570,88 @@ Before EVERY enrichSection() call for specification or handoff sections, validat
|
|
|
474
570
|
6. **Pretty-print JSON** - use 2-space indentation
|
|
475
571
|
7. **Timestamp management** - always set metadata.updatedAt to current ISO timestamp on write
|
|
476
572
|
8. **Idempotency** - calling the same operation twice with same data should produce same result
|
|
573
|
+
9. **File size management** - check file size before write, use incremental operations for large files
|
|
574
|
+
|
|
575
|
+
## File Size Management (CRITICAL)
|
|
576
|
+
|
|
577
|
+
**Problem:** Large feature.json files (>100KB) can cause write failures and token exhaustion.
|
|
578
|
+
|
|
579
|
+
**Solution:** Progressive monitoring and incremental operations.
|
|
580
|
+
|
|
581
|
+
### Size Thresholds
|
|
582
|
+
|
|
583
|
+
| File Size | Status | Action |
|
|
584
|
+
|-----------|--------|--------|
|
|
585
|
+
| < 50KB | ✓ Safe | Use enrichSection normally |
|
|
586
|
+
| 50-100KB | ⚠ Warning | Display warning, recommend enrichSectionIncremental for next operations |
|
|
587
|
+
| 100-500KB | ⚠ High | STRONGLY recommend enrichSectionIncremental, limit enrichSection use |
|
|
588
|
+
| > 500KB | ✗ Critical | BLOCK enrichSection, REQUIRE enrichSectionIncremental or split file |
|
|
589
|
+
|
|
590
|
+
### Pre-Write File Size Check
|
|
591
|
+
|
|
592
|
+
Before EVERY write operation:
|
|
593
|
+
|
|
594
|
+
```javascript
|
|
595
|
+
const currentFileSize = getFileSize(featurePath);
|
|
596
|
+
const estimatedNewSize = currentFileSize + newDataSize;
|
|
597
|
+
|
|
598
|
+
if (estimatedNewSize > 100 * 1024) { // 100KB
|
|
599
|
+
WARNING(`Feature.json will be ${formatBytes(estimatedNewSize)} after write`);
|
|
600
|
+
WARNING(`Recommend using enrichSectionIncremental for future operations`);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (estimatedNewSize > 500 * 1024) { // 500KB
|
|
604
|
+
BLOCKING_ERROR(`Feature.json would exceed 500KB (${formatBytes(estimatedNewSize)})`);
|
|
605
|
+
BLOCKING_ERROR(`Use enrichSectionIncremental instead of enrichSection`);
|
|
606
|
+
STOP;
|
|
607
|
+
}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Operation Selection Guide
|
|
611
|
+
|
|
612
|
+
| Scenario | Recommended Operation | Reason |
|
|
613
|
+
|----------|----------------------|--------|
|
|
614
|
+
| First-time section write | `enrichSection` | No existing data, full write needed |
|
|
615
|
+
| Module loop (3+ iterations) | `enrichSectionIncremental` | Avoids rewriting same data multiple times |
|
|
616
|
+
| Large sections (>20KB) | `enrichSectionIncremental` | Reduces token usage by 50-70% |
|
|
617
|
+
| File size > 100KB | `enrichSectionIncremental` (REQUIRED) | Prevents file size bloat |
|
|
618
|
+
| Single field update | `enrichSectionIncremental` with `update` | Most efficient for targeted changes |
|
|
619
|
+
|
|
620
|
+
### Monitoring & Reporting
|
|
621
|
+
|
|
622
|
+
After EVERY write, report file size status:
|
|
623
|
+
|
|
624
|
+
```
|
|
625
|
+
✓ feature.json written successfully
|
|
626
|
+
Path: docs/business/HumanResources/Projects/business-analyse/v1.0/feature.json
|
|
627
|
+
Size: 87.3 KB (↑ 12.1 KB from previous)
|
|
628
|
+
Status: ⚠ Approaching 100KB threshold
|
|
629
|
+
Recommendation: Use enrichSectionIncremental for remaining modules
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Splitting Large Files (Advanced)
|
|
633
|
+
|
|
634
|
+
If a module feature.json exceeds 500KB despite incremental operations:
|
|
635
|
+
|
|
636
|
+
1. **Split specification section** into separate files:
|
|
637
|
+
- `specification-entities.json` (entities, relationships)
|
|
638
|
+
- `specification-rules.json` (business rules, validations)
|
|
639
|
+
- `specification-ui.json` (wireframes, sections, navigation)
|
|
640
|
+
|
|
641
|
+
2. **Update feature.json** with file references:
|
|
642
|
+
```json
|
|
643
|
+
{
|
|
644
|
+
"specification": {
|
|
645
|
+
"$ref": "./specification-entities.json",
|
|
646
|
+
"$ref2": "./specification-rules.json",
|
|
647
|
+
"$ref3": "./specification-ui.json"
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
3. **ba-reader** auto-resolves references when reading
|
|
653
|
+
|
|
654
|
+
**Note:** File splitting is a LAST RESORT. Prefer enrichSectionIncremental first.
|
|
477
655
|
|
|
478
656
|
## Error Handling
|
|
479
657
|
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Application Roles Seed Data Template
|
|
2
|
+
|
|
3
|
+
> Referenced from `core-seed-data.md` and `step-03-roles.md` — C# template for application-scoped roles in client projects.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem Statement
|
|
8
|
+
|
|
9
|
+
When using `IClientSeedDataProvider` (client projects with `seeding_strategy = "provider"`), role-permission mappings reference roles by their `Code`:
|
|
10
|
+
|
|
11
|
+
```csharp
|
|
12
|
+
var role = roles.FirstOrDefault(r => r.Code == mapping.RoleCode); // "admin", "manager", "contributor", "viewer"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**However**, the current templates do NOT create these application-scoped roles. They assume:
|
|
16
|
+
- System roles (SuperAdmin, PlatformAdmin, TenantAdmin, StandardUser) exist in Core
|
|
17
|
+
- Application-scoped roles (Admin, Manager, Contributor, Viewer) already exist with valid `Code` values
|
|
18
|
+
|
|
19
|
+
**Result:** Role-permission mappings fail silently when `role == null`.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Solution: Application Roles Seed Data
|
|
24
|
+
|
|
25
|
+
Create application-scoped roles with deterministic GUIDs and valid `Code` values.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## File Location
|
|
30
|
+
|
|
31
|
+
**Path:** `Infrastructure/Persistence/Seeding/Data/ApplicationRolesSeedData.cs`
|
|
32
|
+
|
|
33
|
+
This file should be created **ONCE per application** (not per module).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Template
|
|
38
|
+
|
|
39
|
+
```csharp
|
|
40
|
+
using SmartStack.Domain.Platform.Administration.Roles;
|
|
41
|
+
|
|
42
|
+
namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data;
|
|
43
|
+
|
|
44
|
+
/// <summary>
|
|
45
|
+
/// Application-scoped role seed data for {AppLabel}.
|
|
46
|
+
/// Defines the 4 standard application roles: Admin, Manager, Contributor, Viewer.
|
|
47
|
+
/// Consumed by IClientSeedDataProvider at application startup.
|
|
48
|
+
/// </summary>
|
|
49
|
+
public static class ApplicationRolesSeedData
|
|
50
|
+
{
|
|
51
|
+
// Deterministic GUIDs for application roles
|
|
52
|
+
// Generated from: "role-{applicationId}-{roleType}"
|
|
53
|
+
private static readonly Guid ApplicationId = {ApplicationGuid}; // From NavigationApplicationSeedData
|
|
54
|
+
|
|
55
|
+
public static readonly Guid AdminRoleId = GenerateRoleGuid("admin");
|
|
56
|
+
public static readonly Guid ManagerRoleId = GenerateRoleGuid("manager");
|
|
57
|
+
public static readonly Guid ContributorRoleId = GenerateRoleGuid("contributor");
|
|
58
|
+
public static readonly Guid ViewerRoleId = GenerateRoleGuid("viewer");
|
|
59
|
+
|
|
60
|
+
/// <summary>
|
|
61
|
+
/// Returns application-scoped role entries for seeding into core.auth_Roles.
|
|
62
|
+
/// </summary>
|
|
63
|
+
public static IEnumerable<ApplicationRoleSeedEntry> GetRoleEntries()
|
|
64
|
+
{
|
|
65
|
+
yield return new ApplicationRoleSeedEntry
|
|
66
|
+
{
|
|
67
|
+
Id = AdminRoleId,
|
|
68
|
+
Code = "admin",
|
|
69
|
+
Name = "{AppLabel} Admin",
|
|
70
|
+
Description = "Full administrative access to {AppLabel}",
|
|
71
|
+
ApplicationId = ApplicationId,
|
|
72
|
+
IsSystem = false,
|
|
73
|
+
IsActive = true,
|
|
74
|
+
DisplayOrder = 1
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
yield return new ApplicationRoleSeedEntry
|
|
78
|
+
{
|
|
79
|
+
Id = ManagerRoleId,
|
|
80
|
+
Code = "manager",
|
|
81
|
+
Name = "{AppLabel} Manager",
|
|
82
|
+
Description = "Management access to {AppLabel} (Create, Read, Update)",
|
|
83
|
+
ApplicationId = ApplicationId,
|
|
84
|
+
IsSystem = false,
|
|
85
|
+
IsActive = true,
|
|
86
|
+
DisplayOrder = 2
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
yield return new ApplicationRoleSeedEntry
|
|
90
|
+
{
|
|
91
|
+
Id = ContributorRoleId,
|
|
92
|
+
Code = "contributor",
|
|
93
|
+
Name = "{AppLabel} Contributor",
|
|
94
|
+
Description = "Contributor access to {AppLabel} (Create, Read)",
|
|
95
|
+
ApplicationId = ApplicationId,
|
|
96
|
+
IsSystem = false,
|
|
97
|
+
IsActive = true,
|
|
98
|
+
DisplayOrder = 3
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
yield return new ApplicationRoleSeedEntry
|
|
102
|
+
{
|
|
103
|
+
Id = ViewerRoleId,
|
|
104
|
+
Code = "viewer",
|
|
105
|
+
Name = "{AppLabel} Viewer",
|
|
106
|
+
Description = "Read-only access to {AppLabel}",
|
|
107
|
+
ApplicationId = ApplicationId,
|
|
108
|
+
IsSystem = false,
|
|
109
|
+
IsActive = true,
|
|
110
|
+
DisplayOrder = 4
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private static Guid GenerateRoleGuid(string roleType)
|
|
115
|
+
{
|
|
116
|
+
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
117
|
+
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"role-{ApplicationId}-{roleType}"));
|
|
118
|
+
return new Guid(hash.Take(16).ToArray());
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// <summary>Seed entry DTO for application role.</summary>
|
|
123
|
+
public class ApplicationRoleSeedEntry
|
|
124
|
+
{
|
|
125
|
+
public Guid Id { get; init; }
|
|
126
|
+
public string Code { get; init; } = null!;
|
|
127
|
+
public string Name { get; init; } = null!;
|
|
128
|
+
public string Description { get; init; } = null!;
|
|
129
|
+
public Guid ApplicationId { get; init; }
|
|
130
|
+
public bool IsSystem { get; init; }
|
|
131
|
+
public bool IsActive { get; init; }
|
|
132
|
+
public int DisplayOrder { get; init; }
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Placeholder Replacement
|
|
139
|
+
|
|
140
|
+
| Placeholder | Description | Example |
|
|
141
|
+
|-------------|-------------|---------|
|
|
142
|
+
| `{BaseNamespace}` | Root namespace of the client project | `SmartStack.Modules.RessourcesHumaines` |
|
|
143
|
+
| `{AppLabel}` | Human-readable application label (EN) | `Human Resources` |
|
|
144
|
+
| `{ApplicationGuid}` | GUID of the application (from NavigationApplicationSeedData) | `30f1fbba-e8c3-4879-9a49-d18deaa70a83` |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Integration into IClientSeedDataProvider
|
|
149
|
+
|
|
150
|
+
Add a new method `SeedRolesAsync()` to the provider:
|
|
151
|
+
|
|
152
|
+
```csharp
|
|
153
|
+
public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
|
|
154
|
+
{
|
|
155
|
+
// Check idempotence
|
|
156
|
+
var exists = await context.Roles
|
|
157
|
+
.AnyAsync(r => r.ApplicationId == ApplicationRolesSeedData.ApplicationId, ct);
|
|
158
|
+
if (exists) return;
|
|
159
|
+
|
|
160
|
+
// Create application-scoped roles using factory method
|
|
161
|
+
foreach (var entry in ApplicationRolesSeedData.GetRoleEntries())
|
|
162
|
+
{
|
|
163
|
+
var role = Role.Create(
|
|
164
|
+
entry.Code,
|
|
165
|
+
entry.Name,
|
|
166
|
+
entry.Description,
|
|
167
|
+
entry.ApplicationId,
|
|
168
|
+
entry.IsSystem);
|
|
169
|
+
|
|
170
|
+
context.Roles.Add(role);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Execution Order in Provider
|
|
180
|
+
|
|
181
|
+
**CRITICAL:** Roles must be created BEFORE role-permission mappings.
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
1. SeedNavigationAsync() → Creates application + modules + translations
|
|
185
|
+
2. SeedRolesAsync() → Creates application-scoped roles (NEW)
|
|
186
|
+
3. SeedPermissionsAsync() → Creates permissions
|
|
187
|
+
4. SeedRolePermissionsAsync() → Maps roles to permissions (now succeeds because roles exist)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Verification Checklist
|
|
193
|
+
|
|
194
|
+
Before marking the task as completed, verify:
|
|
195
|
+
|
|
196
|
+
- [ ] `ApplicationRolesSeedData.cs` created in `Infrastructure/Persistence/Seeding/Data/`
|
|
197
|
+
- [ ] Deterministic GUIDs used (NEVER `Guid.NewGuid()`)
|
|
198
|
+
- [ ] 4 roles defined: Admin, Manager, Contributor, Viewer
|
|
199
|
+
- [ ] Each role has a valid `Code` value ("admin", "manager", "contributor", "viewer")
|
|
200
|
+
- [ ] Each role has `ApplicationId` set to the application GUID
|
|
201
|
+
- [ ] `SeedRolesAsync()` method added to `IClientSeedDataProvider`
|
|
202
|
+
- [ ] `SeedRolesAsync()` is idempotent (checks existence before inserting)
|
|
203
|
+
- [ ] `Role.Create()` factory method used (NEVER `new Role()`)
|
|
204
|
+
- [ ] `SaveChangesAsync()` called after role creation
|
|
205
|
+
- [ ] Execution order: Navigation → Roles → Permissions → RolePermissions
|
|
206
|
+
- [ ] `dotnet build` passes after generation
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Notes
|
|
211
|
+
|
|
212
|
+
- **Application ID source:** Read from the navigation application created in `SeedNavigationAsync()` or from `{AppPascal}NavigationSeedData.cs`
|
|
213
|
+
- **Role factory method:** Use `Role.Create(code, name, description, applicationId, isSystem)` from SmartStack.Domain
|
|
214
|
+
- **Code uniqueness:** Role codes must be unique within the application scope
|
|
215
|
+
- **System roles:** These are NOT system roles (IsSystem = false) - they are application-scoped roles
|
|
216
|
+
- **Tenant isolation:** Application-scoped roles are automatically tenant-isolated via the Core authorization system
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Migration Impact
|
|
221
|
+
|
|
222
|
+
**For existing projects without application roles:**
|
|
223
|
+
1. Generate `ApplicationRolesSeedData.cs` using this template
|
|
224
|
+
2. Add `SeedRolesAsync()` method to the existing `IClientSeedDataProvider`
|
|
225
|
+
3. Update the provider's execution to call `SeedRolesAsync()` BEFORE `SeedRolePermissionsAsync()`
|
|
226
|
+
4. Run the application - roles will be created on next startup
|
|
227
|
+
5. Role-permission mappings will now succeed
|
|
@@ -19,7 +19,7 @@ using SmartStack.Domain.Platform.Administration.Roles;
|
|
|
19
19
|
namespace {BaseNamespace}.Infrastructure.Persistence.Seeding;
|
|
20
20
|
|
|
21
21
|
/// <summary>
|
|
22
|
-
/// Seeds {AppLabel} navigation, permissions, and role-permission data
|
|
22
|
+
/// Seeds {AppLabel} navigation, roles, permissions, and role-permission data
|
|
23
23
|
/// into the SmartStack Core schema at application startup.
|
|
24
24
|
/// Implements <see cref="IClientSeedDataProvider"/> for runtime seeding
|
|
25
25
|
/// (no Core migrations required).
|
|
@@ -75,6 +75,29 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
|
75
75
|
await ((DbContext)context).SaveChangesAsync(ct);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
|
|
79
|
+
{
|
|
80
|
+
// Check idempotence
|
|
81
|
+
var applicationId = ApplicationRolesSeedData.ApplicationId;
|
|
82
|
+
var exists = await context.Roles
|
|
83
|
+
.AnyAsync(r => r.ApplicationId == applicationId, ct);
|
|
84
|
+
if (exists) return;
|
|
85
|
+
|
|
86
|
+
// Create application-scoped roles (Admin, Manager, Contributor, Viewer)
|
|
87
|
+
// Use data from ApplicationRolesSeedData.cs
|
|
88
|
+
foreach (var entry in ApplicationRolesSeedData.GetRoleEntries())
|
|
89
|
+
{
|
|
90
|
+
var role = Role.Create(
|
|
91
|
+
entry.Code,
|
|
92
|
+
entry.Name,
|
|
93
|
+
entry.Description,
|
|
94
|
+
entry.ApplicationId,
|
|
95
|
+
entry.IsSystem);
|
|
96
|
+
context.Roles.Add(role);
|
|
97
|
+
}
|
|
98
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
99
|
+
}
|
|
100
|
+
|
|
78
101
|
public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
|
|
79
102
|
{
|
|
80
103
|
// Check idempotence
|
|
@@ -126,9 +149,10 @@ services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>();
|
|
|
126
149
|
|
|
127
150
|
## Critical Rules
|
|
128
151
|
|
|
129
|
-
1. **Factory methods mandatory**: `NavigationModule.Create(...)`, `Permission.CreateForModule(...)`, `RolePermission.Create(...)` — NEVER `new Entity()`
|
|
152
|
+
1. **Factory methods mandatory**: `NavigationModule.Create(...)`, `Role.Create(...)`, `Permission.CreateForModule(...)`, `RolePermission.Create(...)` — NEVER `new Entity()`
|
|
130
153
|
2. **Idempotence**: Each Seed method checks existence before inserting
|
|
131
|
-
3. **SaveChangesAsync per group**: Navigation → save → Permissions → save → RolePermissions → save
|
|
132
|
-
4. **
|
|
133
|
-
5. **
|
|
134
|
-
6. **
|
|
154
|
+
3. **SaveChangesAsync per group**: Navigation → save → Roles → save → Permissions → save → RolePermissions → save
|
|
155
|
+
4. **Execution order**: `SeedRolesAsync()` MUST be called BEFORE `SeedRolePermissionsAsync()` (roles must exist before mapping)
|
|
156
|
+
5. **Deterministic GUIDs**: Use IDs from SeedData classes (not `Guid.NewGuid()`)
|
|
157
|
+
6. **Resolve FK by Code**: Parent modules and roles are found by `Code`, not hardcoded GUID
|
|
158
|
+
7. **Order property**: Use `100` as default. If multiple providers exist, they run in Order sequence
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: step-03-roles
|
|
3
|
-
description: Generate role-permission mappings using MCP scaffold_role_permissions (with fallback)
|
|
3
|
+
description: Generate application roles and role-permission mappings using MCP scaffold_role_permissions (with fallback)
|
|
4
4
|
prev_step: steps/step-02-permissions.md
|
|
5
5
|
next_step: steps/step-03b-provider.md
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Step 3: Role-Permission Mapping
|
|
8
|
+
# Step 3: Application Roles & Role-Permission Mapping
|
|
9
9
|
|
|
10
10
|
## MANDATORY EXECUTION RULES
|
|
11
11
|
|
|
12
|
+
- For **client projects** (`seeding_strategy = "provider"`): Generate ApplicationRolesSeedData.cs and module role mappings
|
|
13
|
+
- For **core projects** (`seeding_strategy = "hasdata"`): Use RolePermissionConfiguration.cs
|
|
12
14
|
- PREFER MCP `scaffold_role_permissions` tool as the primary method
|
|
13
15
|
- If MCP is unavailable or the call fails, use the FALLBACK PROCEDURE below
|
|
14
16
|
- ALWAYS assign permissions to default roles
|
|
15
17
|
- NEVER leave permissions without role assignments
|
|
16
|
-
- ALWAYS WRITE generated code to the actual
|
|
18
|
+
- ALWAYS WRITE generated code to the actual files
|
|
17
19
|
|
|
18
20
|
## YOUR TASK
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
For **client projects**:
|
|
23
|
+
1. **ApplicationRolesSeedData.cs** (once per application) — defines the 4 application-scoped roles
|
|
24
|
+
2. **{Module}RolePermissionSeedData.cs** (per module) — maps permissions to roles by Code
|
|
25
|
+
|
|
26
|
+
For **core projects**:
|
|
21
27
|
1. RolePermissionConfiguration.cs HasData() entries
|
|
22
28
|
2. Default role assignments (SuperAdmin, PlatformAdmin, TenantAdmin, StandardUser)
|
|
23
29
|
3. Application-scoped role assignments (Admin, Manager, Contributor, Viewer)
|
|
@@ -131,13 +137,38 @@ If MCP call fails or `{mcp_available}` = false:
|
|
|
131
137
|
**For core (`{seeding_strategy}` = "hasdata"):** Write in RolePermissionConfiguration.cs (existing pattern)
|
|
132
138
|
|
|
133
139
|
**For client (`{seeding_strategy}` = "provider"):** DO NOT write in RolePermissionConfiguration.cs (does not exist in client projects).
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
|
|
141
|
+
Instead, create TWO files:
|
|
142
|
+
|
|
143
|
+
### 1. ApplicationRolesSeedData.cs (ONCE per application)
|
|
144
|
+
|
|
145
|
+
**File:** `Infrastructure/Persistence/Seeding/Data/ApplicationRolesSeedData.cs`
|
|
146
|
+
|
|
147
|
+
**Purpose:** Defines the 4 standard application-scoped roles (Admin, Manager, Contributor, Viewer) with valid `Code` values.
|
|
148
|
+
|
|
149
|
+
**CRITICAL:** Without this file, role-permission mappings in `SeedRolePermissionsAsync()` will fail silently because `roles.FirstOrDefault(r => r.Code == mapping.RoleCode)` will return null.
|
|
150
|
+
|
|
151
|
+
See [references/application-roles-template.md](../references/application-roles-template.md) for the complete template.
|
|
152
|
+
|
|
153
|
+
**Key requirements:**
|
|
154
|
+
- Deterministic GUIDs based on `role-{applicationId}-{roleType}`
|
|
155
|
+
- 4 roles: Admin, Manager, Contributor, Viewer
|
|
156
|
+
- Each role has a valid `Code` property ("admin", "manager", "contributor", "viewer")
|
|
157
|
+
- `ApplicationId` references the navigation application GUID
|
|
158
|
+
- `IsSystem = false` (application-scoped, not system roles)
|
|
159
|
+
|
|
160
|
+
**Detection:** Check if ApplicationRolesSeedData.cs exists. If yes, skip creation (already exists from Module 1). If no, create it.
|
|
161
|
+
|
|
162
|
+
### 2. {Module}RolePermissionSeedData.cs (PER module)
|
|
163
|
+
|
|
164
|
+
**File:** `Infrastructure/Persistence/Seeding/Data/{Domain}/{Module}RolePermissionSeedData.cs`
|
|
165
|
+
|
|
166
|
+
**Purpose:** Maps permissions to roles by Code (e.g., "admin" → "{navRoute}.*").
|
|
136
167
|
|
|
137
168
|
Content: static class with method `GetRolePermissionEntries()` that returns the role-permission mapping data.
|
|
138
169
|
These entries will be consumed by the `IClientSeedDataProvider` at step 03b.
|
|
139
170
|
|
|
140
|
-
**After creating
|
|
171
|
+
**After creating both files:** Proceed to step-03b-provider.md (which will skip for core projects).
|
|
141
172
|
|
|
142
173
|
---
|
|
143
174
|
|
|
@@ -280,6 +311,13 @@ If user selects "Custom adjustments", ask which roles/permissions to change and
|
|
|
280
311
|
|
|
281
312
|
## SUCCESS METRICS
|
|
282
313
|
|
|
314
|
+
**For client projects:**
|
|
315
|
+
- ApplicationRolesSeedData.cs created (once per application)
|
|
316
|
+
- {Module}RolePermissionSeedData.cs created with role-permission mappings
|
|
317
|
+
- All 4 application roles defined with valid Code values
|
|
318
|
+
- Proceeded to step-03b-provider.md
|
|
319
|
+
|
|
320
|
+
**For core projects:**
|
|
283
321
|
- Role-permission mappings generated (via MCP or fallback)
|
|
284
322
|
- RolePermissionConfiguration.cs WRITTEN with new entries
|
|
285
323
|
- All default roles have appropriate access
|
|
@@ -50,9 +50,10 @@ From previous steps:
|
|
|
50
50
|
## FILES TO GENERATE
|
|
51
51
|
|
|
52
52
|
See [references/provider-template.md](../references/provider-template.md) for the complete implementation:
|
|
53
|
-
- **
|
|
53
|
+
- **ApplicationRolesSeedData.cs** (once per application) — defines application-scoped roles (Admin, Manager, Contributor, Viewer)
|
|
54
|
+
- **Provider class** (`{AppPascalName}SeedDataProvider.cs`) with 4 seed methods (Navigation, Roles, Permissions, RolePermissions)
|
|
54
55
|
- **DI registration** pattern for `Infrastructure/DependencyInjection.cs`
|
|
55
|
-
- **
|
|
56
|
+
- **7 critical rules** (factory methods, idempotence, execution order, SaveChangesAsync order, deterministic GUIDs, FK by Code, Order property)
|
|
56
57
|
|
|
57
58
|
---
|
|
58
59
|
|
|
@@ -64,9 +65,11 @@ Identify all SeedData files created in previous steps:
|
|
|
64
65
|
|
|
65
66
|
```
|
|
66
67
|
Glob: **/Persistence/Seeding/Data/{Domain}/*SeedData.cs
|
|
68
|
+
Glob: **/Persistence/Seeding/Data/ApplicationRolesSeedData.cs
|
|
67
69
|
```
|
|
68
70
|
|
|
69
71
|
Expected files:
|
|
72
|
+
- `ApplicationRolesSeedData.cs` (application-level, once per app)
|
|
70
73
|
- `{Module}NavigationSeedData.cs` (from step 01)
|
|
71
74
|
- `{Module}NavigationTranslationSeedData.cs` (from step 01)
|
|
72
75
|
- `{Module}PermissionSeedData.cs` (from step 02)
|
|
@@ -96,9 +99,11 @@ Add the `IClientSeedDataProvider` registration.
|
|
|
96
99
|
### 4. Verify
|
|
97
100
|
|
|
98
101
|
Before proceeding to step-04, verify:
|
|
99
|
-
- [ ]
|
|
102
|
+
- [ ] ApplicationRolesSeedData.cs created (once per application)
|
|
103
|
+
- [ ] Provider generated with 4 methods (SeedNavigationAsync, SeedRolesAsync, SeedPermissionsAsync, SeedRolePermissionsAsync)
|
|
104
|
+
- [ ] Execution order: Navigation → Roles → Permissions → RolePermissions
|
|
100
105
|
- [ ] Registered in DI (`services.AddScoped<IClientSeedDataProvider, ...>()`)
|
|
101
|
-
- [ ] Consumes SeedData classes from steps 01-03
|
|
106
|
+
- [ ] Consumes SeedData classes from steps 01-03 + ApplicationRolesSeedData
|
|
102
107
|
- [ ] Idempotent (check existence before insert)
|
|
103
108
|
- [ ] Uses factory methods (no `new Entity()`)
|
|
104
109
|
|
|
@@ -106,9 +111,11 @@ Before proceeding to step-04, verify:
|
|
|
106
111
|
|
|
107
112
|
## SUCCESS METRICS
|
|
108
113
|
|
|
109
|
-
-
|
|
114
|
+
- ApplicationRolesSeedData.cs created (application-level)
|
|
115
|
+
- Provider class generated with all 4 seed methods (Navigation, Roles, Permissions, RolePermissions)
|
|
116
|
+
- Correct execution order enforced
|
|
110
117
|
- DI registration added
|
|
111
|
-
- SeedData classes from steps 01-03 properly consumed
|
|
118
|
+
- SeedData classes from steps 01-03 + ApplicationRolesSeedData properly consumed
|
|
112
119
|
- All methods are idempotent
|
|
113
120
|
- Factory methods used throughout
|
|
114
121
|
- Proceeded to step-04-backend.md
|