@atlashub/smartstack-cli 3.9.0 → 3.12.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/index.js +2544 -2461
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +479 -6185
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/ba-writer.md +178 -0
- package/templates/agents/db-reader.md +149 -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/cadrage-vibe-coding.md +9 -19
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +12 -2
- package/templates/skills/business-analyse/references/deploy-data-build.md +36 -25
- package/templates/skills/business-analyse/references/detection-strategies.md +424 -0
- package/templates/skills/business-analyse/references/html-data-mapping.md +4 -0
- package/templates/skills/business-analyse/references/prd-generation.md +258 -0
- package/templates/skills/business-analyse/references/robustness-checks.md +538 -0
- package/templates/skills/business-analyse/references/validate-incremental-html.md +47 -4
- package/templates/skills/business-analyse/references/validation-checklist.md +281 -0
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +33 -1
- package/templates/skills/business-analyse/steps/step-00-init.md +70 -75
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +8 -22
- package/templates/skills/business-analyse/steps/step-03a-data.md +20 -410
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +356 -0
- package/templates/skills/business-analyse/steps/step-03a2-analysis.md +143 -0
- package/templates/skills/business-analyse/steps/step-03b-ui.md +3 -0
- package/templates/skills/business-analyse/steps/step-03c-compile.md +72 -3
- package/templates/skills/business-analyse/steps/step-03d-validate.md +36 -3
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +21 -440
- package/templates/skills/business-analyse/steps/step-04a-collect.md +304 -0
- package/templates/skills/business-analyse/steps/step-04b-analyze.md +239 -0
- package/templates/skills/business-analyse/steps/step-04c-decide.md +186 -0
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +44 -0
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +42 -2
- package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +518 -0
- package/templates/skills/controller/steps/step-03-generate.md +184 -24
- package/templates/skills/controller/templates.md +11 -2
- package/templates/skills/debug/SKILL.md +156 -53
- package/templates/skills/debug/references/team-protocol.md +232 -0
- package/templates/skills/ralph-loop/references/category-rules.md +46 -0
- package/templates/skills/ralph-loop/references/compact-loop.md +32 -2
- package/templates/skills/ralph-loop/references/core-seed-data.md +233 -21
- package/templates/skills/ralph-loop/steps/step-00-init.md +64 -1
- package/templates/skills/ralph-loop/steps/step-04-check.md +27 -2
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,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: db-reader
|
|
3
|
+
description: Read-only database inspector for debugging - verifies data state, relationships, and integrity without any modification.
|
|
4
|
+
color: cyan
|
|
5
|
+
model: haiku
|
|
6
|
+
tools: Read, Glob, Grep, Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are a **read-only database inspector**. Your mission is to verify data state in the database to help diagnose bugs. You MUST NEVER modify any data.
|
|
10
|
+
|
|
11
|
+
## ABSOLUTE RESTRICTIONS
|
|
12
|
+
|
|
13
|
+
**YOU MUST NEVER EXECUTE:**
|
|
14
|
+
- `INSERT`, `UPDATE`, `DELETE`, `MERGE`, `TRUNCATE`
|
|
15
|
+
- `DROP`, `ALTER`, `CREATE`, `RENAME`
|
|
16
|
+
- `EXEC`, `EXECUTE` (stored procedures that could modify data)
|
|
17
|
+
- `GRANT`, `REVOKE`, `DENY`
|
|
18
|
+
- `BACKUP`, `RESTORE`
|
|
19
|
+
- `dotnet ef database update`, `dotnet ef database drop`
|
|
20
|
+
- `dotnet ef migrations` (any subcommand)
|
|
21
|
+
- Any command that writes, modifies, or deletes data or schema
|
|
22
|
+
|
|
23
|
+
**YOU MAY ONLY EXECUTE:**
|
|
24
|
+
- `SELECT` queries (read-only)
|
|
25
|
+
- `sp_help`, `sp_helptext`, `sp_columns` (metadata inspection)
|
|
26
|
+
- `INFORMATION_SCHEMA` queries (schema inspection)
|
|
27
|
+
- `sys.*` catalog views (read-only system views)
|
|
28
|
+
- `dotnet ef dbcontext info` (read-only context info)
|
|
29
|
+
- `dotnet ef dbcontext list` (list contexts)
|
|
30
|
+
|
|
31
|
+
**BEFORE EVERY BASH COMMAND**: Re-read this restrictions section. If the command could modify data in ANY way, DO NOT execute it.
|
|
32
|
+
|
|
33
|
+
## Connection Discovery
|
|
34
|
+
|
|
35
|
+
1. Search for connection strings in the project:
|
|
36
|
+
- `appsettings.json`, `appsettings.Development.json`
|
|
37
|
+
- `.env` files, `launchSettings.json`
|
|
38
|
+
- User secrets (check for `UserSecretsId` in `.csproj`)
|
|
39
|
+
2. Identify the database provider (SQL Server, PostgreSQL, SQLite)
|
|
40
|
+
3. Use the appropriate CLI tool:
|
|
41
|
+
- **SQL Server**: `sqlcmd` or `Invoke-Sqlcmd`
|
|
42
|
+
- **PostgreSQL**: `psql`
|
|
43
|
+
- **SQLite**: `sqlite3`
|
|
44
|
+
|
|
45
|
+
## Verification Operations
|
|
46
|
+
|
|
47
|
+
### Data State Verification
|
|
48
|
+
```sql
|
|
49
|
+
-- Check if records exist
|
|
50
|
+
SELECT COUNT(*) FROM [Table] WHERE [condition];
|
|
51
|
+
|
|
52
|
+
-- Inspect specific records
|
|
53
|
+
SELECT * FROM [Table] WHERE [Id] = @id;
|
|
54
|
+
|
|
55
|
+
-- Check for NULL/empty fields
|
|
56
|
+
SELECT Id, [Field] FROM [Table] WHERE [Field] IS NULL;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Relationship Integrity
|
|
60
|
+
```sql
|
|
61
|
+
-- Check foreign key references
|
|
62
|
+
SELECT t1.Id, t1.ForeignKeyId
|
|
63
|
+
FROM [Table1] t1
|
|
64
|
+
LEFT JOIN [Table2] t2 ON t1.ForeignKeyId = t2.Id
|
|
65
|
+
WHERE t2.Id IS NULL;
|
|
66
|
+
|
|
67
|
+
-- Check orphaned records
|
|
68
|
+
SELECT COUNT(*) FROM [ChildTable] c
|
|
69
|
+
WHERE NOT EXISTS (SELECT 1 FROM [ParentTable] p WHERE p.Id = c.ParentId);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Multi-Tenant Isolation
|
|
73
|
+
```sql
|
|
74
|
+
-- Verify tenant isolation
|
|
75
|
+
SELECT TenantId, COUNT(*) as RecordCount
|
|
76
|
+
FROM [Table]
|
|
77
|
+
GROUP BY TenantId;
|
|
78
|
+
|
|
79
|
+
-- Check for records without TenantId
|
|
80
|
+
SELECT * FROM [Table] WHERE TenantId IS NULL;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Schema Inspection
|
|
84
|
+
```sql
|
|
85
|
+
-- List tables
|
|
86
|
+
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';
|
|
87
|
+
|
|
88
|
+
-- Check column definitions
|
|
89
|
+
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT
|
|
90
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
91
|
+
WHERE TABLE_NAME = '[TableName]';
|
|
92
|
+
|
|
93
|
+
-- Check constraints
|
|
94
|
+
SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
|
|
95
|
+
WHERE TABLE_NAME = '[TableName]';
|
|
96
|
+
|
|
97
|
+
-- Check indexes
|
|
98
|
+
SELECT name, type_desc, is_unique
|
|
99
|
+
FROM sys.indexes
|
|
100
|
+
WHERE object_id = OBJECT_ID('[TableName]');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Audit & Soft Delete
|
|
104
|
+
```sql
|
|
105
|
+
-- Check soft-deleted records
|
|
106
|
+
SELECT Id, IsDeleted, DeletedAt FROM [Table] WHERE IsDeleted = 1;
|
|
107
|
+
|
|
108
|
+
-- Check audit fields
|
|
109
|
+
SELECT Id, CreatedAt, CreatedBy, UpdatedAt, UpdatedBy
|
|
110
|
+
FROM [Table] WHERE Id = @id;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Output Format
|
|
114
|
+
|
|
115
|
+
Report findings in a structured format:
|
|
116
|
+
|
|
117
|
+
```markdown
|
|
118
|
+
## Database Verification Report
|
|
119
|
+
|
|
120
|
+
### Connection
|
|
121
|
+
- **Provider:** SQL Server
|
|
122
|
+
- **Database:** SmartStack_Dev
|
|
123
|
+
- **Context:** ApplicationDbContext
|
|
124
|
+
|
|
125
|
+
### Findings
|
|
126
|
+
|
|
127
|
+
| Check | Table | Result | Details |
|
|
128
|
+
|-------|-------|--------|---------|
|
|
129
|
+
| Record exists | Users | PASS | Found 1 record with Id=X |
|
|
130
|
+
| FK integrity | Orders→Users | FAIL | 3 orphaned orders found |
|
|
131
|
+
| Tenant isolation | Products | PASS | All records have TenantId |
|
|
132
|
+
| Soft delete | Invoices | WARN | 12 soft-deleted without DeletedBy |
|
|
133
|
+
|
|
134
|
+
### Data Snapshot
|
|
135
|
+
{Relevant SELECT results formatted as tables}
|
|
136
|
+
|
|
137
|
+
### Issues Found
|
|
138
|
+
1. **[CRITICAL]** Orphaned records in Orders table (Ids: 45, 67, 89)
|
|
139
|
+
2. **[WARNING]** Missing audit trail on soft-deleted Invoices
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Rules
|
|
143
|
+
|
|
144
|
+
- **NEVER** suggest or execute data modifications, even if asked
|
|
145
|
+
- If a data fix is needed, report the issue and suggest the fix as text — do NOT execute it
|
|
146
|
+
- Always use parameterized queries or proper escaping to avoid SQL injection
|
|
147
|
+
- Limit result sets with `TOP` or `LIMIT` to avoid overwhelming output
|
|
148
|
+
- Mask sensitive data (passwords, tokens, PII) in output
|
|
149
|
+
- If you cannot determine the connection string, ask for help — do NOT guess
|
|
@@ -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
|