@polymorphism-tech/morph-spec 2.3.0 → 3.0.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 (166) hide show
  1. package/CLAUDE.md +446 -1730
  2. package/README.md +515 -516
  3. package/bin/morph-spec.js +366 -294
  4. package/bin/task-manager.js +429 -368
  5. package/bin/validate.js +369 -268
  6. package/content/.claude/commands/morph-apply.md +221 -158
  7. package/content/.claude/commands/morph-deploy.md +529 -0
  8. package/content/.claude/commands/morph-preflight.md +227 -0
  9. package/content/.claude/commands/morph-proposal.md +122 -101
  10. package/content/.claude/commands/morph-status.md +86 -86
  11. package/content/.claude/commands/morph-troubleshoot.md +122 -0
  12. package/content/.claude/skills/infra/azure-deploy-specialist.md +699 -0
  13. package/content/.claude/skills/level-0-meta/README.md +7 -0
  14. package/content/.claude/skills/level-0-meta/code-review.md +226 -0
  15. package/content/.claude/skills/level-0-meta/morph-checklist.md +117 -0
  16. package/content/.claude/skills/level-0-meta/simulation-checklist.md +77 -0
  17. package/content/.claude/skills/level-1-workflows/README.md +7 -0
  18. package/content/.claude/skills/level-1-workflows/morph-replicate.md +213 -0
  19. package/content/.claude/{commands/morph-clarify.md → skills/level-1-workflows/phase-clarify.md} +131 -184
  20. package/content/.claude/{commands/morph-design.md → skills/level-1-workflows/phase-design.md} +213 -275
  21. package/content/.claude/skills/level-1-workflows/phase-setup.md +106 -0
  22. package/content/.claude/skills/level-1-workflows/phase-tasks.md +164 -0
  23. package/content/.claude/{commands/morph-uiux.md → skills/level-1-workflows/phase-uiux.md} +169 -211
  24. package/content/.claude/skills/level-2-domains/README.md +14 -0
  25. package/content/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +192 -0
  26. package/content/.claude/skills/{specialists → level-2-domains/architecture}/po-pm-advisor.md +197 -197
  27. package/content/.claude/skills/level-2-domains/architecture/standards-architect.md +156 -0
  28. package/content/.claude/skills/level-2-domains/backend/dotnet-senior.md +287 -0
  29. package/content/.claude/skills/level-2-domains/backend/ef-modeler.md +113 -0
  30. package/content/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +126 -0
  31. package/content/.claude/skills/level-2-domains/backend/ms-agent-expert.md +109 -0
  32. package/content/.claude/skills/level-2-domains/frontend/blazor-builder.md +210 -0
  33. package/content/.claude/skills/level-2-domains/frontend/nextjs-expert.md +154 -0
  34. package/content/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +191 -0
  35. package/content/.claude/skills/{specialists → level-2-domains/infrastructure}/azure-architect.md +142 -142
  36. package/content/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +126 -0
  37. package/content/.claude/skills/level-2-domains/infrastructure/container-specialist.md +131 -0
  38. package/content/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +119 -0
  39. package/content/.claude/skills/level-2-domains/integrations/asaas-financial.md +130 -0
  40. package/content/.claude/skills/level-2-domains/integrations/azure-identity.md +142 -0
  41. package/content/.claude/skills/level-2-domains/integrations/clerk-auth.md +108 -0
  42. package/content/.claude/skills/level-2-domains/integrations/resend-email.md +119 -0
  43. package/content/.claude/skills/level-2-domains/quality/code-analyzer.md +235 -0
  44. package/content/.claude/skills/level-2-domains/quality/testing-specialist.md +126 -0
  45. package/content/.claude/skills/level-3-technologies/README.md +7 -0
  46. package/content/.claude/skills/level-4-patterns/README.md +7 -0
  47. package/content/.claude/skills/specialists/prompt-engineer.md +189 -0
  48. package/content/.claude/skills/specialists/seo-growth-hacker.md +320 -0
  49. package/content/.morph/config/agents.json +762 -242
  50. package/content/.morph/config/config.template.json +122 -108
  51. package/content/.morph/docs/workflows/design-impl.md +37 -0
  52. package/content/.morph/docs/workflows/enforcement-pipeline.md +668 -0
  53. package/content/.morph/docs/workflows/fast-track.md +29 -0
  54. package/content/.morph/docs/workflows/full-morph.md +76 -0
  55. package/content/.morph/docs/workflows/standard.md +44 -0
  56. package/content/.morph/docs/workflows/ui-refresh.md +39 -0
  57. package/content/.morph/examples/scheduled-reports/decisions.md +158 -0
  58. package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
  59. package/content/.morph/examples/scheduled-reports/spec.md +267 -0
  60. package/content/.morph/hooks/README.md +348 -239
  61. package/content/.morph/hooks/pre-commit-agents.sh +24 -24
  62. package/content/.morph/hooks/task-completed.js +73 -0
  63. package/content/.morph/hooks/teammate-idle.js +68 -0
  64. package/content/.morph/schemas/tasks.schema.json +220 -0
  65. package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
  66. package/content/.morph/standards/agent-framework-production.md +410 -0
  67. package/content/.morph/standards/agent-framework-setup.md +413 -453
  68. package/content/.morph/standards/agent-framework-workflows.md +349 -0
  69. package/content/.morph/standards/agent-teams-workflow.md +474 -0
  70. package/content/.morph/standards/architecture.md +325 -325
  71. package/content/.morph/standards/azure.md +605 -379
  72. package/content/.morph/standards/dotnet10-migration.md +520 -494
  73. package/content/.morph/templates/CONTEXT-FEATURE.md +276 -0
  74. package/content/.morph/templates/CONTEXT.md +170 -0
  75. package/content/.morph/templates/agent.cs +163 -172
  76. package/content/.morph/templates/clarify-questions.md +159 -0
  77. package/content/.morph/templates/contracts/Commands.cs +74 -0
  78. package/content/.morph/templates/contracts/Entities.cs +25 -0
  79. package/content/.morph/templates/contracts/Queries.cs +74 -0
  80. package/content/.morph/templates/contracts/README.md +74 -0
  81. package/content/.morph/templates/decisions.md +123 -106
  82. package/content/.morph/templates/infra/azure-pipelines-deploy.yml +480 -0
  83. package/content/.morph/templates/infra/deploy-checklist.md +426 -0
  84. package/content/.morph/templates/proposal.md +141 -155
  85. package/content/.morph/templates/recap.md +94 -105
  86. package/content/.morph/templates/simulation.md +353 -0
  87. package/content/.morph/templates/spec.md +149 -148
  88. package/content/.morph/templates/state.template.json +222 -222
  89. package/content/.morph/templates/tasks.md +257 -235
  90. package/content/.morph/templates/ui-components.md +362 -276
  91. package/content/CLAUDE.md +150 -442
  92. package/detectors/structure-detector.js +245 -250
  93. package/docs/README.md +144 -149
  94. package/docs/getting-started.md +301 -302
  95. package/docs/installation.md +361 -361
  96. package/docs/validation-checklist.md +265 -266
  97. package/package.json +80 -80
  98. package/src/commands/advance-phase.js +266 -0
  99. package/src/commands/analyze-blazor-concurrency.js +193 -0
  100. package/src/commands/deploy.js +780 -0
  101. package/src/commands/detect-agents.js +167 -0
  102. package/src/commands/doctor.js +356 -280
  103. package/src/commands/generate-context.js +40 -0
  104. package/src/commands/init.js +258 -245
  105. package/src/commands/lint-fluent.js +352 -0
  106. package/src/commands/rollback-phase.js +185 -0
  107. package/src/commands/session-summary.js +291 -0
  108. package/src/commands/task.js +78 -75
  109. package/src/commands/troubleshoot.js +222 -0
  110. package/src/commands/update.js +192 -159
  111. package/src/commands/validate-blazor-state.js +210 -0
  112. package/src/commands/validate-blazor.js +156 -0
  113. package/src/commands/validate-css.js +84 -0
  114. package/src/commands/validate-phase.js +221 -0
  115. package/src/lib/blazor-concurrency-analyzer.js +288 -0
  116. package/src/lib/blazor-state-validator.js +291 -0
  117. package/src/lib/blazor-validator.js +374 -0
  118. package/src/lib/complexity-analyzer.js +441 -292
  119. package/src/lib/context-generator.js +513 -0
  120. package/src/lib/continuous-validator.js +421 -440
  121. package/src/lib/css-validator.js +352 -0
  122. package/src/lib/decision-constraint-loader.js +109 -0
  123. package/src/lib/design-system-detector.js +187 -0
  124. package/src/lib/design-system-scaffolder.js +299 -0
  125. package/src/lib/hook-executor.js +256 -0
  126. package/src/lib/recap-generator.js +205 -0
  127. package/src/lib/spec-validator.js +258 -0
  128. package/src/lib/standards-context-injector.js +287 -0
  129. package/src/lib/state-manager.js +397 -340
  130. package/src/lib/team-orchestrator.js +322 -0
  131. package/src/lib/troubleshoot-grep.js +194 -0
  132. package/src/lib/troubleshoot-index.js +144 -0
  133. package/src/lib/validation-runner.js +283 -0
  134. package/src/lib/validators/contract-compliance-validator.js +273 -0
  135. package/src/lib/validators/design-system-validator.js +231 -0
  136. package/src/utils/file-copier.js +187 -139
  137. package/content/.claude/commands/morph-costs.md +0 -206
  138. package/content/.claude/commands/morph-setup.md +0 -100
  139. package/content/.claude/commands/morph-tasks.md +0 -319
  140. package/content/.claude/skills/infra/bicep-architect.md +0 -419
  141. package/content/.claude/skills/infra/container-specialist.md +0 -437
  142. package/content/.claude/skills/infra/devops-engineer.md +0 -405
  143. package/content/.claude/skills/integrations/asaas-financial.md +0 -333
  144. package/content/.claude/skills/integrations/azure-identity.md +0 -309
  145. package/content/.claude/skills/integrations/clerk-auth.md +0 -290
  146. package/content/.claude/skills/specialists/ai-system-architect.md +0 -604
  147. package/content/.claude/skills/specialists/cost-guardian.md +0 -110
  148. package/content/.claude/skills/specialists/ef-modeler.md +0 -211
  149. package/content/.claude/skills/specialists/hangfire-orchestrator.md +0 -255
  150. package/content/.claude/skills/specialists/ms-agent-expert.md +0 -263
  151. package/content/.claude/skills/specialists/standards-architect.md +0 -78
  152. package/content/.claude/skills/specialists/ui-ux-designer.md +0 -1100
  153. package/content/.claude/skills/stacks/dotnet-blazor.md +0 -606
  154. package/content/.claude/skills/stacks/dotnet-nextjs.md +0 -402
  155. package/content/.claude/skills/stacks/shopify.md +0 -445
  156. package/content/.morph/config/azure-pricing.json +0 -70
  157. package/content/.morph/config/azure-pricing.schema.json +0 -50
  158. package/content/.morph/hooks/pre-commit-costs.sh +0 -91
  159. package/docs/api/cost-calculator.js.html +0 -513
  160. package/docs/api/design-system-generator.js.html +0 -382
  161. package/docs/api/global.html +0 -5263
  162. package/docs/api/index.html +0 -96
  163. package/docs/api/state-manager.js.html +0 -423
  164. package/src/commands/cost.js +0 -181
  165. package/src/commands/update-pricing.js +0 -206
  166. package/src/lib/cost-calculator.js +0 -429
