"Create or modify a CoreEx Application-layer policy class. USE FOR: new policy class in Application/Policies/, EnsureExists guard (referenced entity must exist), business rule guards requiring I/O, multi-method policy, composing policies in Result<T> pipelines. DO NOT USE FOR: synchronous validation rules (use coreex-validator), Infrastructure repositories (use coreex-repository), application service scaffolding (use coreex-app-service)."
Resources
1Install
npx skillscat add avanade/coreex/coreex-policy Install via the SkillsCat registry.
CoreEx: Policy
Guides you through creating or modifying a CoreEx Application-layer policy class in Application/Policies/. Policies encapsulate domain-level guard logic that requires async I/O — adapter or repository calls — providing a named, independently testable home for rules that cannot live in a synchronous validator or directly in the domain model.
When to Use
- A guard check needs to call an adapter (external domain) or a repository — synchronous validators cannot do this
- An "ensure referenced entity exists" check that must translate
NotFoundErrorinto a user-visible validation error - A business-rule check (e.g. entity is inactive, quota exceeded) that requires loading state
- Multiple related guards that share the same adapter/repository dependency — group them in one policy class
When Not to Use
- Synchronous field validation (no I/O) — use the
coreex-validatorskill - Guard logic that belongs entirely inside the domain aggregate — keep it in the aggregate method
- Service scaffolding — use the
coreex-app-serviceskill; it covers how to wire a policy into a service
Quick Reference
Clarifying questions before writing any code:
0. Resolve rop-enabled (Result<T> vs exception style — policies compose into Result<T> pipelines) from the solution-root AGENTS.md Feature Configuration before asking the rest; only prompt for what is unrecorded and re-state resolved values for confirmation.
- What entity / concept is being guarded? (names the policy class)
- What type of guard? EnsureExists / business rule / state check
- Which adapter or repository does the policy call?
- Should the policy return the loaded entity (
Result<T>) or just a pass/fail (Result)? - Multiple guard methods in one class, or one method only?
Key rules at a glance:
- Policy lives in
Application/Policies/{Name}Policy.cs - Not DI-registered — instantiated at the call site:
new {Name}Policy(_adapter).EnsureExistsAsync(id) - Constructor accepts adapters and/or repositories already injected into the calling service
- Always returns
ResultorResult<T>— never throws (unless adapter itself throws unexpectedly) - EnsureExists: translate
r.IsNotFoundError→Result.ValidationError(...)— do not letNotFoundExceptionpropagate as-is MessageItem.CreateErrorMessage(nameof(param), "{Entity} was not found.")produces a field-level validation errorLTextstatic fields hold localizable entity names used inside message text — e.g.new LText("Product")as the{0}substitution in"{0} was not found.". The property name (nameof(param)) is always a plainstring?— neverLText- Guard methods can return the loaded entity as
Result<Contracts.T>so callers can use it without a second fetch - Always
.ConfigureAwait(false)on everyawait - Always generate a matching
{Name}PolicyTests.csin*.Test.Unit/Policies/— cover the success path (expectIsSuccess) for each guard method, plus its failure path:IsValidationErrorforEnsureExists-style not-found guards,IsBusinessErrorfor state/condition guards (e.g.EnsureActive)
For full workflow and code examples see `references/workflow.md`.
Key References
- `/.github/instructions/coreex-application-services.instructions.md` — policies, adapters, DI registration principle, Result<T> pipeline operators
- Related skills: `coreex-app-service` (wires the policy into a service), `coreex-adapter` + `coreex-repository` (the dependencies a policy calls), `coreex-validator` (synchronous, no-I/O guard sibling)
- Illustrative examples (CoreEx sample — not present in your project):
- `ProductPolicy` — EnsureExists translating NotFoundError → ValidationError
- `BasketService.cs` —
ItemAddAsyncshows a policy in aResult.GoAsync().ThenAsAsync()pipeline