aw-spec-cli 0.1.8 → 0.1.9

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 (257) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +200 -0
  2. package/assets/.appweaver/ORCH-DIALECT.md +156 -0
  3. package/assets/.appweaver/RULE-PARTITION.md +175 -0
  4. package/assets/.appweaver/STACK-PROFILES.md +461 -0
  5. package/assets/.appweaver/ai-rules.md +180 -0
  6. package/assets/.appweaver/domain.json +1 -0
  7. package/assets/.appweaver/extensions/.registry +19 -0
  8. package/assets/.appweaver/extensions/agent-context/README.md +57 -0
  9. package/assets/.appweaver/extensions/agent-context/agent-context-config.yml +4 -0
  10. package/assets/.appweaver/extensions/agent-context/commands/aw-spec.agent-context.update.md +26 -0
  11. package/assets/.appweaver/extensions/agent-context/extension.yml +37 -0
  12. package/assets/.appweaver/extensions/agent-context/scripts/bash/update-agent-context.sh +201 -0
  13. package/assets/.appweaver/extensions/agent-context/scripts/powershell/update-agent-context.ps1 +237 -0
  14. package/assets/.appweaver/extensions.yml +16 -0
  15. package/assets/.appweaver/feature.json +3 -0
  16. package/assets/.appweaver/init-options.json +9 -0
  17. package/assets/.appweaver/integration.json +15 -0
  18. package/assets/.appweaver/integrations/aw-spec.manifest.json +9 -0
  19. package/assets/.appweaver/interpreter/run-skill.py +1176 -0
  20. package/assets/.appweaver/memory/constitution.md +50 -0
  21. package/assets/.appweaver/orchestrator/.smoke-out/smoke.md +6 -0
  22. package/assets/.appweaver/orchestrator/AGENT-PROFILE.md +94 -0
  23. package/assets/.appweaver/orchestrator/README.md +300 -0
  24. package/assets/.appweaver/orchestrator/adapters/claude_agent_sdk.py +74 -0
  25. package/assets/.appweaver/orchestrator/adapters/google_antigravity.py +74 -0
  26. package/assets/.appweaver/orchestrator/modules/delta-design.orch.yaml +105 -0
  27. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +101 -0
  28. package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +90 -0
  29. package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +126 -0
  30. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +123 -0
  31. package/assets/.appweaver/orchestrator/orchestrator.py +1721 -0
  32. package/assets/.appweaver/orchestrator/rev-templates/ai-rules.reference.md +40 -0
  33. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +101 -0
  34. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +245 -0
  35. package/assets/.appweaver/orchestrator/rev-templates/domain-constitution.skeleton.md +63 -0
  36. package/assets/.appweaver/orchestrator/rev-templates/domain-requirements.skeleton.md +48 -0
  37. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +59 -0
  38. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-file.skeleton.md +39 -0
  39. package/assets/.appweaver/orchestrator/rev-templates/frontend-rule-file.skeleton.md +63 -0
  40. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +116 -0
  41. package/assets/.appweaver/orchestrator/rev-templates/technical-constitution.skeleton.md +106 -0
  42. package/assets/.appweaver/orchestrator/rev-templates/technical-rule-file.skeleton.md +54 -0
  43. package/assets/.appweaver/orchestrator/rev-templates/ui-constitution.skeleton.md +125 -0
  44. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +123 -0
  45. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +294 -0
  46. package/assets/.appweaver/orchestrator/workflows/_smoke.orch.yaml +160 -0
  47. package/assets/.appweaver/orchestrator/workflows/aw-spec-add-component.orch.yaml +270 -0
  48. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +934 -0
  49. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-delta-design.orch.yaml +251 -0
  50. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +265 -0
  51. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +437 -0
  52. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +197 -0
  53. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +475 -0
  54. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +181 -0
  55. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +727 -0
  56. package/assets/.appweaver/orchestrator/workflows/aw-spec-demo.orch.yaml +100 -0
  57. package/assets/.appweaver/orchestrator/workflows/aw-spec-dependency-tree.orch.yaml +69 -0
  58. package/assets/.appweaver/orchestrator/workflows/aw-spec-deploy.orch.yaml +85 -0
  59. package/assets/.appweaver/orchestrator/workflows/aw-spec-design-ui.orch.yaml +386 -0
  60. package/assets/.appweaver/orchestrator/workflows/aw-spec-design.orch.yaml +382 -0
  61. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +462 -0
  62. package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +265 -0
  63. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-delta.orch.yaml +253 -0
  64. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +267 -0
  65. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +287 -0
  66. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +238 -0
  67. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +1822 -0
  68. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +293 -0
  69. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +194 -0
  70. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-domain.orch.yaml +83 -0
  71. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +128 -0
  72. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +296 -0
  73. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +439 -0
  74. package/assets/.appweaver/scripts/bash/aw-inventory.sh +17 -0
  75. package/assets/.appweaver/scripts/bash/aw-parity.sh +13 -0
  76. package/assets/.appweaver/scripts/bash/aw-rev-constitution.sh +13 -0
  77. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +2626 -0
  78. package/assets/.appweaver/scripts/bash/aw-xref.sh +15 -0
  79. package/assets/.appweaver/scripts/bash/common.sh +533 -0
  80. package/assets/.appweaver/scripts/powershell/aw-inventory.ps1 +17 -0
  81. package/assets/.appweaver/scripts/powershell/aw-parity.ps1 +13 -0
  82. package/assets/.appweaver/scripts/powershell/aw-rev-constitution.ps1 +13 -0
  83. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +2232 -0
  84. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +15 -0
  85. package/assets/.appweaver/scripts/python/aw-component-catalog.py +121 -0
  86. package/assets/.appweaver/scripts/python/aw-inventory.py +1180 -0
  87. package/assets/.appweaver/scripts/python/aw-parity.py +380 -0
  88. package/assets/.appweaver/scripts/python/aw-rev-constitution.py +329 -0
  89. package/assets/.appweaver/scripts/python/aw-stack.py +1251 -0
  90. package/assets/.appweaver/scripts/python/aw-xref.py +1330 -0
  91. package/assets/.appweaver/scripts/python/validate_okf.py +352 -0
  92. package/assets/.appweaver/stack-profiles/angular.json +16 -0
  93. package/assets/.appweaver/stack-profiles/blazor-wasm.json +17 -0
  94. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +15 -0
  95. package/assets/.appweaver/stack-profiles/go-chi.json +14 -0
  96. package/assets/.appweaver/stack-profiles/java-spring.json +16 -0
  97. package/assets/.appweaver/stack-profiles/nextjs.json +16 -0
  98. package/assets/.appweaver/stack-profiles/node-express.json +15 -0
  99. package/assets/.appweaver/stack-profiles/python-fastapi.json +16 -0
  100. package/assets/.appweaver/stack-profiles/react-vite.json +17 -0
  101. package/assets/.appweaver/stack-profiles/sveltekit.json +16 -0
  102. package/assets/.appweaver/stack-profiles/vue-vite.json +17 -0
  103. package/assets/.appweaver/stacks.json +3 -0
  104. package/assets/.appweaver/templates/constitution-domain-template.md +116 -0
  105. package/assets/.appweaver/templates/constitution-technical-template.md +108 -0
  106. package/assets/.appweaver/templates/constitution-ui-template.md +116 -0
  107. package/assets/.appweaver/templates/delta-design-template.md +69 -0
  108. package/assets/.appweaver/templates/delta-spec-template.md +63 -0
  109. package/assets/.appweaver/templates/design-template.md +383 -0
  110. package/assets/.appweaver/templates/okf-concept-template.md +114 -0
  111. package/assets/.appweaver/templates/parity-manifest-template.json +49 -0
  112. package/assets/.appweaver/templates/source-map-template.md +85 -0
  113. package/assets/.appweaver/templates/spec-template.md +309 -0
  114. package/assets/.appweaver/templates/ui-design-template.md +273 -0
  115. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.Reference.pack.txt +25248 -0
  116. package/assets/foundations/dotnet/ModularMonolith/ModularMonolith.pack.txt +5726 -0
  117. package/assets/foundations/dotnet/ModularMonolith/pack-foundation.ps1 +145 -0
  118. package/assets/foundations/dotnet/ModularMonolith/pack-foundation.sh +196 -0
  119. package/assets/foundations/dotnet/ModularMonolith/pack-reference-foundation.ps1 +157 -0
  120. package/assets/foundations/dotnet/ModularMonolith/pack-reference-foundation.sh +209 -0
  121. package/assets/foundations/dotnet/ModularMonolith/unpack-foundation.ps1 +126 -0
  122. package/assets/foundations/dotnet/ModularMonolith/unpack-foundation.sh +217 -0
  123. package/assets/foundations/dotnet/ModularMonolith/unpack-reference-foundation.ps1 +129 -0
  124. package/assets/foundations/dotnet/ModularMonolith/unpack-reference-foundation.sh +218 -0
  125. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.Reference.pack.txt +19318 -0
  126. package/assets/foundations/dotnet/VerticalSlice/VerticalSlice.pack.txt +2833 -0
  127. package/assets/foundations/dotnet/VerticalSlice/pack-foundation.ps1 +145 -0
  128. package/assets/foundations/dotnet/VerticalSlice/pack-foundation.sh +196 -0
  129. package/assets/foundations/dotnet/VerticalSlice/pack-reference-foundation.ps1 +157 -0
  130. package/assets/foundations/dotnet/VerticalSlice/pack-reference-foundation.sh +209 -0
  131. package/assets/foundations/dotnet/VerticalSlice/unpack-foundation.ps1 +126 -0
  132. package/assets/foundations/dotnet/VerticalSlice/unpack-foundation.sh +217 -0
  133. package/assets/foundations/dotnet/VerticalSlice/unpack-reference-foundation.ps1 +129 -0
  134. package/assets/foundations/dotnet/VerticalSlice/unpack-reference-foundation.sh +218 -0
  135. package/assets/foundations/dotnet/components/Email/README.md +119 -0
  136. package/assets/foundations/dotnet/components/Email/ReferenceEndpoints/SendEmailEndpoint.cs +84 -0
  137. package/assets/foundations/dotnet/components/Email/integration.md +300 -0
  138. package/assets/foundations/dotnet/components/Jobs/README.md +113 -0
  139. package/assets/foundations/dotnet/components/Jobs/integration.md +259 -0
  140. package/assets/foundations/dotnet/components/QrGenerator/README.md +85 -0
  141. package/assets/foundations/dotnet/components/QrGenerator/ReferenceEndpoints/GenerateQrEndpoint.cs +57 -0
  142. package/assets/foundations/dotnet/components/QrGenerator/integration.md +230 -0
  143. package/assets/foundations/dotnet/components/README.md +74 -0
  144. package/assets/guardrails/README.md +222 -0
  145. package/assets/guardrails/domain/healthcare/ai-rules.domain.md +34 -0
  146. package/assets/guardrails/domain/healthcare/api-versioning.domain.md +17 -0
  147. package/assets/guardrails/domain/healthcare/auditing.domain.md +97 -0
  148. package/assets/guardrails/domain/healthcare/authorization.domain.md +26 -0
  149. package/assets/guardrails/domain/healthcare/clinical-terms.md +20 -0
  150. package/assets/guardrails/domain/healthcare/coding-standards.domain.md +26 -0
  151. package/assets/guardrails/domain/healthcare/consent.md +19 -0
  152. package/assets/guardrails/domain/healthcare/cqrs.domain.md +12 -0
  153. package/assets/guardrails/domain/healthcare/data-classification.domain.md +28 -0
  154. package/assets/guardrails/domain/healthcare/dependency-policy.domain.md +11 -0
  155. package/assets/guardrails/domain/healthcare/dicom.md +15 -0
  156. package/assets/guardrails/domain/healthcare/domain-constitution.md +119 -0
  157. package/assets/guardrails/domain/healthcare/domain-driven-design.domain.md +28 -0
  158. package/assets/guardrails/domain/healthcare/eventing.domain.md +18 -0
  159. package/assets/guardrails/domain/healthcare/fhir.md +36 -0
  160. package/assets/guardrails/domain/healthcare/folder-structure.domain.md +25 -0
  161. package/assets/guardrails/domain/healthcare/hipaa.md +34 -0
  162. package/assets/guardrails/domain/healthcare/hl7.md +15 -0
  163. package/assets/guardrails/domain/healthcare/ihe-conformance.md +31 -0
  164. package/assets/guardrails/domain/healthcare/integration-terms.md +21 -0
  165. package/assets/guardrails/domain/healthcare/persistence.domain.md +20 -0
  166. package/assets/guardrails/domain/healthcare/terminology.md +30 -0
  167. package/assets/guardrails/domain/healthcare/testing.domain.md +15 -0
  168. package/assets/guardrails/domain/healthcare/ux/accessibility-standards.md +36 -0
  169. package/assets/guardrails/domain/healthcare/ux/content-design.md +32 -0
  170. package/assets/guardrails/domain/healthcare/ux/interaction-states.md +44 -0
  171. package/assets/guardrails/domain/healthcare/ux/journey-mapping.md +40 -0
  172. package/assets/guardrails/domain/healthcare/vertical-slices.domain.md +18 -0
  173. package/assets/guardrails/domain/healthcare/zero-trust.domain.md +12 -0
  174. package/assets/guardrails/domain/pharma/ai-rules.domain.md +32 -0
  175. package/assets/guardrails/domain/pharma/api-versioning.domain.md +20 -0
  176. package/assets/guardrails/domain/pharma/atc.md +22 -0
  177. package/assets/guardrails/domain/pharma/auditing.domain.md +90 -0
  178. package/assets/guardrails/domain/pharma/authorization.domain.md +25 -0
  179. package/assets/guardrails/domain/pharma/coding-standards.domain.md +28 -0
  180. package/assets/guardrails/domain/pharma/cqrs.domain.md +16 -0
  181. package/assets/guardrails/domain/pharma/data-classification.domain.md +24 -0
  182. package/assets/guardrails/domain/pharma/data-provenance.md +21 -0
  183. package/assets/guardrails/domain/pharma/dependency-policy.domain.md +11 -0
  184. package/assets/guardrails/domain/pharma/domain-constitution.md +113 -0
  185. package/assets/guardrails/domain/pharma/domain-driven-design.domain.md +36 -0
  186. package/assets/guardrails/domain/pharma/domain-requirements.sample.md +103 -0
  187. package/assets/guardrails/domain/pharma/eventing.domain.md +18 -0
  188. package/assets/guardrails/domain/pharma/fhir.md +31 -0
  189. package/assets/guardrails/domain/pharma/folder-structure.domain.md +26 -0
  190. package/assets/guardrails/domain/pharma/hl7.md +17 -0
  191. package/assets/guardrails/domain/pharma/idmp.md +26 -0
  192. package/assets/guardrails/domain/pharma/integration-terms.md +21 -0
  193. package/assets/guardrails/domain/pharma/ndc.md +28 -0
  194. package/assets/guardrails/domain/pharma/persistence.domain.md +23 -0
  195. package/assets/guardrails/domain/pharma/pharma-terms.md +27 -0
  196. package/assets/guardrails/domain/pharma/product-lifecycle.domain.md +30 -0
  197. package/assets/guardrails/domain/pharma/regulatory-compliance.md +19 -0
  198. package/assets/guardrails/domain/pharma/terminology.md +32 -0
  199. package/assets/guardrails/domain/pharma/testing.domain.md +19 -0
  200. package/assets/guardrails/domain/pharma/vertical-slices.domain.md +17 -0
  201. package/assets/guardrails/domain/pharma/zero-trust.domain.md +12 -0
  202. package/assets/guardrails/projects/dotnet-default-project/domain/domain-requirements.md +58 -0
  203. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +113 -0
  204. package/assets/guardrails/projects/dotnet-default-project/frontend/ui-requirements.md +43 -0
  205. package/assets/guardrails/projects/dotnet-default-project/service/technical-requirements.md +40 -0
  206. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +263 -0
  207. package/assets/guardrails/requirement-spec.txt +27 -0
  208. package/assets/guardrails/technology/dotnet-webapi/PACKAGING.md +184 -0
  209. package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +44 -0
  210. package/assets/guardrails/technology/dotnet-webapi/auditing.md +96 -0
  211. package/assets/guardrails/technology/dotnet-webapi/authorization-matrix.md +14 -0
  212. package/assets/guardrails/technology/dotnet-webapi/authorization.md +39 -0
  213. package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +54 -0
  214. package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +87 -0
  215. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +63 -0
  216. package/assets/guardrails/technology/dotnet-webapi/data-classification.md +38 -0
  217. package/assets/guardrails/technology/dotnet-webapi/dependency-policy.md +40 -0
  218. package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +91 -0
  219. package/assets/guardrails/technology/dotnet-webapi/eventing.md +57 -0
  220. package/assets/guardrails/technology/dotnet-webapi/examples/api-versioning.md +75 -0
  221. package/assets/guardrails/technology/dotnet-webapi/examples/auditing.md +84 -0
  222. package/assets/guardrails/technology/dotnet-webapi/examples/authorization.md +68 -0
  223. package/assets/guardrails/technology/dotnet-webapi/examples/clean-architecture.md +37 -0
  224. package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +97 -0
  225. package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +141 -0
  226. package/assets/guardrails/technology/dotnet-webapi/examples/eventing.md +46 -0
  227. package/assets/guardrails/technology/dotnet-webapi/examples/persistence.md +207 -0
  228. package/assets/guardrails/technology/dotnet-webapi/examples/vertical-slices.md +151 -0
  229. package/assets/guardrails/technology/dotnet-webapi/examples/zero-trust.md +48 -0
  230. package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +61 -0
  231. package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +47 -0
  232. package/assets/guardrails/technology/dotnet-webapi/persistence.md +82 -0
  233. package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +162 -0
  234. package/assets/guardrails/technology/dotnet-webapi/testing.md +55 -0
  235. package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +70 -0
  236. package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +48 -0
  237. package/assets/guardrails/technology/java-spring/README.md +62 -0
  238. package/assets/guardrails/technology/nextjs/accessibility-implementation.md +56 -0
  239. package/assets/guardrails/technology/nextjs/api-client.md +56 -0
  240. package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +174 -0
  241. package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +203 -0
  242. package/assets/guardrails/technology/nextjs/auth-session.md +68 -0
  243. package/assets/guardrails/technology/nextjs/client-security.md +69 -0
  244. package/assets/guardrails/technology/nextjs/component-architecture.md +60 -0
  245. package/assets/guardrails/technology/nextjs/component-conventions.md +58 -0
  246. package/assets/guardrails/technology/nextjs/design-system.md +60 -0
  247. package/assets/guardrails/technology/nextjs/error-handling.md +72 -0
  248. package/assets/guardrails/technology/nextjs/forms-and-validation.md +75 -0
  249. package/assets/guardrails/technology/nextjs/frontend-testing.md +94 -0
  250. package/assets/guardrails/technology/nextjs/i18n.md +57 -0
  251. package/assets/guardrails/technology/nextjs/observability.md +67 -0
  252. package/assets/guardrails/technology/nextjs/performance-budgets.md +66 -0
  253. package/assets/guardrails/technology/nextjs/routing-navigation.md +52 -0
  254. package/assets/guardrails/technology/nextjs/state-management.md +70 -0
  255. package/assets/guardrails/technology/nextjs/ui-constitution.md +166 -0
  256. package/package.json +1 -1
  257. package/assets/docs/pharma-constitution.my-ref.md +0 -86
