aw-spec-cli 0.1.5 → 0.1.6

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 (205) hide show
  1. package/assets/.agents/agent.json +1 -1
  2. package/assets/.agents/skills/{aw-spec-orch → appweaver}/SKILL.md +10 -10
  3. package/assets/.agents/skills/{aw-spec-orch-demo → appweaver-demo}/SKILL.md +9 -9
  4. package/assets/.claude/agent.json +1 -1
  5. package/assets/.claude/skills/{aw-spec-orch → appweaver}/SKILL.md +10 -10
  6. package/assets/.claude/skills/{aw-spec-orch-demo → appweaver-demo}/SKILL.md +9 -9
  7. package/assets/.kilocode/agent.json +1 -1
  8. package/assets/.kilocode/workflows/{aw-spec-orch-demo.md → appweaver-demo.md} +9 -9
  9. package/assets/.kilocode/workflows/{aw-spec-orch.md → appweaver.md} +10 -10
  10. package/assets/docs/pharma-constitution.my-ref.md +86 -0
  11. package/dist/utils/paths.js +2 -2
  12. package/dist/utils/paths.js.map +1 -1
  13. package/package.json +1 -1
  14. package/assets/.aw-spec/DESIGN-FORMAT.md +0 -151
  15. package/assets/.aw-spec/ORCH-DIALECT.md +0 -156
  16. package/assets/.aw-spec/RULE-PARTITION.md +0 -126
  17. package/assets/.aw-spec/domain.json +0 -3
  18. package/assets/.aw-spec/extensions/.registry +0 -19
  19. package/assets/.aw-spec/extensions/agent-context/README.md +0 -57
  20. package/assets/.aw-spec/extensions/agent-context/agent-context-config.yml +0 -4
  21. package/assets/.aw-spec/extensions/agent-context/commands/aw-spec.agent-context.update.md +0 -26
  22. package/assets/.aw-spec/extensions/agent-context/extension.yml +0 -37
  23. package/assets/.aw-spec/extensions/agent-context/scripts/bash/update-agent-context.sh +0 -201
  24. package/assets/.aw-spec/extensions/agent-context/scripts/powershell/update-agent-context.ps1 +0 -237
  25. package/assets/.aw-spec/extensions.yml +0 -16
  26. package/assets/.aw-spec/feature.json +0 -3
  27. package/assets/.aw-spec/init-options.json +0 -9
  28. package/assets/.aw-spec/integration.json +0 -15
  29. package/assets/.aw-spec/integrations/aw-spec.manifest.json +0 -9
  30. package/assets/.aw-spec/interpreter/run-skill.py +0 -1158
  31. package/assets/.aw-spec/memory/constitution.md +0 -50
  32. package/assets/.aw-spec/orchestrator/AGENT-PROFILE.md +0 -94
  33. package/assets/.aw-spec/orchestrator/README.md +0 -300
  34. package/assets/.aw-spec/orchestrator/adapters/claude_agent_sdk.py +0 -74
  35. package/assets/.aw-spec/orchestrator/adapters/google_antigravity.py +0 -74
  36. package/assets/.aw-spec/orchestrator/modules/delta-design.orch.yaml +0 -105
  37. package/assets/.aw-spec/orchestrator/modules/delta-spec.orch.yaml +0 -79
  38. package/assets/.aw-spec/orchestrator/modules/domain-select.orch.yaml +0 -81
  39. package/assets/.aw-spec/orchestrator/modules/tech-select.orch.yaml +0 -75
  40. package/assets/.aw-spec/orchestrator/orchestrator.py +0 -1721
  41. package/assets/.aw-spec/orchestrator/rev-templates/ai-rules.reference.md +0 -40
  42. package/assets/.aw-spec/orchestrator/rev-templates/comandrulemap.skeleton.md +0 -59
  43. package/assets/.aw-spec/orchestrator/rev-templates/commandtechrulemap.skeleton.md +0 -97
  44. package/assets/.aw-spec/orchestrator/rev-templates/domain-constitution.skeleton.md +0 -61
  45. package/assets/.aw-spec/orchestrator/rev-templates/domain-requirements.skeleton.md +0 -48
  46. package/assets/.aw-spec/orchestrator/rev-templates/domain-rule-file.skeleton.md +0 -39
  47. package/assets/.aw-spec/orchestrator/rev-templates/technical-constitution.skeleton.md +0 -106
  48. package/assets/.aw-spec/orchestrator/rev-templates/technical-rule-file.skeleton.md +0 -54
  49. package/assets/.aw-spec/orchestrator/workflows/_smoke.orch.yaml +0 -160
  50. package/assets/.aw-spec/orchestrator/workflows/aw-spec-add-component.orch.yaml +0 -244
  51. package/assets/.aw-spec/orchestrator/workflows/aw-spec-adopt.orch.yaml +0 -775
  52. package/assets/.aw-spec/orchestrator/workflows/aw-spec-analyze-delta-design.orch.yaml +0 -248
  53. package/assets/.aw-spec/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +0 -261
  54. package/assets/.aw-spec/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +0 -425
  55. package/assets/.aw-spec/orchestrator/workflows/aw-spec-build.orch.yaml +0 -188
  56. package/assets/.aw-spec/orchestrator/workflows/aw-spec-characterize.orch.yaml +0 -474
  57. package/assets/.aw-spec/orchestrator/workflows/aw-spec-close-delta.orch.yaml +0 -181
  58. package/assets/.aw-spec/orchestrator/workflows/aw-spec-constitution.orch.yaml +0 -284
  59. package/assets/.aw-spec/orchestrator/workflows/aw-spec-demo.orch.yaml +0 -103
  60. package/assets/.aw-spec/orchestrator/workflows/aw-spec-dependency-tree.orch.yaml +0 -69
  61. package/assets/.aw-spec/orchestrator/workflows/aw-spec-deploy.orch.yaml +0 -85
  62. package/assets/.aw-spec/orchestrator/workflows/aw-spec-design.orch.yaml +0 -382
  63. package/assets/.aw-spec/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +0 -382
  64. package/assets/.aw-spec/orchestrator/workflows/aw-spec-foundation.orch.yaml +0 -246
  65. package/assets/.aw-spec/orchestrator/workflows/aw-spec-implement-delta.orch.yaml +0 -231
  66. package/assets/.aw-spec/orchestrator/workflows/aw-spec-implement.orch.yaml +0 -258
  67. package/assets/.aw-spec/orchestrator/workflows/aw-spec-inventory.orch.yaml +0 -236
  68. package/assets/.aw-spec/orchestrator/workflows/aw-spec-onboard.orch.yaml +0 -949
  69. package/assets/.aw-spec/orchestrator/workflows/aw-spec-parity.orch.yaml +0 -293
  70. package/assets/.aw-spec/orchestrator/workflows/aw-spec-run.orch.yaml +0 -177
  71. package/assets/.aw-spec/orchestrator/workflows/aw-spec-select-domain.orch.yaml +0 -79
  72. package/assets/.aw-spec/orchestrator/workflows/aw-spec-select-feature.orch.yaml +0 -128
  73. package/assets/.aw-spec/orchestrator/workflows/aw-spec-specify.orch.yaml +0 -412
  74. package/assets/.aw-spec/scripts/bash/aw-inventory.sh +0 -14
  75. package/assets/.aw-spec/scripts/bash/aw-parity.sh +0 -13
  76. package/assets/.aw-spec/scripts/bash/aw-rev-constitution.sh +0 -13
  77. package/assets/.aw-spec/scripts/bash/aw-spec-actions.sh +0 -1899
  78. package/assets/.aw-spec/scripts/bash/aw-xref.sh +0 -15
  79. package/assets/.aw-spec/scripts/bash/common.sh +0 -533
  80. package/assets/.aw-spec/scripts/powershell/aw-inventory.ps1 +0 -14
  81. package/assets/.aw-spec/scripts/powershell/aw-parity.ps1 +0 -13
  82. package/assets/.aw-spec/scripts/powershell/aw-rev-constitution.ps1 +0 -13
  83. package/assets/.aw-spec/scripts/powershell/aw-spec-actions.ps1 +0 -1563
  84. package/assets/.aw-spec/scripts/powershell/aw-xref.ps1 +0 -15
  85. package/assets/.aw-spec/scripts/python/aw-inventory.py +0 -683
  86. package/assets/.aw-spec/scripts/python/aw-parity.py +0 -380
  87. package/assets/.aw-spec/scripts/python/aw-rev-constitution.py +0 -312
  88. package/assets/.aw-spec/scripts/python/aw-xref.py +0 -1330
  89. package/assets/.aw-spec/scripts/python/validate_okf.py +0 -349
  90. package/assets/.aw-spec/templates/constitution-domain-template.md +0 -75
  91. package/assets/.aw-spec/templates/constitution-technical-template.md +0 -70
  92. package/assets/.aw-spec/templates/delta-design-template.md +0 -69
  93. package/assets/.aw-spec/templates/delta-spec-template.md +0 -60
  94. package/assets/.aw-spec/templates/design-template.md +0 -383
  95. package/assets/.aw-spec/templates/okf-concept-template.md +0 -103
  96. package/assets/.aw-spec/templates/parity-manifest-template.json +0 -49
  97. package/assets/.aw-spec/templates/source-map-template.md +0 -85
  98. package/assets/.aw-spec/templates/spec-template.md +0 -252
  99. package/assets/docs/ModularMonolith/ModularMonolith.Reference.pack.txt +0 -25248
  100. package/assets/docs/ModularMonolith/ModularMonolith.pack.txt +0 -5726
  101. package/assets/docs/ModularMonolith/pack-foundation.ps1 +0 -145
  102. package/assets/docs/ModularMonolith/pack-foundation.sh +0 -196
  103. package/assets/docs/ModularMonolith/pack-reference-foundation.ps1 +0 -157
  104. package/assets/docs/ModularMonolith/pack-reference-foundation.sh +0 -209
  105. package/assets/docs/ModularMonolith/unpack-foundation.ps1 +0 -126
  106. package/assets/docs/ModularMonolith/unpack-foundation.sh +0 -217
  107. package/assets/docs/ModularMonolith/unpack-reference-foundation.ps1 +0 -129
  108. package/assets/docs/ModularMonolith/unpack-reference-foundation.sh +0 -218
  109. package/assets/docs/README.md +0 -106
  110. package/assets/docs/VerticalSlice/PACKAGING.md +0 -185
  111. package/assets/docs/VerticalSlice/VerticalSlice.Reference.pack.txt +0 -19318
  112. package/assets/docs/VerticalSlice/VerticalSlice.pack.txt +0 -2833
  113. package/assets/docs/VerticalSlice/pack-foundation.ps1 +0 -145
  114. package/assets/docs/VerticalSlice/pack-foundation.sh +0 -196
  115. package/assets/docs/VerticalSlice/pack-reference-foundation.ps1 +0 -157
  116. package/assets/docs/VerticalSlice/pack-reference-foundation.sh +0 -209
  117. package/assets/docs/VerticalSlice/unpack-foundation.ps1 +0 -126
  118. package/assets/docs/VerticalSlice/unpack-foundation.sh +0 -217
  119. package/assets/docs/VerticalSlice/unpack-reference-foundation.ps1 +0 -129
  120. package/assets/docs/VerticalSlice/unpack-reference-foundation.sh +0 -218
  121. package/assets/docs/architecture/clean-architecture.md +0 -71
  122. package/assets/docs/architecture/cqrs.md +0 -141
  123. package/assets/docs/architecture/eventing.md +0 -83
  124. package/assets/docs/architecture/folder-structure.md +0 -61
  125. package/assets/docs/architecture/modular-monolith.md +0 -47
  126. package/assets/docs/architecture/persistence.md +0 -269
  127. package/assets/docs/architecture/testing.md +0 -55
  128. package/assets/docs/architecture/vertical-slices.md +0 -201
  129. package/assets/docs/components/Email/README.md +0 -119
  130. package/assets/docs/components/Email/ReferenceEndpoints/SendEmailEndpoint.cs +0 -84
  131. package/assets/docs/components/Email/integration.md +0 -300
  132. package/assets/docs/components/Jobs/README.md +0 -113
  133. package/assets/docs/components/Jobs/integration.md +0 -259
  134. package/assets/docs/components/QrGenerator/README.md +0 -85
  135. package/assets/docs/components/QrGenerator/ReferenceEndpoints/GenerateQrEndpoint.cs +0 -57
  136. package/assets/docs/components/QrGenerator/integration.md +0 -230
  137. package/assets/docs/components/README.md +0 -74
  138. package/assets/docs/domain/domain-driven-design.md +0 -212
  139. package/assets/docs/domainspecs/healthcare/ai-rules.domain.md +0 -34
  140. package/assets/docs/domainspecs/healthcare/api-versioning.domain.md +0 -17
  141. package/assets/docs/domainspecs/healthcare/auditing.domain.md +0 -97
  142. package/assets/docs/domainspecs/healthcare/authorization.domain.md +0 -26
  143. package/assets/docs/domainspecs/healthcare/clinical-terms.md +0 -20
  144. package/assets/docs/domainspecs/healthcare/coding-standards.domain.md +0 -26
  145. package/assets/docs/domainspecs/healthcare/comandrulemap.md +0 -100
  146. package/assets/docs/domainspecs/healthcare/commandtechrulemap.md +0 -119
  147. package/assets/docs/domainspecs/healthcare/consent.md +0 -19
  148. package/assets/docs/domainspecs/healthcare/cqrs.domain.md +0 -12
  149. package/assets/docs/domainspecs/healthcare/data-classification.domain.md +0 -28
  150. package/assets/docs/domainspecs/healthcare/dependency-policy.domain.md +0 -11
  151. package/assets/docs/domainspecs/healthcare/dicom.md +0 -15
  152. package/assets/docs/domainspecs/healthcare/domain-driven-design.domain.md +0 -28
  153. package/assets/docs/domainspecs/healthcare/domain-requirements.md +0 -55
  154. package/assets/docs/domainspecs/healthcare/eventing.domain.md +0 -18
  155. package/assets/docs/domainspecs/healthcare/fhir.md +0 -36
  156. package/assets/docs/domainspecs/healthcare/folder-structure.domain.md +0 -23
  157. package/assets/docs/domainspecs/healthcare/hipaa.md +0 -34
  158. package/assets/docs/domainspecs/healthcare/hl7.md +0 -15
  159. package/assets/docs/domainspecs/healthcare/integration-terms.md +0 -21
  160. package/assets/docs/domainspecs/healthcare/persistence.domain.md +0 -20
  161. package/assets/docs/domainspecs/healthcare/terminology.md +0 -30
  162. package/assets/docs/domainspecs/healthcare/testing.domain.md +0 -15
  163. package/assets/docs/domainspecs/healthcare/vertical-slices.domain.md +0 -18
  164. package/assets/docs/domainspecs/healthcare/zero-trust.domain.md +0 -12
  165. package/assets/docs/domainspecs/pharma/ai-rules.domain.md +0 -32
  166. package/assets/docs/domainspecs/pharma/api-versioning.domain.md +0 -20
  167. package/assets/docs/domainspecs/pharma/atc.md +0 -22
  168. package/assets/docs/domainspecs/pharma/auditing.domain.md +0 -90
  169. package/assets/docs/domainspecs/pharma/authorization.domain.md +0 -25
  170. package/assets/docs/domainspecs/pharma/coding-standards.domain.md +0 -28
  171. package/assets/docs/domainspecs/pharma/comandrulemap.md +0 -108
  172. package/assets/docs/domainspecs/pharma/commandtechrulemap.md +0 -119
  173. package/assets/docs/domainspecs/pharma/cqrs.domain.md +0 -16
  174. package/assets/docs/domainspecs/pharma/data-classification.domain.md +0 -24
  175. package/assets/docs/domainspecs/pharma/data-provenance.md +0 -21
  176. package/assets/docs/domainspecs/pharma/dependency-policy.domain.md +0 -11
  177. package/assets/docs/domainspecs/pharma/domain-driven-design.domain.md +0 -36
  178. package/assets/docs/domainspecs/pharma/domain-requirements.md +0 -99
  179. package/assets/docs/domainspecs/pharma/eventing.domain.md +0 -18
  180. package/assets/docs/domainspecs/pharma/fhir.md +0 -31
  181. package/assets/docs/domainspecs/pharma/folder-structure.domain.md +0 -24
  182. package/assets/docs/domainspecs/pharma/hl7.md +0 -17
  183. package/assets/docs/domainspecs/pharma/idmp.md +0 -26
  184. package/assets/docs/domainspecs/pharma/integration-terms.md +0 -21
  185. package/assets/docs/domainspecs/pharma/ndc.md +0 -28
  186. package/assets/docs/domainspecs/pharma/persistence.domain.md +0 -23
  187. package/assets/docs/domainspecs/pharma/pharma-terms.md +0 -27
  188. package/assets/docs/domainspecs/pharma/product-lifecycle.domain.md +0 -30
  189. package/assets/docs/domainspecs/pharma/regulatory-compliance.md +0 -19
  190. package/assets/docs/domainspecs/pharma/terminology.md +0 -32
  191. package/assets/docs/domainspecs/pharma/testing.domain.md +0 -19
  192. package/assets/docs/domainspecs/pharma/vertical-slices.domain.md +0 -17
  193. package/assets/docs/domainspecs/pharma/zero-trust.domain.md +0 -12
  194. package/assets/docs/glossary/ai-rules.md +0 -59
  195. package/assets/docs/governance/api-versioning.md +0 -99
  196. package/assets/docs/governance/coding-standards.md +0 -87
  197. package/assets/docs/governance/dependency-policy.md +0 -40
  198. package/assets/docs/interop/ihe-conformance.md +0 -31
  199. package/assets/docs/requirement-spec.txt +0 -27
  200. package/assets/docs/security/auditing.md +0 -160
  201. package/assets/docs/security/authorization-matrix.md +0 -14
  202. package/assets/docs/security/authorization.md +0 -87
  203. package/assets/docs/security/data-classification.md +0 -38
  204. package/assets/docs/security/zero-trust.md +0 -76
  205. package/assets/docs/technology-constitution.md +0 -172