@@ -0,0 +1,130 @@
1
+ # Asaas Financial
2
+
3
+ > **Layer:** 2 | **Load:** on-keyword | **Keywords:** asaas, payment, pix, boleto, cobranca, subscription, billing, pagamento
4
+
5
+ Integração com Asaas para pagamentos no Brasil. API REST, sem SDK .NET oficial. Suporta PIX, Boleto, Cartão.
6
+
7
+ ## Setup
8
+
9
+ ```csharp
10
+ // appsettings.json
11
+ { "Asaas": { "BaseUrl": "https://sandbox.asaas.com/api/v3", "ApiKey": "${ASAAS_API_KEY}" } }
12
+
13
+ // Program.cs
14
+ builder.Services.Configure<AsaasOptions>(builder.Configuration.GetSection("Asaas"));
15
+ builder.Services.AddHttpClient<IAsaasClient, AsaasClient>((sp, client) =>
16
+ {
17
+ var options = sp.GetRequiredService<IOptions<AsaasOptions>>().Value;
18
+ client.BaseAddress = new Uri(options.BaseUrl);
19
+ client.DefaultRequestHeaders.Add("access_token", options.ApiKey);
20
+ client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
21
+ });
22
+ ```
23
+
24
+ ## Client Interface
25
+
26
+ ```csharp
27
+ public interface IAsaasClient
28
+ {
29
+ Task<AsaasCustomer> CreateCustomerAsync(CreateCustomerRequest request);
30
+ Task<AsaasPayment> CreatePaymentAsync(CreatePaymentRequest request);
31
+ Task<AsaasPayment> GetPaymentAsync(string paymentId);
32
+ Task<AsaasSubscription> CreateSubscriptionAsync(CreateSubscriptionRequest request);
33
+ }
34
+ ```
35
+
36
+ Implementation: standard `HttpClient.PostAsJsonAsync` / `GetAsync` + `ReadFromJsonAsync` pattern. Log errors, throw `AsaasException` on failure.
37
+
38
+ ## DTOs (all use `[JsonPropertyName]`)
39
+
40
+ ```csharp
41
+ public record CreateCustomerRequest
42
+ {
43
+ [JsonPropertyName("name")] public required string Name { get; init; }
44
+ [JsonPropertyName("cpfCnpj")] public required string CpfCnpj { get; init; } // REQUIRED even in sandbox
45
+ [JsonPropertyName("email")] public string? Email { get; init; }
46
+ }
47
+
48
+ public record CreatePaymentRequest
49
+ {
50
+ [JsonPropertyName("customer")] public required string Customer { get; init; }
51
+ [JsonPropertyName("billingType")] public required string BillingType { get; init; } // BOLETO, PIX, CREDIT_CARD
52
+ [JsonPropertyName("value")] public required decimal Value { get; init; }
53
+ [JsonPropertyName("dueDate")] public required string DueDate { get; init; } // yyyy-MM-dd
54
+ [JsonPropertyName("description")] public string? Description { get; init; }
55
+ [JsonPropertyName("externalReference")] public string? ExternalReference { get; init; }
56
+ }
57
+
58
+ public record AsaasPayment
59
+ {
60
+ [JsonPropertyName("id")] public string Id { get; init; } = "";
61
+ [JsonPropertyName("status")] public string Status { get; init; } = ""; // PENDING, RECEIVED, CONFIRMED, OVERDUE
62
+ [JsonPropertyName("invoiceUrl")] public string? InvoiceUrl { get; init; }
63
+ [JsonPropertyName("bankSlipUrl")] public string? BankSlipUrl { get; init; }
64
+ [JsonPropertyName("pixQrCode")] public AsaasPixQrCode? PixQrCode { get; init; }
65
+ }
66
+
67
+ public record AsaasPixQrCode
68
+ {
69
+ [JsonPropertyName("encodedImage")] public string? EncodedImage { get; init; } // Base64
70
+ [JsonPropertyName("payload")] public string? Payload { get; init; } // PIX copia-e-cola
71
+ }
72
+ ```
73
+
74
+ Follow same pattern for `CreateSubscriptionRequest` (add `cycle`: MONTHLY/WEEKLY/YEARLY, `nextDueDate`).
75
+
76
+ ## Webhooks
77
+
78
+ ```csharp
79
+ [ApiController, Route("api/webhooks/asaas")]
80
+ public class AsaasWebhookController(IPaymentService payments, ILogger<AsaasWebhookController> logger) : ControllerBase
81
+ {
82
+ [HttpPost]
83
+ public async Task<IActionResult> Handle([FromBody] AsaasWebhookPayload payload)
84
+ {
85
+ logger.LogInformation("Asaas webhook: {Event} payment {Id}", payload.Event, payload.Payment?.Id);
86
+ switch (payload.Event)
87
+ {
88
+ case "PAYMENT_CONFIRMED": case "PAYMENT_RECEIVED":
89
+ await payments.ConfirmPaymentAsync(payload.Payment!.Id); break;
90
+ case "PAYMENT_OVERDUE":
91
+ await payments.MarkOverdueAsync(payload.Payment!.Id); break;
92
+ case "PAYMENT_REFUNDED":
93
+ await payments.RefundPaymentAsync(payload.Payment!.Id); break;
94
+ }
95
+ return Ok();
96
+ }
97
+ }
98
+ ```
99
+
100
+ ## Environments
101
+
102
+ | Environment | Base URL |
103
+ |-------------|----------|
104
+ | Sandbox | `https://sandbox.asaas.com/api/v3` |
105
+ | Production | `https://www.asaas.com/api/v3` |
106
+
107
+ ## Gotchas
108
+
109
+ | Issue | Fix |
110
+ |-------|-----|
111
+ | PIX QR Code returns Base64, NOT URL | `$"data:image/png;base64,{response.EncodedImage}"` |
112
+ | CPF required even in sandbox | Always include `cpfCnpj` in CreateCustomer |
113
+ | Missing `User-Agent` header | Add to HttpClient defaults |
114
+ | Date format must be `yyyy-MM-dd` | `DateTime.Today.AddDays(1).ToString("yyyy-MM-dd")` |
115
+
116
+ ## Checklist
117
+
118
+ - [ ] API Key configured (not hardcoded)
119
+ - [ ] HttpClient with retry policy (Polly)
120
+ - [ ] Headers: `access_token` + `User-Agent`
121
+ - [ ] CPF/CNPJ always included for customers
122
+ - [ ] PIX QR Code → data URI conversion
123
+ - [ ] Dates in `yyyy-MM-dd` format
124
+ - [ ] Webhook endpoint + signature validation
125
+ - [ ] ExternalReference for reconciliation
126
+ - [ ] Tests with sandbox
127
+
128
+ ---
129
+
130
+ *MORPH-SPEC by Polymorphism Tech*
@@ -0,0 +1,142 @@
1
+ # Azure Identity (Microsoft Identity)
2
+
3
+ > **Layer:** 2 | **Load:** on-keyword | **Keywords:** identity, entra, azure ad, microsoft auth, msal, oauth, oidc, microsoft identity
4
+
5
+ Microsoft Identity Platform for .NET/Blazor. SDK: `Microsoft.Identity.Web`.
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ dotnet add package Microsoft.Identity.Web
11
+ dotnet add package Microsoft.Identity.Web.UI # For Blazor
12
+ ```
13
+
14
+ ```json
15
+ // appsettings.json
16
+ { "AzureAd": {
17
+ "Instance": "https://login.microsoftonline.com/",
18
+ "Domain": "yourdomain.onmicrosoft.com",
19
+ "TenantId": "your-tenant-id",
20
+ "ClientId": "your-client-id",
21
+ "ClientSecret": "${AZURE_AD_CLIENT_SECRET}",
22
+ "CallbackPath": "/signin-oidc"
23
+ } }
24
+ ```
25
+
26
+ ```csharp
27
+ // Program.cs
28
+ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
29
+ .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
30
+ builder.Services.AddControllersWithViews().AddMicrosoftIdentityUI();
31
+ builder.Services.AddAuthorization();
32
+ app.UseAuthentication();
33
+ app.UseAuthorization();
34
+ ```
35
+
36
+ ## Blazor Server
37
+
38
+ ```csharp
39
+ // Additional setup
40
+ builder.Services.AddServerSideBlazor().AddMicrosoftIdentityConsentHandler();
41
+
42
+ // App.razor
43
+ <CascadingAuthenticationState>
44
+ <Router AppAssembly="@typeof(App).Assembly">
45
+ <Found Context="routeData">
46
+ <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
47
+ <NotAuthorized>
48
+ @if (!context.User.Identity?.IsAuthenticated ?? true) { <RedirectToLogin /> }
49
+ else { <p>No permission.</p> }
50
+ </NotAuthorized>
51
+ </AuthorizeRouteView>
52
+ </Found>
53
+ </Router>
54
+ </CascadingAuthenticationState>
55
+
56
+ // RedirectToLogin.razor
57
+ @inject NavigationManager Nav
58
+ @code {
59
+ protected override void OnInitialized() =>
60
+ Nav.NavigateTo($"MicrosoftIdentity/Account/SignIn?redirectUri={Uri.EscapeDataString(Nav.Uri)}", forceLoad: true);
61
+ }
62
+ ```
63
+
64
+ ## Protected Pages
65
+
66
+ ```razor
67
+ @page "/secure"
68
+ @attribute [Authorize]
69
+
70
+ <AuthorizeView>
71
+ <Authorized>Welcome, @context.User.Identity?.Name!</Authorized>
72
+ </AuthorizeView>
73
+
74
+ <AuthorizeView Roles="Admin"><Authorized><AdminPanel /></Authorized></AuthorizeView>
75
+ ```
76
+
77
+ ## API Protection
78
+
79
+ ```csharp
80
+ // API Program.cs
81
+ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
82
+ .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
83
+
84
+ // Controller
85
+ [ApiController, Route("api/[controller]"), Authorize]
86
+ public class ProfileController : ControllerBase
87
+ {
88
+ [HttpGet]
89
+ public IActionResult GetProfile() => Ok(new {
90
+ UserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value,
91
+ Name = User.Identity?.Name
92
+ });
93
+
94
+ [HttpGet("admin"), Authorize(Roles = "Admin")]
95
+ public IActionResult AdminOnly() => Ok("Admin access");
96
+ }
97
+ ```
98
+
99
+ ## Downstream APIs (Microsoft Graph)
100
+
101
+ ```csharp
102
+ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
103
+ .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
104
+ .EnableTokenAcquisitionToCallDownstreamApi()
105
+ .AddMicrosoftGraph(builder.Configuration.GetSection("Graph"))
106
+ .AddInMemoryTokenCaches();
107
+ ```
108
+
109
+ ## Authorization Policies
110
+
111
+ ```csharp
112
+ builder.Services.AddAuthorization(o => {
113
+ o.AddPolicy("RequireAdmin", p => p.RequireRole("Admin"));
114
+ o.AddPolicy("RequireManager", p => p.RequireAssertion(c =>
115
+ c.User.IsInRole("Admin") || c.User.IsInRole("Manager")));
116
+ });
117
+ ```
118
+
119
+ ## Multi-tenant & B2C
120
+
121
+ ```json
122
+ // Multi-tenant: TenantId = "common" or "organizations"
123
+ // Then validate allowed tenants in TokenValidationParameters.IssuerValidator
124
+
125
+ // B2C: Use "AzureAdB2C" section with SignUpSignInPolicyId, ResetPasswordPolicyId
126
+ ```
127
+
128
+ ## Checklist
129
+
130
+ - [ ] App registered in Azure Portal
131
+ - [ ] Client ID + Tenant ID configured
132
+ - [ ] Client Secret in Key Vault
133
+ - [ ] Redirect URIs configured
134
+ - [ ] API permissions defined
135
+ - [ ] Token caching configured
136
+ - [ ] Authorization policies created
137
+ - [ ] Logout flow implemented
138
+ - [ ] Token expiry error handling
139
+
140
+ ---
141
+
142
+ *MORPH-SPEC by Polymorphism Tech*
@@ -0,0 +1,108 @@
1
+ # Clerk Auth
2
+
3
+ > **Layer:** 2 | **Load:** on-keyword | **Keywords:** clerk, auth, login, signup, authentication, session, jwt, user
4
+
5
+ Autenticação SaaS com Clerk para .NET/Blazor. SDK: `Clerk.Net.AspNetCore.Security`.
6
+
7
+ ## Setup
8
+
9
+ ```csharp
10
+ // appsettings.json
11
+ { "Clerk": { "SecretKey": "${CLERK_SECRET_KEY}", "PublishableKey": "pk_test_xxx" } }
12
+
13
+ // Program.cs
14
+ builder.Services.AddClerk(builder.Configuration);
15
+ builder.Services.AddAuthentication(ClerkAuthenticationDefaults.AuthenticationScheme)
16
+ .AddClerk(o => { o.Authority = "https://clerk.{instance}.com"; o.ValidAudiences = ["your-app-id"]; });
17
+ builder.Services.AddAuthorization();
18
+ app.UseAuthentication();
19
+ app.UseAuthorization();
20
+ ```
21
+
22
+ ## Protected Endpoints
23
+
24
+ ```csharp
25
+ // Minimal API
26
+ app.MapGet("/api/profile", async (ClaimsPrincipal user, IClerkClient clerk) =>
27
+ {
28
+ var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
29
+ if (userId is null) return Results.Unauthorized();
30
+ var u = await clerk.Users.GetUserAsync(userId);
31
+ return Results.Ok(new { u.Id, Email = u.EmailAddresses.FirstOrDefault()?.EmailAddress });
32
+ }).RequireAuthorization();
33
+
34
+ // Controller: same pattern with [Authorize] + User.FindFirstValue()
35
+ // Role-based: [Authorize(Roles = "admin")]
36
+ ```
37
+
38
+ ## Blazor Server
39
+
40
+ > **Ref:** Same `CascadingAuthenticationState` + `AuthorizeRouteView` pattern as `azure-identity.md`
41
+
42
+ ```razor
43
+ @* RedirectToLogin.razor *@
44
+ @inject NavigationManager Nav
45
+ @code {
46
+ protected override void OnInitialized() =>
47
+ Nav.NavigateTo($"/sign-in?redirect_url={Uri.EscapeDataString(Nav.Uri)}", forceLoad: true);
48
+ }
49
+
50
+ @* Protected page *@
51
+ @page "/dashboard"
52
+ @attribute [Authorize]
53
+ <AuthorizeView><Authorized>Welcome, @context.User.Identity?.Name!</Authorized></AuthorizeView>
54
+ <AuthorizeView Roles="admin"><Authorized><AdminPanel /></Authorized></AuthorizeView>
55
+ ```
56
+
57
+ ## Client Operations
58
+
59
+ ```csharp
60
+ var user = await _clerk.Users.GetUserAsync(userId); // Get
61
+ var users = await _clerk.Users.GetUserListAsync(new() { Limit = 10 }); // List
62
+ await _clerk.Users.UpdateUserMetadataAsync(userId, new() // Update metadata
63
+ { PublicMetadata = new Dictionary<string, object> { ["plan"] = "pro" } });
64
+ await _clerk.Users.DeleteUserAsync(userId); // Delete
65
+ ```
66
+
67
+ ## Webhooks
68
+
69
+ ```csharp
70
+ [ApiController, Route("api/webhooks/clerk")]
71
+ public class ClerkWebhookController(IUserService users) : ControllerBase
72
+ {
73
+ [HttpPost]
74
+ public async Task<IActionResult> Handle([FromBody] ClerkWebhookPayload payload)
75
+ {
76
+ switch (payload.Type)
77
+ {
78
+ case "user.created": await users.SyncUserAsync(payload.Data); break;
79
+ case "user.updated": await users.UpdateUserAsync(payload.Data); break;
80
+ case "user.deleted": await users.DeleteUserAsync(payload.Data.Id); break;
81
+ }
82
+ return Ok();
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## Clerk vs Azure Identity
88
+
89
+ | Aspecto | Clerk | Azure Identity |
90
+ |---------|-------|----------------|
91
+ | Setup | Faster | More complex |
92
+ | Cost | Freemium (5k MAU free) | Free (Azure AD) |
93
+ | Social login | 20+ providers | Limited |
94
+ | UI components | Pre-built | Build your own |
95
+ | Best for | SaaS B2C, MVPs | Enterprise/Azure |
96
+
97
+ ## Checklist
98
+
99
+ - [ ] Secret Key in Key Vault (not hardcoded)
100
+ - [ ] Authentication scheme configured
101
+ - [ ] Authorization policies defined
102
+ - [ ] Webhook endpoint + signature validation
103
+ - [ ] Redirect after login configured
104
+ - [ ] Error handling for expired sessions
105
+
106
+ ---
107
+
108
+ *MORPH-SPEC by Polymorphism Tech*
@@ -0,0 +1,119 @@
1
+ # Resend Email
2
+
3
+ > **Layer:** 2 | **Load:** on-keyword | **Keywords:** resend, email, envio, transactional, notification, send email, template
4
+
5
+ Transactional email via Resend for .NET. REST API, no official SDK. Simple, developer-friendly.
6
+
7
+ ## Setup
8
+
9
+ ```csharp
10
+ // appsettings.json
11
+ { "Resend": { "BaseUrl": "https://api.resend.com", "ApiKey": "${RESEND_API_KEY}", "FromEmail": "noreply@yourdomain.com" } }
12
+
13
+ // Program.cs
14
+ builder.Services.Configure<ResendOptions>(builder.Configuration.GetSection("Resend"));
15
+ builder.Services.AddHttpClient<IResendClient, ResendClient>((sp, client) =>
16
+ {
17
+ var options = sp.GetRequiredService<IOptions<ResendOptions>>().Value;
18
+ client.BaseAddress = new Uri(options.BaseUrl);
19
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", options.ApiKey);
20
+ });
21
+ ```
22
+
23
+ ## Client Interface
24
+
25
+ ```csharp
26
+ public interface IResendClient
27
+ {
28
+ Task<EmailResponse> SendAsync(SendEmailRequest request, CancellationToken ct = default);
29
+ Task<EmailResponse> SendBatchAsync(IEnumerable<SendEmailRequest> requests, CancellationToken ct = default);
30
+ Task<EmailDetails> GetEmailAsync(string emailId, CancellationToken ct = default);
31
+ }
32
+ ```
33
+
34
+ Implementation: `HttpClient.PostAsJsonAsync("/emails")` + `ReadFromJsonAsync<EmailResponse>`. Log errors, throw `ResendException` on failure.
35
+
36
+ ## DTOs
37
+
38
+ ```csharp
39
+ public record SendEmailRequest
40
+ {
41
+ [JsonPropertyName("from")] public required string From { get; init; }
42
+ [JsonPropertyName("to")] public required string[] To { get; init; }
43
+ [JsonPropertyName("subject")] public required string Subject { get; init; }
44
+ [JsonPropertyName("html")] public string? Html { get; init; }
45
+ [JsonPropertyName("text")] public string? Text { get; init; }
46
+ [JsonPropertyName("reply_to")] public string? ReplyTo { get; init; }
47
+ [JsonPropertyName("tags")] public Tag[]? Tags { get; init; }
48
+ }
49
+
50
+ public record EmailResponse
51
+ {
52
+ [JsonPropertyName("id")] public string Id { get; init; } = "";
53
+ }
54
+
55
+ public record Tag
56
+ {
57
+ [JsonPropertyName("name")] public required string Name { get; init; }
58
+ [JsonPropertyName("value")] public required string Value { get; init; }
59
+ }
60
+ ```
61
+
62
+ ## Webhooks
63
+
64
+ ```csharp
65
+ [ApiController, Route("api/webhooks/resend")]
66
+ public class ResendWebhookController(IEmailTrackingService tracking, ILogger<ResendWebhookController> logger) : ControllerBase
67
+ {
68
+ [HttpPost]
69
+ public async Task<IActionResult> Handle([FromBody] ResendWebhookPayload payload)
70
+ {
71
+ logger.LogInformation("Resend webhook: {Type} for {EmailId}", payload.Type, payload.Data?.EmailId);
72
+ switch (payload.Type)
73
+ {
74
+ case "email.delivered": await tracking.MarkDeliveredAsync(payload.Data!.EmailId); break;
75
+ case "email.bounced": await tracking.MarkBouncedAsync(payload.Data!.EmailId); break;
76
+ case "email.complained": await tracking.MarkComplainedAsync(payload.Data!.EmailId); break;
77
+ }
78
+ return Ok();
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Abstraction Pattern
84
+
85
+ ```csharp
86
+ // Use IEmailService abstraction over IResendClient for testability
87
+ public interface IEmailService
88
+ {
89
+ Task<string> SendTransactionalAsync(string to, string subject, string htmlBody, CancellationToken ct = default);
90
+ }
91
+
92
+ // ResendEmailService implements IEmailService using IResendClient
93
+ // FakeEmailClient implements IEmailService for simulation mode
94
+ ```
95
+
96
+ ## Gotchas
97
+
98
+ | Issue | Fix |
99
+ |-------|-----|
100
+ | Rate limit: 100 req/sec (free), 1000 (pro) | Queue emails via Hangfire for bulk sends |
101
+ | Domain verification required for production | Verify in Resend dashboard, add DNS records |
102
+ | `from` must match verified domain | Use `noreply@yourdomain.com` |
103
+ | HTML email rendering varies | Test with Litmus/Email on Acid, use MJML |
104
+ | Webhook signature validation | Verify `svix-signature` header in production |
105
+
106
+ ## Checklist
107
+
108
+ - [ ] API Key in Key Vault (not hardcoded)
109
+ - [ ] HttpClient with `Authorization: Bearer` header
110
+ - [ ] Domain verified in Resend dashboard
111
+ - [ ] `IEmailService` abstraction for testability
112
+ - [ ] Webhook endpoint + signature validation
113
+ - [ ] Rate limiting handled (queue for bulk)
114
+ - [ ] Tags used for tracking/analytics
115
+ - [ ] Simulation mode (`FakeEmailClient`) for dev
116
+
117
+ ---
118
+
119
+ *MORPH-SPEC by Polymorphism Tech*