@@ -0,0 +1,68 @@
1
+ # Authorization Standards — canonical example
2
+
3
+ > The worked `<SampleProjectName>` example for
4
+ > `guardrails/technology/dotnet-webapi/authorization.md`. It is reference material for the phases
5
+ > that generate code — mapped for `design` — and carries no rule of its own: where this
6
+ > example and the rule file disagree, the rule file wins.
7
+ >
8
+ > It is deliberately absent from the `## /aw-spec-constitution` set. The constitution is a
9
+ > thin authority document — one declarative line and a `→` pointer per principle — and has no
10
+ > use for sample code.
11
+
12
+ ## Canonical example (agent reference — `<SampleProjectName>` sample)
13
+
14
+ Secure endpoint — declare auth scheme + roles in `Configure()`, and the security/cache policy
15
+ in `GetSecurityCachePolicy()`:
16
+
17
+ ```csharp
18
+ public override void Configure()
19
+ {
20
+ Post("/projects");
21
+ AuthSchemes("Bearer"); // JWT from the external IdP
22
+ Roles("Admin"); // role-based gate (combine with scope checks for FHIR — see .domain)
23
+ Summary(s => s.Summary = "Create a new project");
24
+ Options(o => o.WithTags("02 - Project - (FastEndpoints)"));
25
+ }
26
+
27
+ protected override SecurityCachePolicy GetSecurityCachePolicy() => new()
28
+ {
29
+ SecurityLevel = SecurityLevel.Internal, // Internal | Restricted (admin/sensitive)
30
+ CachePolicy = CachePolicy.NoStore // NoStore for mutating/sensitive; PrivateCache for cacheable reads
31
+ };
32
+ ```
33
+
34
+ > Mutating endpoints use `CachePolicy.NoStore`. Admin/audit endpoints (e.g. list-including-deleted)
35
+ > use `SecurityLevel.Restricted`. `AllowAnonymous()` is only for infrastructure/discovery routes
36
+ > or pre-merge scaffolding (the template ships endpoints scaffolded with `AllowAnonymous()` and
37
+ > the `AuthSchemes`/`Roles` lines commented — replace them before merging to a protected branch).
38
+
39
+ Composition — external IdP only (no local identity store), role-based authorization:
40
+
41
+ ```csharp
42
+ // <SampleProjectName>.Service/Program.cs
43
+ builder.Services.AddAppWeaverSecurity(security =>
44
+ {
45
+ security
46
+ .AddAuthentication(auth =>
47
+ {
48
+ if (authProvider == "EntraExternalID")
49
+ auth.UseEntraExternalID(o => { o.TenantId = cfg["EntraExternalID:TenantId"]!;
50
+ o.ClientId = cfg["EntraExternalID:ClientId"]!; }, cfg);
51
+ else
52
+ auth.UseAzureAdB2C(o => { o.Domain = cfg["AzureAdB2C:Domain"]!;
53
+ o.ClientId = cfg["AzureAdB2C:ClientId"]!;
54
+ o.SignUpSignInPolicyId = cfg["AzureAdB2C:SignUpSignInPolicyId"]!; }, cfg);
55
+ })
56
+ .AddAuthorization(authz => authz.UseRoleBased(o => o.AdminRole = "Admin"));
57
+ // NOTE: no .AddIdentity(...) — the IdP is the source of truth (see [[zero-trust]])
58
+ }, options => { }, configuration: cfg);
59
+
60
+ // ... pipeline order ...
61
+ app.UseAuthentication();
62
+ app.UseAuthorization();
63
+ app.UseFastEndpoints();
64
+ ```
65
+
66
+ ## Related
67
+
68
+ - Rule file: `guardrails/technology/dotnet-webapi/authorization.md`
@@ -0,0 +1,37 @@
1
+ # Clean Architecture Standards — canonical example
2
+
3
+ > The worked `<SampleProjectName>` example for
4
+ > `guardrails/technology/dotnet-webapi/clean-architecture.md`. It is reference material for the
5
+ > phases that generate code — mapped for `design` and `build` — and carries no rule of its
6
+ > own: where this example and the rule file disagree, the rule file wins.
7
+ >
8
+ > It is deliberately absent from the `## /aw-spec-constitution` set. The constitution is a
9
+ > thin authority document — one declarative line and a `→` pointer per principle — and has no
10
+ > use for sample code.
11
+
12
+ ## Canonical example (agent reference — `<SampleProjectName>` sample)
13
+
14
+ The dependency rule in practice — the **API** layer depends inward via `IMediator` only
15
+ (never `IRepository`/`DbContext`); the **Application** layer depends on Domain abstractions
16
+ (`IRepository<Project>`), never on concrete EF Core types:
17
+
18
+ ```csharp
19
+ // API layer — <SampleProjectName>.Service/Projects/Features/CreateProject/CreateProjectEndpoint.cs
20
+ public class CreateProjectEndpoint(IMediator _mediator)
21
+ : SecureFastEndpoint<CreateProjectRequest, ProjectDto>
22
+ {
23
+ protected override Task<Result<ProjectDto>> ExecuteAsync(
24
+ CreateProjectRequest req, CancellationToken ct)
25
+ => _mediator.Send(new CreateProjectCommand(
26
+ req.Code, req.Name, req.Description, req.StartDate, req.EndDate, req.OwnerId), ct);
27
+ }
28
+
29
+ // Application layer — depends on Domain abstractions, not EF Core
30
+ public class CreateProjectCommandHandler(
31
+ IRepository<Project> _repo, IUnitOfWork _uow, IPublisher _eventBus)
32
+ : ICommandHandler<CreateProjectCommand, Result<ProjectDto>> { /* ... */ }
33
+ ```
34
+
35
+ ## Related
36
+
37
+ - Rule file: `guardrails/technology/dotnet-webapi/clean-architecture.md`
@@ -0,0 +1,97 @@
1
+ # CQRS + Mediator Standards — canonical example
2
+
3
+ > The worked `<SampleProjectName>` example for `guardrails/technology/dotnet-webapi/cqrs.md`. It
4
+ > is reference material for the phases that generate code — mapped for `design` — and carries
5
+ > no rule of its own: where this example and the rule file disagree, the rule file wins.
6
+ >
7
+ > It is deliberately absent from the `## /aw-spec-constitution` set. The constitution is a
8
+ > thin authority document — one declarative line and a `→` pointer per principle — and has no
9
+ > use for sample code.
10
+
11
+ ## Canonical example (agent reference — `<SampleProjectName>` sample)
12
+
13
+ Command as a `record`, write handler with transaction + event publication, and a read handler
14
+ returning `Result.Failure(...)` for the expected not-found outcome (no exceptions):
15
+
16
+ ```csharp
17
+ // Command — record implementing ICommand<Result<TDto>>
18
+ public record CreateProjectCommand(
19
+ string Code, string Name, string? Description,
20
+ DateOnly StartDate, DateOnly? EndDate, Guid? OwnerId
21
+ ) : ICommand<Result<ProjectDto>>;
22
+
23
+ // Write handler — primary-ctor DI, transaction, publish + clear events, Result<T>
24
+ public class CreateProjectCommandHandler(
25
+ IRepository<Project> _repo, IUnitOfWork _uow, IPublisher _eventBus
26
+ ) : ICommandHandler<CreateProjectCommand, Result<ProjectDto>>
27
+ {
28
+ public async ValueTask<Result<ProjectDto>> Handle(
29
+ CreateProjectCommand request, CancellationToken ct)
30
+ => await _uow.ExecuteInTransactionAsync(async () =>
31
+ {
32
+ var ownerId = request.OwnerId.HasValue
33
+ ? TaskUserId.From(request.OwnerId.Value) : (TaskUserId?)null;
34
+ var entity = new Project(request.Code, request.Name, request.StartDate, ownerId);
35
+
36
+ await _repo.AddAsync(entity, ct);
37
+ await _uow.SaveChangesAsync(ct);
38
+
39
+ foreach (var e in entity.DomainEvents) await _eventBus.Publish(e, ct);
40
+ entity.ClearDomainEvents();
41
+
42
+ return Result<ProjectDto>.Ok(entity.ToDto());
43
+ }, IsolationLevel.ReadCommitted, ct);
44
+ }
45
+
46
+ // Read handler — IReadRepository + Specification; not-found is a Result, not an exception
47
+ public class GetProjectByIdQueryHandler(IReadRepository<Project> _repo)
48
+ : IQueryHandler<GetProjectByIdQuery, Result<ProjectDto>>
49
+ {
50
+ public async ValueTask<Result<ProjectDto>> Handle(
51
+ GetProjectByIdQuery request, CancellationToken ct)
52
+ {
53
+ Guard.NotDefault(request.Id, nameof(request.Id));
54
+ var entity = await _repo.FirstOrDefaultAsync(
55
+ new ProjectWithTasksSpec(ProjectId.From(request.Id)), ct);
56
+
57
+ return entity is null
58
+ ? Result<ProjectDto>.Failure(new Error(
59
+ "project.not_found", $"Project {request.Id} not found", 404, "projects"))
60
+ : Result<ProjectDto>.Ok(entity.ToDto());
61
+ }
62
+ }
63
+ ```
64
+
65
+ Commands with no return payload use `ICommand<Result>` (e.g. delete) and return
66
+ `Result.Success()` / `Result.Failure(...)`:
67
+
68
+ ```csharp
69
+ public record DeleteProjectCommand(Guid Id, string RowVersionBase64) : ICommand<Result>;
70
+
71
+ public class DeleteProjectCommandHandler(IRepository<Project> _repo, IUnitOfWork _uow, IPublisher _bus)
72
+ : ICommandHandler<DeleteProjectCommand, Result> { /* ... returns Result.Success() */ }
73
+ ```
74
+
75
+ Composition — register the mediator with the logging + validation pipeline behaviors,
76
+ the validators, and the in-process event bus (one-time startup wiring):
77
+
78
+ ```csharp
79
+ // <SampleProjectName>.Service/Extensions/ApplicationServiceRegistration.cs
80
+ services.AddMediator(builder =>
81
+ {
82
+ builder.UseDefault()
83
+ .UseBehaviors()
84
+ .AddLoggingBehavior()
85
+ .AddFluentValidationBehavior(); // domain projects also add PHI-audit — see [[cqrs.domain]]
86
+ }, options => options.Provider = "Default");
87
+
88
+ services.AddAppWeaverMediatorHandlersFrom(typeof(ApplicationServiceRegistration));
89
+ services.AddValidatorsFromAssembly(typeof(ApplicationServiceRegistration).Assembly);
90
+
91
+ services.AddEventBus(b => b.UseDefaultEventBus(o =>
92
+ o.RegisterServicesFromAssembly(typeof(ApplicationServiceRegistration).Assembly)));
93
+ ```
94
+
95
+ ## Related
96
+
97
+ - Rule file: `guardrails/technology/dotnet-webapi/cqrs.md`
@@ -0,0 +1,141 @@
1
+ # Domain-Driven Design Standards (Tactical Patterns) — canonical example
2
+
3
+ > The worked `<SampleProjectName>` example for
4
+ > `guardrails/technology/dotnet-webapi/domain-driven-design.md`. It is reference material for the
5
+ > phases that generate code — mapped for `specify` — and carries no rule of its own: where
6
+ > this example and the rule file disagree, the rule file wins.
7
+ >
8
+ > It is deliberately absent from the `## /aw-spec-constitution` set. The constitution is a
9
+ > thin authority document — one declarative line and a `→` pointer per principle — and has no
10
+ > use for sample code.
11
+
12
+ ## Canonical example (agent reference — `<SampleProjectName>` sample)
13
+
14
+ Aggregate root with a strongly-typed Vogen ID, owned child collection, concurrency token,
15
+ domain events, and an invariant enforced **inside** the aggregate:
16
+
17
+ ```csharp
18
+ // <SampleProjectName>.Service/Projects/Domain/Project.cs
19
+ [ValueObject<Guid>]
20
+ public partial struct ProjectId { }
21
+
22
+ public class Project :
23
+ EntityBase<ProjectId>, IAggregateRoot, IFullAuditedObject, IFieldAudited,
24
+ ISoftDeletable, IConcurrencyTracked, IHasDomainEvents, INamedEntity<ProjectId>
25
+ {
26
+ private readonly List<ProjectTask> _tasks = new();
27
+
28
+ public string Name { get; set; } = default!; // INamedEntity
29
+ public ProjectCode Code { get; private set; } = default!; // value object
30
+ public ProjectStatus Status { get; private set; }
31
+ public TaskUserId? OwnerId { get; private set; } // cross-aggregate ref by ID only
32
+ public IReadOnlyCollection<ProjectTask> Tasks => _tasks.AsReadOnly();
33
+
34
+ public byte[] RowVersion { get; set; } = Array.Empty<byte>(); // IConcurrencyTracked
35
+
36
+ private readonly List<DomainEventBase> _domainEvents = new();
37
+ [NotMapped] public IEnumerable<DomainEventBase> DomainEvents => _domainEvents.AsReadOnly();
38
+ public void ClearDomainEvents() => _domainEvents.Clear();
39
+
40
+ private Project() { } // EF Core ctor
41
+
42
+ public Project(string code, string name, DateOnly startDate, TaskUserId? ownerId)
43
+ {
44
+ Id = ProjectId.From(Guid.NewGuid());
45
+ Code = ProjectCode.From(code); // VO validates in its ctor
46
+ Name = ProjectTitle.From(name).Value;
47
+ Status = ProjectStatus.Planning;
48
+ OwnerId = ownerId;
49
+ AddDomainEvent(new ProjectCreatedEvent(Id.Value, Code.Value, Name));
50
+ }
51
+
52
+ // Invariant lives in the aggregate — never in the handler/validator.
53
+ public ProjectTask AddTask(string title, TaskPriority priority)
54
+ {
55
+ if (Status is ProjectStatus.Cancelled or ProjectStatus.Archived)
56
+ throw new DomainException("Cannot add tasks to a Cancelled or Archived project.");
57
+ var task = new ProjectTask(Id, title, priority);
58
+ _tasks.Add(task);
59
+ AddDomainEvent(new TaskCreatedEvent(Id.Value, task.Id.Value, task.Title.Value));
60
+ return task;
61
+ }
62
+
63
+ private void AddDomainEvent(DomainEventBase e) => _domainEvents.Add(e);
64
+ }
65
+ ```
66
+
67
+ Value object — sealed, private ctor, validates in the ctor, `From(...)` factory,
68
+ throws `DomainException`:
69
+
70
+ ```csharp
71
+ // <SampleProjectName>.Service/Projects/Domain/ProjectCode.cs
72
+ public sealed class ProjectCode : IEquatable<ProjectCode>
73
+ {
74
+ private static readonly Regex _valid = new(@"^[A-Z0-9][A-Z0-9\-]{1,14}$", RegexOptions.Compiled);
75
+ public string Value { get; private set; } = default!;
76
+
77
+ private ProjectCode() { }
78
+ private ProjectCode(string value)
79
+ {
80
+ if (string.IsNullOrWhiteSpace(value))
81
+ throw new DomainException("Project code cannot be empty.");
82
+ var trimmed = value.Trim().ToUpperInvariant();
83
+ if (!_valid.IsMatch(trimmed))
84
+ throw new DomainException("Project code must be uppercase alphanumeric with hyphens.");
85
+ Value = trimmed;
86
+ }
87
+
88
+ public static ProjectCode From(string value) => new(value);
89
+ public bool Equals(ProjectCode? o) => o is not null && Value == o.Value;
90
+ public override bool Equals(object? obj) => Equals(obj as ProjectCode);
91
+ public override int GetHashCode() => HashCode.Combine(Value);
92
+ }
93
+ ```
94
+
95
+ Child entity — implements `IAggregateChild`, exposes `GetAggregateRoot()`, has only `private`
96
+ setters, and is mutated **only** through methods the parent aggregate calls:
97
+
98
+ ```csharp
99
+ // <SampleProjectName>.Service/Projects/Domain/ProjectTask.cs
100
+ [ValueObject<Guid>]
101
+ public partial struct ProjectTaskId { }
102
+
103
+ public class ProjectTask : EntityBase<ProjectTaskId>, IAggregateChild
104
+ {
105
+ public ProjectId ProjectId { get; private set; } // FK back to the root
106
+ public TaskTitle Title { get; private set; } = default!; // value object
107
+ public ProjectTaskStatus Status { get; private set; }
108
+ public TaskUserId? AssigneeId { get; private set; } // cross-aggregate ref by ID
109
+
110
+ public Project Project { get; private set; } = default!; // EF-only nav, private setter
111
+ public object GetAggregateRoot() => Project; // IAggregateChild
112
+
113
+ private ProjectTask() { } // EF Core ctor
114
+ public ProjectTask(ProjectId projectId, string title, TaskPriority priority)
115
+ {
116
+ Id = ProjectTaskId.From(Guid.NewGuid());
117
+ ProjectId = projectId;
118
+ Title = TaskTitle.From(title);
119
+ Status = ProjectTaskStatus.Open;
120
+ Priority = priority;
121
+ }
122
+
123
+ // State-machine invariant lives on the child; the parent calls it (project.ChangeTaskStatus).
124
+ public void ChangeStatus(ProjectTaskStatus newStatus)
125
+ {
126
+ if (Status == ProjectTaskStatus.Cancelled)
127
+ throw new DomainException("A cancelled task cannot be re-opened.");
128
+ Status = newStatus;
129
+ }
130
+
131
+ public void AssignTo(TaskUserId assigneeId) => AssigneeId = assigneeId;
132
+ }
133
+ ```
134
+
135
+ > The aggregate root mutates children only through its own methods (e.g. `Project.AssignTask`
136
+ > calls `task.AssignTo(...)` and raises the domain event) — callers never touch `ProjectTask`
137
+ > directly. EF mapping for the owned collection is in [[persistence]].
138
+
139
+ ## Related
140
+
141
+ - Rule file: `guardrails/technology/dotnet-webapi/domain-driven-design.md`
@@ -0,0 +1,46 @@
1
+ # Eventing Standards — canonical example
2
+
3
+ > The worked `<SampleProjectName>` example for `guardrails/technology/dotnet-webapi/eventing.md`.
4
+ > It is reference material for the phases that generate code — mapped for `design` — and
5
+ > carries no rule of its own: where this example and the rule file disagree, the rule file
6
+ > wins.
7
+ >
8
+ > It is deliberately absent from the `## /aw-spec-constitution` set. The constitution is a
9
+ > thin authority document — one declarative line and a `→` pointer per principle — and has no
10
+ > use for sample code.
11
+
12
+ ## Canonical example (agent reference — `<SampleProjectName>` sample)
13
+
14
+ Immutable domain event, raised inside the aggregate, published explicitly by the handler:
15
+
16
+ ```csharp
17
+ // <SampleProjectName>.Service/Projects/Domain/Events/ProjectCreatedEvent.cs
18
+ public class ProjectCreatedEvent(Guid projectId, string code, string name) : DomainEventBase
19
+ {
20
+ public Guid ProjectId { get; } = projectId; // get-only — immutable
21
+ public string Code { get; } = code;
22
+ public string Name { get; } = name;
23
+ }
24
+ ```
25
+
26
+ ```csharp
27
+ // Raised inside the aggregate method (never in the handler):
28
+ AddDomainEvent(new ProjectCreatedEvent(Id.Value, Code.Value, Name));
29
+
30
+ // Published by the command handler after SaveChanges, then cleared:
31
+ foreach (var domainEvent in entity.DomainEvents)
32
+ await _eventBus.Publish(domainEvent, ct); // IPublisher — in-process today, broker tomorrow
33
+ entity.ClearDomainEvents();
34
+ ```
35
+
36
+ Composition — register the event bus (handlers discovered from the assembly):
37
+
38
+ ```csharp
39
+ // <SampleProjectName>.Service/Extensions/ApplicationServiceRegistration.cs
40
+ services.AddEventBus(b => b.UseDefaultEventBus(o =>
41
+ o.RegisterServicesFromAssembly(typeof(ApplicationServiceRegistration).Assembly)));
42
+ ```
43
+
44
+ ## Related
45
+
46
+ - Rule file: `guardrails/technology/dotnet-webapi/eventing.md`
@@ -0,0 +1,207 @@
1
+ # Persistence Standards — canonical example
2
+
3
+ > The worked `<SampleProjectName>` example for
4
+ > `guardrails/technology/dotnet-webapi/persistence.md`. It is reference material for the phases
5
+ > that generate code — mapped for `design` and `run` — and carries no rule of its own: where
6
+ > this example and the rule file disagree, the rule file wins.
7
+ >
8
+ > It is deliberately absent from the `## /aw-spec-constitution` set. The constitution is a
9
+ > thin authority document — one declarative line and a `→` pointer per principle — and has no
10
+ > use for sample code.
11
+
12
+ ## Canonical example (agent reference — `<SampleProjectName>` sample)
13
+
14
+ EF Core configuration (Vogen ID + value object conversions, unique business key,
15
+ cross-aggregate FK stored as a raw `Guid` with no navigation):
16
+
17
+ ```csharp
18
+ // <SampleProjectName>.Service/Projects/Persistence/ProjectConfiguration.cs
19
+ public class ProjectConfiguration : IEntityTypeConfiguration<Project>
20
+ {
21
+ public void Configure(EntityTypeBuilder<Project> builder)
22
+ {
23
+ builder.ToTable("Projects");
24
+ builder.HasKey(e => e.Id);
25
+
26
+ // Strongly-typed Vogen ID -> Guid, never DB-generated
27
+ builder.Property(e => e.Id)
28
+ .HasConversion(id => id.Value, v => ProjectId.From(v))
29
+ .ValueGeneratedNever();
30
+
31
+ // Value object -> column + unique business key
32
+ builder.Property(e => e.Code)
33
+ .HasConversion(c => c.Value, v => ProjectCode.From(v))
34
+ .HasColumnName("Code").HasMaxLength(20).IsRequired();
35
+ builder.HasIndex(e => e.Code).IsUnique();
36
+
37
+ builder.Property(e => e.Status).HasConversion<string>().HasMaxLength(30).IsRequired();
38
+
39
+ // Cross-aggregate FK as nullable Guid — NO EF navigation to TaskUser
40
+ builder.Property(e => e.OwnerId)
41
+ .HasConversion(
42
+ id => id.HasValue ? id.Value.Value : (Guid?)null,
43
+ v => v.HasValue ? TaskUserId.From(v.Value) : (TaskUserId?)null)
44
+ .HasColumnName("OwnerId");
45
+ }
46
+ }
47
+ ```
48
+
49
+ Composable specification (filter + include + paging):
50
+
51
+ ```csharp
52
+ // {Module}/Domain/Specifications/ProjectSearchSpec.cs
53
+ public sealed class ProjectSearchSpec : Specification<Project>
54
+ {
55
+ public ProjectSearchSpec(string? search, ProjectStatus? status = null)
56
+ {
57
+ Query.Include(p => p.Tasks);
58
+ if (!string.IsNullOrWhiteSpace(search))
59
+ {
60
+ var s = search.Trim().ToLowerInvariant();
61
+ Query.Where(p => p.Code.Value.ToLower().Contains(s) || p.Name.ToLower().Contains(s));
62
+ }
63
+ if (status.HasValue) Query.Where(p => p.Status == status.Value);
64
+ }
65
+
66
+ public ProjectSearchSpec WithPaging(int pageNumber, int pageSize)
67
+ {
68
+ Query.Skip(Math.Max(0, (pageNumber - 1) * pageSize)).Take(Math.Max(1, pageSize));
69
+ return this;
70
+ }
71
+ }
72
+ ```
73
+
74
+ Read handler using the spec + typed ordering helper, returning `PagedResult<T>`:
75
+
76
+ ```csharp
77
+ var spec = new ProjectSearchSpec(request.Search, request.Status)
78
+ .OrderByIdDesc<ProjectSearchSpec, Project, ProjectId>(); // typed helper, not ad-hoc OrderBy
79
+
80
+ var total = await _repo.CountAsync(spec, ct);
81
+ var items = await _repo.ListAsync(spec.WithPaging(request.PageNumber, request.PageSize), ct);
82
+
83
+ return Result<PagedResult<ProjectDto>>.Ok(new PagedResult<ProjectDto>
84
+ {
85
+ Items = items.ToDtoList(), TotalCount = total,
86
+ PageNumber = request.PageNumber, PageSize = request.PageSize
87
+ });
88
+ ```
89
+
90
+ Owned child-entity configuration (separate table, FK + cascade, soft-delete mirror filter,
91
+ no EF navigation to other aggregates):
92
+
93
+ ```csharp
94
+ // {Module}/Persistence/ProjectTaskConfiguration.cs
95
+ public class ProjectTaskConfiguration : IEntityTypeConfiguration<ProjectTask>
96
+ {
97
+ public void Configure(EntityTypeBuilder<ProjectTask> builder)
98
+ {
99
+ builder.ToTable("ProjectTasks");
100
+ builder.HasKey(e => e.Id);
101
+ builder.Property(e => e.Id)
102
+ .HasConversion(id => id.Value, v => ProjectTaskId.From(v)).ValueGeneratedNever();
103
+
104
+ builder.Property(e => e.ProjectId) // FK back to the root
105
+ .HasConversion(id => id.Value, v => ProjectId.From(v)).IsRequired();
106
+
107
+ builder.Property(e => e.Title) // Vogen value object
108
+ .HasConversion(t => t.Value, v => TaskTitle.From(v)).HasMaxLength(300).IsRequired();
109
+ builder.Property(e => e.Status).HasConversion<string>().HasMaxLength(30).IsRequired();
110
+
111
+ // Cross-aggregate ref stored as raw Guid — NO navigation to TaskUser
112
+ builder.Property(e => e.AssigneeId)
113
+ .HasConversion(
114
+ id => id.HasValue ? id.Value.Value : (Guid?)null,
115
+ v => v.HasValue ? TaskUserId.From(v.Value) : (TaskUserId?)null);
116
+
117
+ builder.HasOne(e => e.Project).WithMany(p => p.Tasks) // owned collection
118
+ .HasForeignKey(e => e.ProjectId).OnDelete(DeleteBehavior.Cascade);
119
+
120
+ // Mirror the parent's ISoftDeletable filter onto the required dependent
121
+ builder.HasQueryFilter(t => !t.Project.IsDeleted);
122
+ builder.HasIndex(e => e.ProjectId);
123
+ builder.HasIndex(e => e.AssigneeId);
124
+ }
125
+ }
126
+ ```
127
+
128
+ DbContext — `ContextAwareDbContext`, assembly-scanned configs, provider-aware `timestamptz`,
129
+ cross-cutting behaviors, and the client-managed concurrency token override:
130
+
131
+ ```csharp
132
+ // <SampleProjectName>.Service/Infrastructure/ApplicationDbContext.cs
133
+ public class ApplicationDbContext(
134
+ DbContextOptions<ApplicationDbContext> options, ICurrentTenant? tenant = null)
135
+ : ContextAwareDbContext<ApplicationDbContext>(options, tenant)
136
+ {
137
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
138
+ {
139
+ modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
140
+
141
+ if (Database.ProviderName == "Npgsql.EntityFrameworkCore.PostgreSQL")
142
+ foreach (var et in modelBuilder.Model.GetEntityTypes())
143
+ foreach (var p in et.GetProperties()
144
+ .Where(p => p.ClrType == typeof(DateTimeOffset) || p.ClrType == typeof(DateTimeOffset?)))
145
+ p.SetColumnType("timestamptz");
146
+
147
+ modelBuilder.ApplyCrossCuttingBehaviors(this); // soft-delete + tenant filters + audit
148
+
149
+ // Client-managed concurrency token (works on SQLite + PostgreSQL; not SQL Server IsRowVersion)
150
+ foreach (var et in modelBuilder.Model.GetEntityTypes())
151
+ if (typeof(IConcurrencyTracked).IsAssignableFrom(et.ClrType))
152
+ modelBuilder.Entity(et.ClrType).Property<byte[]>("RowVersion")
153
+ .IsConcurrencyToken().ValueGeneratedNever();
154
+
155
+ base.OnModelCreating(modelBuilder);
156
+ }
157
+
158
+ public DbSet<Project> Projects => Set<Project>();
159
+ public DbSet<ProjectTask> ProjectTasks => Set<ProjectTask>();
160
+ }
161
+ ```
162
+
163
+ Soft-delete: logical delete via `MarkAsDeleted(...)` + `DeleteAsync` (never a hard delete),
164
+ and a separate `IgnoreQueryFilters()` spec for admin/audit reads:
165
+
166
+ ```csharp
167
+ // Delete handler — soft delete + concurrency guard (returns Result, no DTO)
168
+ var entity = await _repo.GetByIdAsync(ProjectId.From(request.Id), ct);
169
+ if (entity is null)
170
+ return Result.Failure(new Error("project.not_found", $"Project {request.Id} not found", 404, "projects"));
171
+ // ... concurrency token check (see api-versioning) ...
172
+ entity.MarkAsDeleted(); // sets IsDeleted/DeletedOn + raises ProjectDeletedEvent
173
+ await _repo.DeleteAsync(entity, ct); // framework translates to UPDATE, not DELETE
174
+
175
+ // Admin/audit spec that bypasses the global soft-delete filter
176
+ public sealed class ProjectSearchIncludeDeletedSpec : Specification<Project>
177
+ {
178
+ public ProjectSearchIncludeDeletedSpec(string? search, ProjectStatus? status = null)
179
+ {
180
+ Query.Include(p => p.Tasks).IgnoreQueryFilters(); // include soft-deleted rows
181
+ // ... same Where filters as ProjectSearchSpec ...
182
+ }
183
+ }
184
+ ```
185
+
186
+ Composition — repository + cross-cutting behaviors registered once at startup:
187
+
188
+ ```csharp
189
+ // <SampleProjectName>.Service/Extensions/InfrastructureServiceRegistration.cs
190
+ services.AddRepositories(builder =>
191
+ {
192
+ builder
193
+ .UseSingleDbContextEFRepositories<ApplicationDbContext>(db => db.UseSqlite(/* or UseNpgsql */)
194
+ .ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)))
195
+ .EnableTenancy()
196
+ .EnableSoftDelete()
197
+ .EnableClientManagedConcurrency()
198
+ .EnableAuditing().EnableAdvancedAuditing() // see [[auditing]]
199
+ .Done();
200
+ }, options => options.Provider = "EfCore");
201
+
202
+ services.TryAddScoped(typeof(IUnitOfWork), typeof(EfUnitOfWork<ApplicationDbContext>));
203
+ ```
204
+
205
+ ## Related
206
+
207
+ - Rule file: `guardrails/technology/dotnet-webapi/persistence.md`