@@ -1,269 +0,0 @@
1
- # Persistence Standards
2
-
3
- > Pure technology (domain-agnostic) — domain additions in [[persistence.domain]]. NON-NEGOTIABLE where marked. Commands: `/aw-spec-design`, `/aw-spec-run`.
4
-
5
- ## EF Core conventions
6
-
7
- - The `DbContext` is a `ContextAwareDbContext<TContext>` and is **provider-aware**.
8
- - EF Core mapping lives **only** in `{Module}/Persistence/<Aggregate>Configuration.cs`,
9
- applied via `ApplyConfigurationsFromAssembly`.
10
- - `DateTimeOffset` columns **MUST** be re-mapped to `timestamptz` when the provider is Npgsql.
11
- - Cross-cutting behaviors are applied via `modelBuilder.ApplyCrossCuttingBehaviors(this)`:
12
- soft-delete global filters, tenant filters, base concurrency-token configuration.
13
-
14
- ## Repository rules
15
-
16
- - **Read handlers** inject `IReadRepository<TAggregate>`; **write handlers** inject
17
- `IRepository<TAggregate>` + `IUnitOfWork`.
18
- - No "repository as data-access god object" — **no custom methods** beyond
19
- `IRepository<T>` / `IReadRepository<T>` + `Specification<T>`.
20
- - Direct `DbContext.Set<T>()` LINQ inside Application Features is FORBIDDEN — use
21
- `IReadRepository<T>` + a `Specification<T>` only.
22
-
23
- ## Specification pattern
24
-
25
- - Every non-trivial query is encapsulated in a `Specification<TAggregate>` under
26
- `{Module}/Domain/Specifications/`.
27
- - Specifications **MUST be composable**; paging via `WithPaging(pageNumber, pageSize)`.
28
- - Ordering uses shared generic helpers (`OrderByIdAsc`, `OrderByIdDesc`, `OrderByNameAsc`);
29
- ad-hoc `OrderBy` in handlers is forbidden when a typed helper exists.
30
- - **Cross-aggregate joins via EF navigation properties are FORBIDDEN** — reference parent
31
- aggregates by strongly-typed ID and project client-side.
32
-
33
- ## Concurrency handling
34
-
35
- - Every mutable aggregate **MUST** implement `IConcurrencyTracked`.
36
- - Concurrency tokens are **client-managed**: `byte[] RowVersion`,
37
- `IsConcurrencyToken().ValueGeneratedNever()`, refreshed by a `SaveChanges` interceptor.
38
- - SQL Server `IsRowVersion()` semantics **MUST NOT** leak into entity configurations.
39
- - ETag / `If-Match` at the edge: see [[api-versioning]].
40
- - Server sets the ETag header on every create/read/update via `SetETagFromBase64(...)`.
41
- - Update/Delete endpoints extract `If-Match` via `GetIfMatchBase64Token()` and pass it
42
- to the command; mismatch returns `409 concurrency.conflict`.
43
- - `DbUpdateConcurrencyException` is translated to the same `409 concurrency.conflict`.
44
-
45
- ## Soft-delete rules
46
-
47
- - Soft delete is enabled via `EnableSoftDelete()`.
48
- - **Hard deletes are FORBIDDEN** for any aggregate flagged as retention-protected / regulated;
49
- deletion is logical via `MarkAsDeleted(...)`. (See [[persistence.domain]] for which data
50
- classes are retention-protected.)
51
- - Append-only aggregates have no delete path at all.
52
-
53
- ## Tenancy patterns
54
-
55
- - Tenancy is enabled via `EnableTenancy()` and `RegisterDefaultContexts(...)`.
56
- - Tenant resolution **MUST be transparent** to handlers.
57
- - Default model: **tenant-per-row** with tenant-aware global filters; deployments MAY opt
58
- into tenant-per-database via configuration.
59
-
60
- ## Migrations policy
61
-
62
- - Migrations are authored against **PostgreSQL** (production provider). Development MAY run
63
- on SQLite (non-sensitive development data only).
64
- - Multi-provider warnings (`PendingModelChangesWarning`) **MUST** be suppressed only at the
65
- runtime registration site, **never** in migration code.
66
- - Migrations **MUST** apply cleanly to a fresh PostgreSQL instance (CI gate).
67
-
68
- ## Provider profile
69
-
70
- - Production: PostgreSQL 16+ (encryption-at-rest profile in [[persistence.domain]]);
71
- per-module schemas where reasonable.
72
- - Development: PostgreSQL via Aspire containers; SQLite only for in-memory unit-test fixtures.
73
-
74
- ## Canonical example (agent reference — `<SampleProjectName>` sample)
75
-
76
- EF Core configuration (Vogen ID + value object conversions, unique business key,
77
- cross-aggregate FK stored as a raw `Guid` with no navigation):
78
-
79
- ```csharp
80
- // <SampleProjectName>.Service/Projects/Persistence/ProjectConfiguration.cs
81
- public class ProjectConfiguration : IEntityTypeConfiguration<Project>
82
- {
83
- public void Configure(EntityTypeBuilder<Project> builder)
84
- {
85
- builder.ToTable("Projects");
86
- builder.HasKey(e => e.Id);
87
-
88
- // Strongly-typed Vogen ID -> Guid, never DB-generated
89
- builder.Property(e => e.Id)
90
- .HasConversion(id => id.Value, v => ProjectId.From(v))
91
- .ValueGeneratedNever();
92
-
93
- // Value object -> column + unique business key
94
- builder.Property(e => e.Code)
95
- .HasConversion(c => c.Value, v => ProjectCode.From(v))
96
- .HasColumnName("Code").HasMaxLength(20).IsRequired();
97
- builder.HasIndex(e => e.Code).IsUnique();
98
-
99
- builder.Property(e => e.Status).HasConversion<string>().HasMaxLength(30).IsRequired();
100
-
101
- // Cross-aggregate FK as nullable Guid — NO EF navigation to TaskUser
102
- builder.Property(e => e.OwnerId)
103
- .HasConversion(
104
- id => id.HasValue ? id.Value.Value : (Guid?)null,
105
- v => v.HasValue ? TaskUserId.From(v.Value) : (TaskUserId?)null)
106
- .HasColumnName("OwnerId");
107
- }
108
- }
109
- ```
110
-
111
- Composable specification (filter + include + paging):
112
-
113
- ```csharp
114
- // {Module}/Domain/Specifications/ProjectSearchSpec.cs
115
- public sealed class ProjectSearchSpec : Specification<Project>
116
- {
117
- public ProjectSearchSpec(string? search, ProjectStatus? status = null)
118
- {
119
- Query.Include(p => p.Tasks);
120
- if (!string.IsNullOrWhiteSpace(search))
121
- {
122
- var s = search.Trim().ToLowerInvariant();
123
- Query.Where(p => p.Code.Value.ToLower().Contains(s) || p.Name.ToLower().Contains(s));
124
- }
125
- if (status.HasValue) Query.Where(p => p.Status == status.Value);
126
- }
127
-
128
- public ProjectSearchSpec WithPaging(int pageNumber, int pageSize)
129
- {
130
- Query.Skip(Math.Max(0, (pageNumber - 1) * pageSize)).Take(Math.Max(1, pageSize));
131
- return this;
132
- }
133
- }
134
- ```
135
-
136
- Read handler using the spec + typed ordering helper, returning `PagedResult<T>`:
137
-
138
- ```csharp
139
- var spec = new ProjectSearchSpec(request.Search, request.Status)
140
- .OrderByIdDesc<ProjectSearchSpec, Project, ProjectId>(); // typed helper, not ad-hoc OrderBy
141
-
142
- var total = await _repo.CountAsync(spec, ct);
143
- var items = await _repo.ListAsync(spec.WithPaging(request.PageNumber, request.PageSize), ct);
144
-
145
- return Result<PagedResult<ProjectDto>>.Ok(new PagedResult<ProjectDto>
146
- {
147
- Items = items.ToDtoList(), TotalCount = total,
148
- PageNumber = request.PageNumber, PageSize = request.PageSize
149
- });
150
- ```
151
-
152
- Owned child-entity configuration (separate table, FK + cascade, soft-delete mirror filter,
153
- no EF navigation to other aggregates):
154
-
155
- ```csharp
156
- // {Module}/Persistence/ProjectTaskConfiguration.cs
157
- public class ProjectTaskConfiguration : IEntityTypeConfiguration<ProjectTask>
158
- {
159
- public void Configure(EntityTypeBuilder<ProjectTask> builder)
160
- {
161
- builder.ToTable("ProjectTasks");
162
- builder.HasKey(e => e.Id);
163
- builder.Property(e => e.Id)
164
- .HasConversion(id => id.Value, v => ProjectTaskId.From(v)).ValueGeneratedNever();
165
-
166
- builder.Property(e => e.ProjectId) // FK back to the root
167
- .HasConversion(id => id.Value, v => ProjectId.From(v)).IsRequired();
168
-
169
- builder.Property(e => e.Title) // Vogen value object
170
- .HasConversion(t => t.Value, v => TaskTitle.From(v)).HasMaxLength(300).IsRequired();
171
- builder.Property(e => e.Status).HasConversion<string>().HasMaxLength(30).IsRequired();
172
-
173
- // Cross-aggregate ref stored as raw Guid — NO navigation to TaskUser
174
- builder.Property(e => e.AssigneeId)
175
- .HasConversion(
176
- id => id.HasValue ? id.Value.Value : (Guid?)null,
177
- v => v.HasValue ? TaskUserId.From(v.Value) : (TaskUserId?)null);
178
-
179
- builder.HasOne(e => e.Project).WithMany(p => p.Tasks) // owned collection
180
- .HasForeignKey(e => e.ProjectId).OnDelete(DeleteBehavior.Cascade);
181
-
182
- // Mirror the parent's ISoftDeletable filter onto the required dependent
183
- builder.HasQueryFilter(t => !t.Project.IsDeleted);
184
- builder.HasIndex(e => e.ProjectId);
185
- builder.HasIndex(e => e.AssigneeId);
186
- }
187
- }
188
- ```
189
-
190
- DbContext — `ContextAwareDbContext`, assembly-scanned configs, provider-aware `timestamptz`,
191
- cross-cutting behaviors, and the client-managed concurrency token override:
192
-
193
- ```csharp
194
- // <SampleProjectName>.Service/Infrastructure/ApplicationDbContext.cs
195
- public class ApplicationDbContext(
196
- DbContextOptions<ApplicationDbContext> options, ICurrentTenant? tenant = null)
197
- : ContextAwareDbContext<ApplicationDbContext>(options, tenant)
198
- {
199
- protected override void OnModelCreating(ModelBuilder modelBuilder)
200
- {
201
- modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
202
-
203
- if (Database.ProviderName == "Npgsql.EntityFrameworkCore.PostgreSQL")
204
- foreach (var et in modelBuilder.Model.GetEntityTypes())
205
- foreach (var p in et.GetProperties()
206
- .Where(p => p.ClrType == typeof(DateTimeOffset) || p.ClrType == typeof(DateTimeOffset?)))
207
- p.SetColumnType("timestamptz");
208
-
209
- modelBuilder.ApplyCrossCuttingBehaviors(this); // soft-delete + tenant filters + audit
210
-
211
- // Client-managed concurrency token (works on SQLite + PostgreSQL; not SQL Server IsRowVersion)
212
- foreach (var et in modelBuilder.Model.GetEntityTypes())
213
- if (typeof(IConcurrencyTracked).IsAssignableFrom(et.ClrType))
214
- modelBuilder.Entity(et.ClrType).Property<byte[]>("RowVersion")
215
- .IsConcurrencyToken().ValueGeneratedNever();
216
-
217
- base.OnModelCreating(modelBuilder);
218
- }
219
-
220
- public DbSet<Project> Projects => Set<Project>();
221
- public DbSet<ProjectTask> ProjectTasks => Set<ProjectTask>();
222
- }
223
- ```
224
-
225
- Soft-delete: logical delete via `MarkAsDeleted(...)` + `DeleteAsync` (never a hard delete),
226
- and a separate `IgnoreQueryFilters()` spec for admin/audit reads:
227
-
228
- ```csharp
229
- // Delete handler — soft delete + concurrency guard (returns Result, no DTO)
230
- var entity = await _repo.GetByIdAsync(ProjectId.From(request.Id), ct);
231
- if (entity is null)
232
- return Result.Failure(new Error("project.not_found", $"Project {request.Id} not found", 404, "projects"));
233
- // ... concurrency token check (see api-versioning) ...
234
- entity.MarkAsDeleted(); // sets IsDeleted/DeletedOn + raises ProjectDeletedEvent
235
- await _repo.DeleteAsync(entity, ct); // framework translates to UPDATE, not DELETE
236
-
237
- // Admin/audit spec that bypasses the global soft-delete filter
238
- public sealed class ProjectSearchIncludeDeletedSpec : Specification<Project>
239
- {
240
- public ProjectSearchIncludeDeletedSpec(string? search, ProjectStatus? status = null)
241
- {
242
- Query.Include(p => p.Tasks).IgnoreQueryFilters(); // include soft-deleted rows
243
- // ... same Where filters as ProjectSearchSpec ...
244
- }
245
- }
246
- ```
247
-
248
- Composition — repository + cross-cutting behaviors registered once at startup:
249
-
250
- ```csharp
251
- // <SampleProjectName>.Service/Extensions/InfrastructureServiceRegistration.cs
252
- services.AddRepositories(builder =>
253
- {
254
- builder
255
- .UseSingleDbContextEFRepositories<ApplicationDbContext>(db => db.UseSqlite(/* or UseNpgsql */)
256
- .ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)))
257
- .EnableTenancy()
258
- .EnableSoftDelete()
259
- .EnableClientManagedConcurrency()
260
- .EnableAuditing().EnableAdvancedAuditing() // see [[auditing]]
261
- .Done();
262
- }, options => options.Provider = "EfCore");
263
-
264
- services.TryAddScoped(typeof(IUnitOfWork), typeof(EfUnitOfWork<ApplicationDbContext>));
265
- ```
266
-
267
- ## Related
268
-
269
- - [[clean-architecture]] · [[cqrs]] · [[domain-driven-design]] · [[api-versioning]] · [[persistence.domain]]
@@ -1,55 +0,0 @@
1
- # Testing Standards
2
-
3
- > Pure technology (domain-agnostic) — domain additions in [[testing.domain]]. Commands: `/aw-spec-design`, `/aw-spec-implement`, `/aw-spec-implement-delta`, `/aw-spec-analyze-slice`.
4
-
5
- ## Test-first engineering
6
-
7
- - Generated tests follow Test-First Engineering and **MUST fail before** the production
8
- change is in place.
9
- - Any change to an aggregate invariant, endpoint contract, or error code **MUST** include
10
- the corresponding test update **in the same commit**.
11
-
12
- ## Unit test conventions
13
-
14
- - Unit tests are required for **every** aggregate behavior, value object, and specification.
15
- - Tests are **deterministic** — time, randomness, and IDs are injected
16
- (use the framework-provided `IClock` / `IGuidGenerator`). Never `DateTime.UtcNow` /
17
- `Guid.NewGuid()` in the system under test.
18
- - Tests **MUST NOT** reach into module internals; they exercise the public endpoint or
19
- `IMediator` surface.
20
-
21
- ## Handler tests
22
-
23
- Required for **every** Command/Query handler and MUST cover:
24
-
25
- - happy path
26
- - validation failure
27
- - not-found
28
- - (for writes) concurrency conflict
29
-
30
- ## Integration testing
31
-
32
- - Required for **every** endpoint; run against **PostgreSQL via Testcontainers** or Aspire.
33
- - Test database engine: Testcontainers PostgreSQL for integration; in-memory fixtures for
34
- unit tests.
35
-
36
- ## Testcontainers usage
37
-
38
- - Integration and contract tests spin up real PostgreSQL (and other backing services)
39
- via Testcontainers / Aspire — no faked persistence in integration tests.
40
-
41
- ## Test data hygiene
42
-
43
- - Tests use a **shared fixture library** rather than inlining fabricated data.
44
- - Production data MUST NOT be used in development, test, demo, or shared CI.
45
- (Domain specifics — synthetic fixtures + terminology sentinels — in [[testing.domain]].)
46
-
47
- ## Coverage rules
48
-
49
- - Minimum **85% line coverage** on Application + Domain.
50
- - **100%** on aggregate behaviors and value objects.
51
- - Code coverage on changed files must meet the threshold (CI gate).
52
-
53
- ## Related
54
-
55
- - [[domain-driven-design]] · [[persistence]] · [[testing.domain]]
@@ -1,201 +0,0 @@
1
- # Vertical Slice Standards
2
-
3
- > Pure technology (domain-agnostic) — domain additions in [[vertical-slices.domain]]. Commands: `/aw-spec-design`.
4
-
5
- ## Feature organization
6
-
7
- - Each feature is a **self-contained vertical slice**: Command/Query, Handler, Endpoint,
8
- Validator co-located in **one folder**.
9
- - No horizontal "Services/Managers" layers spanning slices.
10
-
11
- ## Folder structure (per module)
12
-
13
- ```
14
- <Module>/
15
- Domain/
16
- <Aggregate>.cs
17
- <ChildEntity>.cs
18
- <ValueObject>.cs
19
- <Aggregate>Id.cs [ValueObject<Guid>] partial struct
20
- Events/ DomainEventBase descendants
21
- Specifications/
22
- Persistence/<Aggregate>Configuration.cs
23
- Features/<FeatureName>/ Command/Query + Handler + Endpoint + Validator
24
- Interop/ external-protocol adapters (see [[vertical-slices.domain]])
25
- Shared/ module-public DTOs + ToDto extensions
26
- ```
27
-
28
- ## Endpoint conventions
29
-
30
- - One endpoint **per file**. Endpoints inherit `SecureFastEndpoint<TRequest, TResponse>`.
31
- - Kebab-case routes; resources are plural nouns; verbs map to intent
32
- (POST create, GET list/fetch, PUT full update, PATCH partial update, DELETE logical delete).
33
- - GUID route ids **MUST** use the `:guid` constraint.
34
- - Tagged via `Options(o => o.WithTags("NN - <Module> - (FastEndpoints)"))` and **MUST**
35
- provide a `Summary`.
36
- - HTTP request classes are declared **inside the same file as the Endpoint** and contain
37
- **primitive types only**.
38
- - Endpoints **MUST NOT** swallow exceptions — the central exception handler owns the contract.
39
- - See [[authorization]] for security cache policy on endpoints.
40
-
41
- ## Validator placement
42
-
43
- - Validators live in the **feature folder** alongside the Command/Query they validate.
44
- - Validators check **request shape only**; aggregate invariants are enforced in the Domain.
45
-
46
- ## DTO placement (immutable contracts)
47
-
48
- - DTOs live in `{Module}/Shared/` and are mapped with **hand-written** `ToDto()` /
49
- `ToDtoList()` extension methods. AutoMapper-style reflection mappers are FORBIDDEN.
50
- - `record EntityDto { public required Guid Id { get; init; } ... }` — no setters;
51
- required init for non-nullable members.
52
- - Every DTO **MUST** include a base64 `RowVersionBase64` when the aggregate implements
53
- `IConcurrencyTracked`.
54
- - Paged results **MUST** use the standard `PagedResult<T>` shape
55
- (`Items, TotalCount, PageNumber, PageSize`, computed `TotalPages`) with required init props.
56
-
57
- ## Specification placement
58
-
59
- - Query composition lives in the **Domain**, under `{Module}/Domain/Specifications/`.
60
- See [[persistence]] for specification rules.
61
-
62
- ## Canonical example (agent reference — `<SampleProjectName>` sample)
63
-
64
- A complete slice: Endpoint (+ request class in the same file), Validator, DTO, and mapping.
65
-
66
- ```csharp
67
- // <SampleProjectName>.Service/Projects/Features/CreateProject/CreateProjectEndpoint.cs
68
- public class CreateProjectEndpoint(IMediator _mediator)
69
- : SecureFastEndpoint<CreateProjectRequest, ProjectDto>
70
- {
71
- public override void Configure()
72
- {
73
- Post("/projects"); // kebab-case, plural noun
74
- Summary(s => s.Summary = "Create a new project");
75
- Options(o => o.WithTags("02 - Project - (FastEndpoints)"));
76
- }
77
-
78
- protected override SecurityCachePolicy GetSecurityCachePolicy() => new()
79
- {
80
- SecurityLevel = SecurityLevel.Internal,
81
- CachePolicy = CachePolicy.NoStore // mutating endpoint
82
- };
83
-
84
- protected override async Task<Result<ProjectDto>> ExecuteAsync(
85
- CreateProjectRequest req, CancellationToken ct)
86
- {
87
- var result = await _mediator.Send(new CreateProjectCommand( // IMediator only
88
- req.Code, req.Name, req.Description, req.StartDate, req.EndDate, req.OwnerId), ct);
89
-
90
- if (result.IsSuccess && !string.IsNullOrWhiteSpace(result.Value?.RowVersionBase64))
91
- HttpContext.Response.SetETagFromBase64(result.Value!.RowVersionBase64);
92
- return result;
93
- }
94
- }
95
-
96
- // Request lives in the SAME file — primitive types only
97
- public class CreateProjectRequest
98
- {
99
- public string Code { get; set; } = string.Empty;
100
- public string Name { get; set; } = string.Empty;
101
- public string? Description { get; set; }
102
- public DateOnly StartDate { get; set; }
103
- public DateOnly? EndDate { get; set; }
104
- public Guid? OwnerId { get; set; }
105
- }
106
- ```
107
-
108
- ```csharp
109
- // .../CreateProject/CreateProjectValidator.cs — request shape only
110
- public class CreateProjectValidator : AbstractValidator<CreateProjectCommand>
111
- {
112
- public CreateProjectValidator()
113
- {
114
- RuleFor(x => x.Code).NotEmpty().MaximumLength(20).Matches(@"^[A-Z0-9\-]+$");
115
- RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
116
- RuleFor(x => x.EndDate)
117
- .GreaterThanOrEqualTo(x => x.StartDate).When(x => x.EndDate.HasValue);
118
- }
119
- }
120
- ```
121
-
122
- ```csharp
123
- // {Module}/Shared/ProjectDto.cs — includes RowVersionBase64 (IConcurrencyTracked)
124
- public record ProjectDto
125
- {
126
- public Guid Id { get; set; }
127
- public string Code { get; set; } = string.Empty;
128
- public string Name { get; set; } = string.Empty;
129
- public string Status { get; set; } = string.Empty;
130
- public Guid? OwnerId { get; set; }
131
- public List<ProjectTaskDto> Tasks { get; set; } = new();
132
- public string RowVersionBase64 { get; set; } = string.Empty;
133
- }
134
-
135
- // {Module}/Shared/ProjectMappingExtensions.cs — hand-written, zero reflection
136
- public static class ProjectMappingExtensions
137
- {
138
- public static ProjectDto ToDto(this Project e) => new()
139
- {
140
- Id = e.Id.Value, Code = e.Code.Value, Name = e.Name,
141
- Status = e.Status.ToString(), OwnerId = e.OwnerId?.Value,
142
- Tasks = e.Tasks.Select(t => t.ToDto()).ToList(),
143
- RowVersionBase64 = e.RowVersion is { Length: > 0 } rv
144
- ? Convert.ToBase64String(rv) : string.Empty
145
- };
146
-
147
- public static List<ProjectDto> ToDtoList(this IEnumerable<Project> es)
148
- => es.Select(e => e.ToDto()).ToList();
149
- }
150
- ```
151
-
152
- Sub-resource endpoint for a distinct operation on a child — nested route, multiple route
153
- params, `EmptyResponse` when there is no body to return:
154
-
155
- ```csharp
156
- // .../Features/AssignTask/AssignTaskEndpoint.cs
157
- public class AssignTaskEndpoint(IMediator _mediator)
158
- : SecureFastEndpoint<AssignTaskRequest, EmptyResponse>
159
- {
160
- public override void Configure()
161
- {
162
- Put("/projects/{projectId:guid}/tasks/{taskId:guid}/assignee"); // sub-resource
163
- Summary(s => s.Summary = "Assign a task to a TaskUser");
164
- Options(o => o.WithTags("02 - Project - (FastEndpoints)"));
165
- }
166
-
167
- protected override SecurityCachePolicy GetSecurityCachePolicy() => new()
168
- { SecurityLevel = SecurityLevel.Internal, CachePolicy = CachePolicy.NoStore };
169
-
170
- protected override async Task<Result<EmptyResponse>> ExecuteAsync(
171
- AssignTaskRequest req, CancellationToken ct)
172
- {
173
- var projectId = Route<Guid>("projectId");
174
- var taskId = Route<Guid>("taskId");
175
- var result = await _mediator.Send(new AssignTaskCommand(projectId, taskId, req.AssigneeId), ct);
176
- return result.IsSuccess
177
- ? Result<EmptyResponse>.Ok(new EmptyResponse())
178
- : Result<EmptyResponse>.Failure([.. result.Errors]);
179
- }
180
- }
181
-
182
- public class AssignTaskRequest { public Guid AssigneeId { get; set; } }
183
- ```
184
-
185
- Standard `PagedResult<T>` contract (required init props, computed `TotalPages`):
186
-
187
- ```csharp
188
- // {Module}/Common/PagedResult.cs
189
- public sealed class PagedResult<T>
190
- {
191
- public required IReadOnlyList<T> Items { get; init; }
192
- public required int TotalCount { get; init; }
193
- public required int PageNumber { get; init; }
194
- public required int PageSize { get; init; }
195
- public int TotalPages => (int)Math.Ceiling((double)TotalCount / PageSize);
196
- }
197
- ```
198
-
199
- ## Related
200
-
201
- - [[clean-architecture]] · [[cqrs]] · [[persistence]] · [[authorization]] · [[vertical-slices.domain]]
@@ -1,119 +0,0 @@
1
- # Email Component (`AppWeaver.Messaging.Email`)
2
-
3
- > Pure technology (domain-agnostic). Reference sheet for the Email messaging component.
4
- > To wire it into a project, follow the agent runbook in [integration.md](./integration.md).
5
- > Commands: `/aw-spec-add-component`.
6
-
7
- ## What it is
8
-
9
- A provider-agnostic email-sending component. Application code depends on a single abstraction,
10
- `IEmailService`; the transport (Microsoft Graph, SMTP, SendGrid, OAuth2-SMTP, or a local File
11
- sink) is chosen once during DI registration via `AddEmailMessaging(...)`.
12
-
13
- **Use when:** a feature must send transactional or notification email (user-created alerts,
14
- confirmations, admin notices, password flows, attachments).
15
-
16
- ## Public contract (`AppWeaver.Messaging.Email`)
17
-
18
- ```csharp
19
- namespace AppWeaver.Messaging.Email;
20
-
21
- public interface IEmailService
22
- {
23
- // Convenience overload — single recipient.
24
- Task SendAsync(string from, string to, string subject, string body,
25
- bool isHtml = true, CancellationToken cancellationToken = default);
26
-
27
- // Full message — multi-recipient, Cc/Bcc, attachments.
28
- Task SendAsync(EmailMessage message, CancellationToken cancellationToken = default);
29
- }
30
- ```
31
-
32
- ```csharp
33
- namespace AppWeaver.Messaging.Email.Entities;
34
-
35
- public class EmailMessage
36
- {
37
- public string From { get; set; } // falls back to provider DefaultFrom if empty
38
- public List<string> To { get; set; } = new();
39
- public List<string> Cc { get; set; } = new();
40
- public List<string> Bcc { get; set; } = new();
41
- public string Subject { get; set; }
42
- public string Body { get; set; }
43
- public bool IsHtml { get; set; } = true;
44
- public List<EmailAttachment> Attachments { get; set; } = new();
45
- }
46
-
47
- public class EmailAttachment
48
- {
49
- public string FileName { get; set; }
50
- public byte[] Content { get; set; }
51
- public string ContentType { get; set; } = "application/octet-stream";
52
- }
53
- ```
54
-
55
- ## Packages
56
-
57
- | Package | Version | Role | Builder method | Provider key |
58
- |---|---|---|---|---|
59
- | `AppWeaver.Messaging.Email` | `0.1.3` | **Required.** Abstraction + `AddEmailMessaging` + facade. | — | — |
60
- | `AppWeaver.Messaging.Email.MsGraph` | `0.1.2` | Microsoft 365 / Graph API (recommended for prod). | `UseMsGraph` | `MsGraph` |
61
- | `AppWeaver.Messaging.Email.Smtp` | `0.1.3` | Classic SMTP (host/port/user/password). | `UseSmtp` | `Smtp` |
62
- | `AppWeaver.Messaging.Email.SendGrid` | `0.1.2` | SendGrid API key. | `UseSendGrid` | `SendGrid` |
63
- | `AppWeaver.Messaging.Email.OAuth2` | `0.1.3` | OAuth2-authenticated SMTP (O365). | `UseOAuth2` | `OAuth2` |
64
- | `AppWeaver.Messaging.Email.File` | `0.1.2` | Writes email to a local `.txt` file — local dev / tests. | `UseFile` | `File` |
65
-
66
- Install the abstraction **plus exactly one provider** for the environment you are wiring.
67
-
68
- ## Provider option shapes
69
-
70
- | Provider | Options type | Key fields | Bound config section |
71
- |---|---|---|---|
72
- | MsGraph | `MsGraphEmailOptions` | `TenantId`, `ClientId`, `ClientSecret`, `DefaultFrom`, `Scopes` | `Messaging.Email.MsGraph` |
73
- | Smtp | `SmtpEmailOptions` | `Host`, `Port`, `Username`, `Password`, `EnableSsl`, `DefaultFrom` | `Messaging.Email.Smtp` |
74
- | SendGrid | `SendGridEmailOptions` | `ApiKey` *(required)*, `DefaultFrom` | `Messaging.Email.SendGrid` |
75
- | OAuth2 | `OAuth2EmailOptions` | `TenantId`, `ClientId`, `ClientSecret`, `Host`, `Port`, `Scopes`, `DefaultFrom` | `Messaging.Email.OAuth2` |
76
- | File | `FileEmailOptions` | `DefaultFrom` | `Messaging.Email.Smtp` *(quirk — pass options inline)* |
77
-
78
- ## Registration entry point
79
-
80
- ```csharp
81
- // In AppWeaver.Messaging.Email.Extensions
82
- IComponentBuilder<IEmailService> AddEmailMessaging(
83
- this IServiceCollection services,
84
- Action<IComponentBuilder<IEmailService>> configureBuilder, // required
85
- Action<MessagingEmailConfiguration>? configureOptions = null,// set Provider here
86
- IConfiguration? configuration = null); // OR bind from config
87
- ```
88
-
89
- `MessagingEmailConfiguration` exposes a single `[Required] string Provider`. The facade
90
- (`DefaultImplementation`) resolves the keyed provider whose key equals `Provider`.
91
-
92
- ## Source map (framework)
93
-
94
- ```
95
- framework/Messaging.Email/
96
- Integration.md ← original MsGraph tutorial (reference)
97
- src/
98
- AppWeaver.Messaging.Email/ ← IEmailService, EmailMessage, AddEmailMessaging, facade
99
- AppWeaver.Messaging.Email.MsGraph/ ← UseMsGraph + MsGraphEmailService
100
- AppWeaver.Messaging.Email.Smtp/ ← UseSmtp + SmtpEmailService
101
- AppWeaver.Messaging.Email.SendGrid/ ← UseSendGrid + SendGridEmailService
102
- AppWeaver.Messaging.Email.OAuth2/ ← UseOAuth2 + OAuth2EmailService
103
- AppWeaver.Messaging.Email.File/ ← UseFile + FileEmailService
104
- test/
105
- MsGraphTest/Program.cs ← MsGraph end-to-end sample
106
- TestApp/Program.cs ← OAuth2 + File multi-provider sample
107
- ```
108
-
109
- ## Sample reference endpoint
110
-
111
- A ready-to-run FastEndpoint that sends mail inline (no command/handler) is bundled at
112
- [`ReferenceEndpoints/SendEmailEndpoint.cs`](./ReferenceEndpoints/SendEmailEndpoint.cs). It is offered
113
- (opt-in) at the end of the [integration.md](./integration.md) runbook and, if accepted, copied into
114
- the host project's `ReferenceEndpoints/` folder. Endpoint: `POST /reference/email/send`.
115
-
116
- ## Related
117
-
118
- - [integration.md](./integration.md) · [[components-readme]] · [[cqrs]] ·
119
- [[clean-architecture]] · [[dependency-policy]]