Attributes
The full catalog of Elarion attributes — what each one triggers, its parameters, and their defaults.
A lookup-only catalog of every Elarion attribute, grouped by area. Each entry lists the package, what it applies to, a one-line "triggers", and a parameter table (type, default, meaning). For the behavior behind each attribute see the linked capability or concept page.
Parameters are listed as constructor arguments and init properties. Defaults are the literal
defaults baked into the attribute. Enum value tables follow the group they belong to.
Modules & DI
[AppModule]
- Package:
Elarion.Abstractions· Applies to: class - Triggers: groups handlers/services/jobs/consumers under a module boundary and emits the
{ModuleType}ElarionModuleServices.ConfigureDefaultServicessibling plus host bootstrapping; feature modules are gated byModules:{Name}:Enabled.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string (ctor) | required | Unique module name; used for the feature-flag key Modules:{Name}:Enabled and logging. |
Kind | AppModuleKind (init) | Feature | Feature (optional, can be disabled) or Core (always enabled, ignores feature flags). |
DependsOn | string? (init) | null | Comma-separated module names this module depends on; the generator topologically sorts so dependencies initialize first. |
The class may optionally implement convention-based statics: ConfigureServices(IServiceCollection, IConfiguration), MapEndpoints(IEndpointRouteBuilder), GetJsonTypeInfoResolver(), and ConfigureEndpointGroup(IEndpointRouteBuilder).
AppModuleKind: Feature (= 0), Core (= 1).
See Modules.
[ClientFeatures]
- Package:
Elarion.Abstractions(Modules) · Applies to: class (alongside[AppModule]) - Triggers: opts named feature flags and variants into the generated client-capability manifest. The list is the complete client-visible vocabulary for that module: internal flags are not exposed by discovery, and a disabled module contributes none. A listed name may be a pure UI flag; it does not need a server-side
[FeatureGate].
| Parameter | Type | Default | Meaning |
|---|---|---|---|
features | params string[] (ctor) → Features (IReadOnlyList<string>) | empty | Feature flag or variant names the session endpoint may evaluate and return for this module. |
See Client capabilities.
[Service]
- Package:
Elarion.Abstractions· Applies to: class - Triggers: source-generated DI registration. Without explicit service types, directly implemented interfaces are used as contracts; with none, the implementation type registers as itself. Base-class interfaces are not treated as explicit contracts.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
serviceTypes | params Type[] (ctor) → IReadOnlyList<Type> ServiceTypes | empty | Explicit contract types to register; when empty, contracts are inferred from directly implemented interfaces. |
Scope | ServiceScope (init) | Scoped | DI lifetime. |
ServiceScope: Scoped, Singleton, Transient.
See Services.
[GenerateContractSetRegistration]
- Package:
Elarion.Abstractions· Applies to: method (astatic partialextension method the generator implements) - Triggers: runs the
ContractSetRegistrationGeneratorto compose every implementation of one contract, unconditionally — the module-less counterpart to[Service]for infrastructure seams (protocol packet bindings, codec catalogs, pipeline stages) that live in host assemblies with no[AppModule]. The host authors, names, and places the composition method; the generator fills in the body. Discovery is compilation-local: every non-abstract, non-generic class in the declaring assembly assignable to the contract is registered viaTryAddEnumerable(calling the method twice never duplicates the set). The method is pulled by the host's composition root, exactly once, unconditionally; no bootstrapper invokes it and no configuration gates it, so a transport's routing table cannot be silently emptied by a module switch. The method shape is fixed (ELSG019otherwise):static partial IServiceCollection Name(this IServiceCollection services)on a non-generic static partial class, without a hand-written implementation.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
contractType | Type (ctor) → ContractType | — | The composed contract; must be an interface or abstract class (open generics rejected). |
Scope | ServiceScope (init) | Singleton | DI lifetime for every implementation — infrastructure seams are boot-composed. |
public static partial class PacketBindingRegistrations {
[GenerateContractSetRegistration(typeof(IPacketBinding))]
public static partial IServiceCollection AddPacketBindings(this IServiceCollection services);
}
// host composition root:
services.AddPacketBindings();Diagnostics ELSG014 (contract not an interface/abstract class) / ELSG015 (empty set) / ELSG016 (same contract declared twice) / ELSG017 (generic implementation) / ELSG018 (implementation also a [Service] under the same contract) / ELSG019 (invalid method shape). See Services — module services vs. contract sets.
[DecoratorList]
- Package:
Elarion.Abstractions(Pipeline) · Applies to: class - Triggers: declares the decorator types for an application-owned pipeline profile attribute so the generator can read the list at compile time. (Pipeline profile attributes themselves, e.g.
DefaultPipelineAttribute, are application-defined, not framework-shipped.)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
decorators | params Type[] (ctor) → Decorators | empty | Ordered decorator types (open generics), applied outermost-first: the first type is the outermost decorator. |
See Decorator pipelines.
Transports
[Handler]
- Package:
Elarion.Abstractions· Applies to: class - Triggers: marks a handler as a named operation on the transport-neutral request/reply bus (the handler must implement
IHandler<TRequest, TResponse>). The operation name is declared once and shared by every dispatcher transport;Transportsselects JSON-RPC, MCP, or both. JSON-RPC and MCP are thin adapters over the one bus, so a handler is "define once, choose surfaces via theTransportsflag". REST is a separate opt-in via[HttpEndpoint].
The operation name is optional: [Handler] alone is valid. When omitted, the name is inferred by convention as {module}.{operation}, where operation is the handler type name minus a Handler/Command/Query/Request suffix, camelCased (e.g. module Clients + CreateClient → clients.createClient). Supply an explicit name for stable public/wire contracts.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string? (ctor) → Name | optional | The operation name, e.g. "clients.create". When omitted, inferred by convention as {module}.{operation}. |
Transports | HandlerTransports (init, [Flags]) | All (JsonRpc | Mcp | Connection) | Which dispatcher transports expose the handler. |
Scope | ServiceScope (init) | Scoped | The handler's registration lifetime — the same vocabulary and semantics as [Service(Scope = …)]. Singleton removes scope participation from dispatch (the low-allocation choice, ADR-0066) and is compile-time verified: every constructor dependency must be provably singleton (ELSG011/ELSG012) and the pipeline must not attach scope-dependent features (ELSG013). |
HandlerTransports: JsonRpc (= 1), Mcp (= 2), Connection (= 4, bidirectional connection dispatch), All (= JsonRpc \| Mcp \| Connection).
[HandlerTelemetry]
- Package:
Elarion.Abstractions· Applies to: class (handler or module), assembly - Triggers: declares how much always-on observability the generated pipeline gives handlers. Leveled, nearest declaration wins: handler class → module class → assembly →
Full. With the effective modeNone, the registration generator does not emit the observability decorator at all — no span, no execution metric, no context enrichment, no log scope, no decorator object on the hot path (ADR-0066). Failures are unaffected: they remainResultvalues and exceptions still propagate to the transport. The typical use is a hot module opting down once while one cold handler inside it re-enables by declaringFullon itself.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
mode | HandlerTelemetryMode (ctor) → Mode | required | Full (= 0, the default when the attribute is absent) or None (= 1). |
See Telemetry and Connections — the low-allocation dispatch profile.
[McpHandler]
- Package:
Elarion.Abstractions· Applies to: class - Triggers: optional MCP tool-name customization for a handler already on the MCP surface (its
[Handler]TransportsincludesMcp). Purely additive — carries no enable/disable flag. Tool/parameter descriptions come fromSystem.ComponentModel.DescriptionAttribute, not this attribute.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
ToolName | string? (init) | null | Overrides the MCP tool name; when null/empty the name is derived from the handler's operation name via the host's transform (e.g. "clients.create" → "clients_create"). |
See MCP.
[HttpEndpoint]
- Package:
Elarion.Abstractions· Applies to: class - Triggers: marks a handler as an HTTP/REST endpoint;
AppModuleDiscoveryGeneratoremits the matchingMapGet/MapPost/… registration as an AOT-safeRequestDelegatewith generator-owned binding (ADR-0071). The handler must implementIHandler<TRequest, Result<TResponse>>. With no explicit verb the verb is inferred from the request's CQRS marker (ICommand→ POST,IQuery→ GET); a request with neither marker requires an explicit verb (elseELHTTP004). Carries no ASP.NET Core dependency; a handler may carry both this and[Handler].
The attribute has two constructors:
| Constructor | Parameters | Effect |
|---|---|---|
HttpEndpointAttribute(string route) | route (required) | Maps at route, inferring the verb from the request's ICommand/IQuery marker. |
HttpEndpointAttribute(HttpVerb verb, string route) | verb, route (both required) | Maps at route with the explicit verb; sets HasVerb = true. |
Read-only properties:
| Property | Type | Default | Meaning |
|---|---|---|---|
Route | string | required | The route pattern, e.g. "clients/{id}". |
Verb | HttpVerb | default(HttpVerb) = Get | The explicit HTTP method, or the default when HasVerb is false. |
HasVerb | bool | false | Whether Verb was set explicitly; when false the verb is inferred from the CQRS marker. |
HttpVerb: Get, Post, Put, Patch, Delete.
A handler responding with Result<ElarionFile> (the in-memory small-file payload — content bytes +
content type + file name, in Elarion.Abstractions) is mapped as a file download instead of JSON; the
same handler may also carry [Handler], where the payload rides the canonical base64 envelope over
JSON-RPC/MCP (and the generated TypeScript client maps it to a native File). Large files use the
staged-blob tier instead (ADR-0039). See
Files and
File payloads.
See HTTP endpoints.
[GenerateModuleBootstrapper]
- Package:
Elarion.AspNetCore· Applies to: assembly - Triggers: marks the host assembly to have the fixed-name
ElarionBootstrapperstatic generated byAppModuleDiscoveryGenerator(in the host's root namespace; see ADR-0018) — the single transport-wiring path; emits per-module and aggregateMap/Addmethods for HTTP, JSON-RPC, and MCP, all module-scoped and feature-flag gated. Marker only — no parameters.
See Hosting.
[ModuleEndpoints]
- Package:
Elarion.AspNetCore· Applies to: class (static) - Triggers: declares endpoint hooks for a module from outside its assembly (typically the host, on behalf of a web-free module assembly). The class declares the same convention hooks a module type may declare —
static MapEndpoints(IEndpointRouteBuilder)and/orstatic ConfigureEndpointGroup(IEndpointRouteBuilder)— andAppModuleDiscoveryGeneratorcalls them inside the named module's feature gate inMapElarionEndpoints, composed with the module's own hooks (module first, contributors in stable type-name order, group hooks chained). Discovered in the host compilation and via referenced assemblies' Elarion manifests. An unknown module name warnsELMOD004(hooks skipped); a class with no recognized hook warnsELMOD005.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
moduleName | string (ctor) → ModuleName | required | The [AppModule] name the hooks contribute to. |
Scheduling
[ScheduledJob]
- Package:
Elarion.Abstractions· Applies to: method | class - Triggers: marks a method as a compile-time scheduled job or a class as a runtime-schedulable job type. Methods must declare exactly one of
FixedRate,FixedDelay, orCron; runtime-schedulable classes may declare at most one. String values accept${Config:Key}and${Config:Key:-default}placeholders re-resolved per occurrence.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string (ctor) → Name | required | Stable unique job name used in logs, telemetry, and runtime scheduling. |
FixedRate | string? (init) | null | Grid-aligned interval between due times regardless of run duration. Accepts TimeSpan text or suffixes (50ms, 30s, 15m, 6h, 1d). |
FixedDelay | string? (init) | null | Delay between completion of one run and start of the next (polling-loop style). Same duration format; misfire policy does not apply. |
Cron | string? (init) | null | Cron expression with 5 (minute-level) or 6 (second-level) fields, evaluated in TimeZone. "-" disables the schedule. |
TimeZone | string? (init) | null (UTC) | Time-zone id used to evaluate Cron; passed to TimeZoneInfo.FindSystemTimeZoneById after placeholder resolution (prefer IANA ids). |
InitialDelay | string? (init) | null | Optional delay before the first execution (same format as FixedRate). Not valid with Cron. |
RunOnStart | bool (init) | true | Whether interval jobs run once immediately at host start. Not valid with Cron. |
Group | string? (init) | null | Optional key serializing jobs that must not run concurrently; jobs sharing a non-empty group share a serialization gate. |
Overlap | ScheduledJobOverlap (init) | Skip | How occurrences behave when another is already active. |
MisfirePolicy | ScheduledJobMisfirePolicy (init) | FireOnce | How fixed-rate/cron schedules handle missed in-process occurrences. |
MaxConcurrentRuns | int (init) | 0 | Max concurrently executing occurrences when Overlap = AllowConcurrent. 0 means no job-local cap (global scheduler limit still applies); negative is rejected by the generator. |
Enabled | string? (init) | null (always enabled) | "true", "false", or a placeholder, evaluated per occurrence. An unconfigured placeholder without inline default means enabled; an unparseable value means disabled. |
Placement | JobPlacement (init) | Cluster | Multi-node execution: Cluster = exactly one node per occurrence when a cross-instance coordinator is registered; EveryNode = every node runs every occurrence (for jobs maintaining process-local in-memory state). Identical on a single node. |
ScheduledJobOverlap: Skip, Queue, AllowConcurrent.
ScheduledJobMisfirePolicy: FireOnce (one overdue, skip intermediate), Skip, CatchUp.
See Scheduling, Schedules, and Overlap and misfire.
Events
[ConsumeEvent]
- Package:
Elarion.Abstractions· Applies to: method | class - Triggers: marks an event consumer, discovered by the event-consumer generator and registered as an
EventSubscriptionDescriptor.- Handler form (preferred): on a class implementing
IHandler<TEvent>(sugar overIHandler<TEvent, Result<Unit>>) whose request type is the event; runs through the full decorator pipeline. Always a fan-out subscriber — the event bus is pub/sub-only. A non-UnitResult<T>return is rejected (ELEVT005). - Method form: on a method of a
[Service]class (no pipeline); plane taken from the message type marker (IDomainEvent/IIntegrationEvent). Always a fan-out subscriber, returningvoid/Task/ValueTaskor the non-genericResult/Task<Result>/ValueTask<Result>(a failedResult→EventConsumerFailedException). AResult<T>with a value is request/reply and is rejected.
- Handler form (preferred): on a class implementing
| Parameter | Type | Default | Meaning |
|---|---|---|---|
Order | int (init) | 0 | Relative invocation order among fan-out subscribers of the same event, ascending. Equal orders run in a stable generator-determined sequence. |
See Consuming events and Backends.
Actors
[Actor]
- Package:
Elarion.Actors· Applies to: class - Triggers: marks a plain class as an in-memory actor: its public async methods (
Task/Task<T>/ValueTask/ValueTask<T>) become request/reply methods on a source-generated typed facade (I{Name}, class name minus theActorsuffix), while methods returningIAsyncEnumerable<T>become facade streams with a generated trailing cancellation token. Calls attach through the activation's mailbox; stream enumeration continues off-mailbox. Keyed when the constructor takes anIActorContext<TKey>(one activation per key, activated on first message, passivated when idle) orKeyTypeis set; otherwise a process singleton. Registered per module via the generatedAdd{Module}Actors; requires the assembly opt-in[assembly: GenerateActors](or[UseElarion]).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
Name | string? (init) | class name minus Actor | Logical actor name used in telemetry, logging, and the registration. |
KeyType | Type? (init) | null | Explicit key type for a keyed actor whose constructor does not take an IActorContext<TKey>; must match the context parameter when both are present (ELACT004). |
MailboxCapacity | int (init) | 0 (unbounded) | Bounded mailbox capacity. |
MailboxFullMode | ActorMailboxFullMode (init) | Wait | Full-mailbox behaviour: Wait (async backpressure) or Fail (ActorMailboxFullException). No drop modes — facade calls are request/reply. |
IdleTimeoutSeconds | double (init) | 0 (5 min) | Inactivity window before passivation; -1 disables passivation. |
CallTimeoutSeconds | double (init) | 0 (30 s) | End-to-end facade call timeout (queue wait + execution) failing with TimeoutException — the deadlock backstop for actor→actor call cycles; -1 disables it. |
Placement | ActorPlacementMode (init) | Local | SingleHome runs only on the actor-home holder; VirtualShards assigns a keyed actor to a fixed virtual-shard role lease. Register the corresponding PostgreSQL placement recipe; calls elsewhere fail with ActorNotHomedException and are never forwarded. |
ActorPlacementMode: Local (default), SingleHome, VirtualShards. ActorMailboxFullMode: Wait (default), Fail.
[Reentrant]
- Package:
Elarion.Actors· Applies to: class (alongside[Actor]) - Triggers: opts the actor into Orleans-style turn-based interleaving: while one message awaits, the mailbox may start another, but turns never run in parallel (an exclusive scheduler serializes every continuation). State observed across an
awaitmay have been changed by an interleaved message;ConfigureAwait(false)in the actor's own code escapes the scheduler and forfeits the guarantee (flagged byELACT006; libraries the actor calls may use it internally without harm).
[ActorKey]
- Package:
Elarion.Actors· Applies to: method (alongside[ConsumeEvent]on a keyed actor) - Triggers: names the consumed event's property that supplies the actor key when the generator cannot infer it (the event has zero or more than one property assignable to the actor's key type). Constructor arg
propertyName(usenameof); the property's type must be assignable to the key type, elseELACT008. Not needed when inference is unambiguous or the actor is a singleton.[ConsumeEvent]itself stays an actor-agnostic contract inElarion.Abstractions— the key selector is an actor concern, so it lives here (ADR-0046).
See Actors.
Caching
[Cacheable]
- Package:
Elarion.Abstractions· Applies to: class - Triggers: marks a handler as cacheable and supplies compile-time metadata to the handler-registration generator. Only successful
Result<T>responses are cached; failed results are returned but not stored.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
tags | params string[] (ctor) → Tags | empty | Logical cache tags for grouping and invalidation. |
DurationSeconds | int (init) | 60 | Entry lifetime in seconds for both distributed and local cache layers. |
Scope | HandlerCacheScope (init) | CurrentUser | Whether generated keys/tags are scoped to the current user or shared globally. |
KeyProperties | string[] (init) | [] | Request property names included in the generated key; when empty, all public request properties are used. Invalid names are reported at build time. |
HandlerCacheScope: CurrentUser (= 0), Global (= 1).
[CacheInvalidate]
- Package:
Elarion.Abstractions· Applies to: class - Triggers: marks a mutating handler as invalidating cached entries for the given tags. Invalidation runs only after the inner handler returns a successful
Result<T>; failed validation or business results do not evict cached reads.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
tags | params string[] (ctor) → Tags | empty | Logical cache tags to invalidate after a successful handler result. |
Scope | HandlerCacheScope (init) | CurrentUser | Whether invalidation applies to the current user's entries or globally shared entries. |
Runtime: the cache itself was extracted out of Elarion core (ADR-0017). [Cacheable]/[CacheInvalidate]
now require the Elarion.Caching package — the host wires the HybridCache-backed IHandlerCache with
services.AddElarionHandlerCaching(). Without it the attributes are inert.
See Caching.
Resilience
[ResiliencePolicy]
- Package:
Elarion.Abstractions· Applies to: class - Triggers: marks a static partial type as a source-generated resilience policy (a named execution behavior applied to handlers and scheduled jobs). Retry options create additional attempts after a handled exception;
Timeoutlimits each individual attempt (not a total deadline across retries).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string (ctor) → Name | required | Stable policy name used by [Resilient] and ScheduledJobOptions.ResiliencePolicy. |
MaxRetryAttempts | int (init) | 3 | Maximum retry attempts after the original attempt (0 = no retries; 3 = up to four total). Supplying this or another retry property enables retry generation. |
Delay | string (init) | "2s" | Base delay before a retry, e.g. 200ms, 2s, 5m, or TimeSpan text. May be changed by Backoff, capped by MaxDelay, randomized by UseJitter. |
Backoff | ResilienceBackoffType (init) | Constant | How retry delays grow between attempts. |
MaxDelay | string? (init) | null | Optional maximum retry delay after applying Backoff; used with linear/exponential backoff to cap waits. |
UseJitter | bool (init) | false | Whether retry delays include jitter so many failures do not retry at the same instant. |
Timeout | string? (init) | null | Optional per-attempt timeout, e.g. 30s or TimeSpan text. Not a total deadline across retries; cancels the attempt token. |
ResilienceBackoffType: Constant, Linear, Exponential.
[Resilient]
- Package:
Elarion.Abstractions· Applies to: class | method - Triggers: applies a named resilience policy to a generated handler or scheduled-job invocation. On handlers and compile-time jobs the policy executes inline around the current invocation; runtime scheduled jobs can alternatively use
ScheduledJobOptionsfor scheduler-deferred retry.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
policyName | string (ctor) → PolicyName | required | Stable policy name registered by a [ResiliencePolicy] type or a manual resilience pipeline registration. |
Runtime: the resilience runner was extracted out of Elarion core (ADR-0017). [Resilient]
handlers and scheduler-deferred retries now require the Elarion.Resilience package — the host wires the
Microsoft/Polly-backed IResiliencePipelineRunner with services.AddElarionResilience(). Breaking:
AddElarionScheduler no longer auto-wires the runner, so deferred scheduler retries opt in through this package too.
See Resilience.
Feature flags
[FeatureGate]
- Package:
Elarion.Abstractions(Features) · Applies to: class (AllowMultiple, inherited) - Triggers: gates a handler behind one or more feature flags. The handler-registration generator auto-attaches the
FeatureGateDecorator<TRequest, TResponse>just inside the authorization gate so the flag is evaluated before the handler runs (before caching and the rest of the pipeline). When the gate is not satisfied the call short-circuits toAppError.NotFound(404) — a disabled feature is indistinguishable from a missing one, so the feature name is never leaked (MS-MVC-style "hide the roadmap"). Transport-neutral: identical under JSON-RPC, MCP, and HTTP. Stacking multiple[FeatureGate]attributes ANDs them. The flag is evaluated through theIFeatureFlagServiceseam; the default provider ships inElarion.FeatureFlags.OpenFeature.
The attribute has two constructors:
| Constructor | Parameters | Effect |
|---|---|---|
FeatureGateAttribute(params string[] features) | features | Gates on features with Requirement = FeatureRequirement.All. |
FeatureGateAttribute(FeatureRequirement requirement, params string[] features) | requirement, features | Gates on features with the explicit requirement. |
Read-only / settable properties:
| Property | Type | Default | Meaning |
|---|---|---|---|
Features | IReadOnlyList<string> | required (≥ 1 non-blank) | The feature names this gate evaluates. A blank/empty set has no effect (ELFEAT002). |
Requirement | FeatureRequirement | All | All = every feature must be on (AND); Any = at least one must be on (OR). |
Negate | bool (init) | false | When true, the gate is satisfied when the feature(s) are off (an inverse / kill-switch gate). |
FeatureRequirement: All, Any.
The gated handler's response must be able to represent failure (Result<T> / Result); otherwise the gate
cannot short-circuit and ELFEAT001 is reported. A [FeatureGate] with no non-blank feature name reports
ELFEAT002. Module flags (Modules:{Name}:Enabled) are compose-time (the module disappears); feature gates
are runtime (gradual rollouts, targeting, kill switches without a redeploy).
See Feature flags.
[FeatureVariant]
- Package:
Elarion.Abstractions(Features) · Applies to: class (single, not inherited) - Triggers: a modifier on a
[Service]registration that binds multiple implementations of one contract; a named feature flag's allocated variant picks which implementation resolves per request. The implementation class must also carry[Service](which declares the contract(s) and lifetime) — otherwiseELVAR007. The contract is not repeated here: it is whatever[Service]registers under, and each registered contract becomes variant-resolved. Consumers inject the plain contract transparently — a handler injecting it directly is registered behind an async-resolving proxy that awaits the right variant for the current user; outside a handler constructor, injectIVariantServiceProvider<TService>. Runtime seams:IFeatureVariantService+IVariantServiceProvider<T>; theVariantServiceRegistrationGeneratoremits the wiring (ADR-0019).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
feature | string (ctor) → Feature | required | The single feature whose allocated variant selects the implementation. All variants of one contract must name the same feature (ELVAR004). |
Variant | string? (init) | null | The variant key this implementation serves. Omit it on exactly one implementation to mark it the default (used when no variant is allocated); a contract without a default warns ELVAR003, and a duplicate variant key reports ELVAR001. |
IsDefault | bool (init) | false | Marks this implementation as a named default: it serves its Variant name and every unallocated/unknown allocation. More than one default per contract reports ELVAR001. |
Diagnostics: ELVAR001 (duplicate variant key), ELVAR003 (no default implementation — warning), ELVAR004
(contract bound to more than one selector), ELVAR005 (blank feature name — warning), ELVAR006 (generic
implementation unsupported), ELVAR007 (missing [Service]), ELVAR008 (contract also bound by
[ConfigurationVariant] — one selection axis per contract). ELVAR002 is intentionally unused.
See Feature flags.
[ConfigurationVariant]
- Package:
Elarion.Abstractions(Features) · Applies to: class (single, not inherited) - Triggers: the configuration-selected sibling of
[FeatureVariant]— a modifier on a[Service]registration that binds multiple implementations of one contract, with a plainIConfigurationvalue picking which implementation resolves. Selection is a synchronous configuration read, so there is no async-resolving proxy and no per-scope warm-up: any consumer (handler, service, any assembly) injects the contract directly, each new DI scope observes the current value, and an open scope keeps the implementation it started with. Any configuration provider drives the switch —appsettings.json(withreloadOnChange), environment variables, or the settings bridge (AddElarionSettingsConfiguration) for admin-writable, cluster-propagated runtime switching with no flag provider. The implementation class must also carry[Service](otherwiseELVAR007); the contract is whatever[Service]registers under. Runtime seam:IVariantServiceProvider<TService>(completes synchronously); theVariantServiceRegistrationGeneratoremits the wiring (ADR-0028).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
key | string (ctor) → Key | required | The configuration key whose value selects the implementation (e.g. "Email:Backend"). All variants of one contract must name the same key (ELVAR004), and a contract cannot mix [ConfigurationVariant] with [FeatureVariant] (ELVAR008). A blank key warns ELVAR009. |
Value | string? (init) | null | The configured value this implementation is selected for, matched case-insensitively. Omit it on exactly one implementation to mark it the default (used when the key is absent or its value matches no variant); a contract without a default warns ELVAR003, and a duplicate value — including case-only differences — reports ELVAR001. |
IsDefault | bool (init) | false | Marks this implementation as a named default: selectable by its Value and the fallback when the key is absent or unknown — so the default state stays writable by name (an admin switches back with "smtp" rather than by removing the key). Recommended for admin-facing switches; more than one default per contract reports ELVAR001. |
Diagnostics: ELVAR001 (duplicate value), ELVAR003 (no default implementation — warning), ELVAR004
(contract bound to more than one selector), ELVAR006 (generic implementation unsupported), ELVAR007
(missing [Service]), ELVAR008 (contract also bound by [FeatureVariant]), ELVAR009 (blank
configuration key — warning).
See Feature flags.
Idempotency
[Idempotent]
- Package:
Elarion.Abstractions(Idempotency) · Applies to: class (single, not inherited) - Triggers: marks a command handler as idempotent — a retried or duplicated request carrying the same idempotency key executes at most once and replays the first result. The handler-registration generator auto-attaches the
IdempotencyDecorator, which owns the unit-of-work transaction: it writes the key row atomically with the handler's business writes, lets a database unique constraint reject a duplicate, and replays the stored result. Applies only toICommandhandlers whose response can represent failure (Result<T>/Result). Declarative, transport-neutral, and provider-agnostic — the same shape as[Cacheable]/[FeatureGate].
| Parameter | Type | Default | Meaning |
|---|---|---|---|
RetentionHours | int (init) | 24 | How long a completed key is retained and replayable, in hours. |
KeyRequired | bool (init) | true | Whether a request without an idempotency key is rejected with a 400. Set false to run without idempotency when no key is supplied. |
Scope | IdempotencyScope (init) | CurrentUser | Whether the key is scoped per authenticated user or shared globally. |
Fingerprint | bool (init) | true | Whether a request fingerprint is stored so reusing the key with a different request body is rejected (422). |
ConflictBehavior | IdempotencyConflictBehavior (init) | Conflict | Behavior for a concurrent in-flight duplicate (default 409). |
StoreFailures | IdempotencyFailureStorage (init) | None | Whether definitive failures are stored and replayed (default success-only). |
IdempotencyScope: CurrentUser (= 0, isolated per authenticated user), Global (= 1, single shared namespace), Consumer (= 2, isolated per event consumer — the inbox; owner is the consuming handler's identity, key is the message id).
IdempotencyConflictBehavior: Conflict (= 0, fail fast with 409 then replay — the IETF/Stripe standard), WaitThenReplay (= 1, block on the key's lock until the first request commits, then replay).
IdempotencyFailureStorage: None (= 0, success-only — a failed result rolls back and stays retryable), Definitive (= 1, also store and replay definitive failures via a savepoint; transient failures stay retryable).
The [Idempotent] attribute, seams, decorator, and in-memory default store live in Elarion.Abstractions/Elarion;
the durable PostgreSQL key store ships in Elarion.Idempotency.EntityFrameworkCore
(AddElarionIdempotencyEntityFrameworkCore<TDbContext> + [GenerateElarionIdempotencyKeys], below), atop the EF
unit of work in Elarion.EntityFrameworkCore.UnitOfWork.
See Idempotency.
[AllowDuplicates]
- Package:
Elarion.Abstractions(Messaging) · Applies to: class (single, not inherited) - Triggers: declares that duplicate deliveries are harmless to this handler-form integration-event consumer, opting it out of the default-on inbox (ADR-0022). The inbox attaches automatically to every handler whose request is an
IIntegrationEvent— aConsumer-scopedIdempotencyDecoratorclaiming (consumer, message id) inside the consumer's own transaction, so a redelivered message is acknowledged instead of re-run (claims expire after 24 h, well above the outbox retry window). Declare[AllowDuplicates]when re-running is safe by construction — the effect is naturally idempotent (a conditional transition/upsert), or the only effect is a call to a downstream keyed onIEventContext.MessageId— and the plain transaction decorator returns. The consumer-side mirror of[AllowAnonymous]: a positive property declaration switching off a default guard. Inert on non-integration-event handlers (ELINBX001); domain-event consumers never have an inbox (exactly-once by atomicity); method-form consumers have no pipeline to attach to.
See Handling duplicates.
Auditing
[Auditable]
- Package:
Elarion.Abstractions(Auditing) · Applies to: class (single, not inherited) - Triggers: marks a handler for audit recording (ADR-0045) — every invocation produces one structured
AuditRecord(actor, action, resource, outcome, correlation, field-level changes). The handler-registration generator auto-attaches two decorators: the outer observer (just outside authorization, recording denials/failures on a detached path that survives the rollback) and the inner success recorder (inside the transaction, so the success record commits atomically with the business writes). Attachment is soft — no registeredIAuditTrail, no change to the pipeline. Declarative, transport-neutral, and provider-agnostic — the same shape as[Idempotent]/[FeatureGate].
| Parameter | Type | Default | Meaning |
|---|---|---|---|
Enabled | bool (init) | true | Set false to opt a handler out under [ElarionAuditDefaults]. |
Resource | string? (init) | null | Fallback resource type only (the record's ResourceType) — used when the handler does not call IAuditScope.SetResource. It never sets the resource id; SetResource(type, id) supplies both and its type wins, so don't set Resource when the handler calls SetResource. Use the same string as the handler's [RequirePermission(resource, …)] (the plural, K8s-RBAC resource name — clients, invoices) so authorization and audit share one vocabulary. |
See Audit trail.
[ElarionAuditDefaults]
- Package:
Elarion.Abstractions(Auditing) · Applies to: assembly or module class (single, not inherited) - Triggers: opts the scope into audit-by-default: every command handler is audited as if it carried
[Auditable], unless it opts out with[Auditable(Enabled = false)]. Queries and event consumers are never audited by defaults (read auditing is per-handler; a delivery scope has no caller). Mirrors[ElarionAuthorizationDefaults].
See Audit trail.
[Audited]
- Package:
Elarion.Abstractions(Auditing) · Applies to: entity class (single, not inherited) - Triggers: opts an entity into automatic change capture: while an audited handler runs, the EF change-capture interceptor records field-level
{property, oldValue, newValue}diffs for this entity on every flush, attached to the invocation's record. Opt-in on purpose (fail-closed PII stance; keeps framework tables out of capture).ExecuteUpdate/raw-SQL writes bypass the change tracker — the handler records those viaIAuditScope.AddChange.
See Audit trail.
[AuditIgnore]
- Package:
Elarion.Abstractions(Auditing) · Applies to: property (single, not inherited) - Triggers: excludes a property of an
[Audited]entity from automatic change capture — its values never appear in a record. For sensitive columns (password hashes, tokens, personal data) and noisy technical columns.
See Audit trail.
Validation
There is no Elarion validation attribute. Request validation is declared with the standard
System.ComponentModel.DataAnnotations attributes on the handler's request DTO — [Range],
[MinLength]/[MaxLength]/[Length]/[StringLength], [RegularExpression], [EmailAddress],
[Url], [Base64String] — with requiredness from nullability + the required modifier (no
[Required] needed). The handler-registration generator auto-attaches the framework
ValidationDecorator (just inside the feature gate) for any handler whose request type graph carries
validation attributes, and the same attributes are exported to the JSON-RPC schema, MCP tool schemas,
the OpenAPI document, and the generated Zod client. Reusable custom constraints subclass a mapped
attribute (e.g. [Slug] : RegularExpressionAttribute). Enforcement requires the opt-in
Elarion.Validation package (AddElarionValidation()); diagnostics ELVAL001/ELVAL002.
See Validation.
Authorization
Handler requirements are class attributes the handler-registration generator reads to auto-attach the AuthorizationDecorator (the outermost functional gate, just inside the observability decorator's handler span). Different attribute kinds AND together; multiple of one kind AND; OR lives inside a single [RequireClaim]. All are transport-neutral (identical under JSON-RPC, MCP, and HTTP) and evaluate against ICurrentUser — an unauthenticated caller yields AppError.Unauthorized (401), authenticated-but-denied yields AppError.Forbidden (403). The gated handler's response must be able to represent failure (Result<T>/Result) or ELAUTH001 is reported.
[RequirePermission]
- Package:
Elarion.Abstractions(Authorization) · Applies to: class (AllowMultiple, inherited) - Triggers: requires the principal to hold a permission to perform a verb on a resource — the Kubernetes-RBAC
(resource, verb)shape, sugar for[RequireClaim(PermissionClaimType, "{resource}.{verb}")]. Surfaced into the generated permission catalog (see[GeneratePermissionCatalog]).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
resource | string (ctor) → Resource | required | The resource the permission applies to, e.g. "properties". |
verb | string (ctor) → Verb | required | The action on the resource; a Verbs constant or any string (open vocabulary). |
Permission | string (read-only) | derived | The composed claim value enforced — {Resource}.{Verb} (separator "."). |
Verbs (constants, open vocabulary): Read ("read"), List ("list"), Create ("create"), Update ("update"), Write ("write"), Delete ("delete"), Manage ("manage").
[RequireRole]
- Package:
Elarion.Abstractions(Authorization) · Applies to: class (AllowMultiple, inherited) - Triggers: requires the principal to be in the named role. Also surfaced into the permission catalog.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
role | string (ctor) → Role | required | The required role name. |
[RequireClaim]
- Package:
Elarion.Abstractions(Authorization) · Applies to: class (AllowMultiple, inherited) - Triggers: requires the principal to carry a claim of the given type; with values supplied the principal must match at least one (OR). The general primitive
[RequirePermission]/[RequireRole]build on.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
claimType | string (ctor) → ClaimType | required | The claim type the principal must carry. |
allowedValues | params string[] (ctor) → AllowedValues (IReadOnlyList<string>) | empty | Accepted values (OR); empty means presence-only. |
[RequirePolicy]
- Package:
Elarion.Abstractions(Authorization) · Applies to: class (AllowMultiple, inherited) - Triggers: requires a named
IAuthorizationPolicy(an Elarion-native policy evaluated againstICurrentUser+ the request, not the ASP.NET policy engine) to pass.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
policy | string (ctor) → Policy | required | The policy name, matched against IAuthorizationPolicy.Name / [AuthorizationPolicy("name")]. |
[AllowAnonymous]
- Package:
Elarion.Abstractions(Authorization) · Applies to: class (single, inherited) - Triggers: marks a handler as public; authorization is skipped entirely and wins over any
Require*attribute and over an[ElarionAuthorizationDefaults]deny-by-default scope. Marker only — no parameters.
[AuthorizationPolicy]
- Package:
Elarion.Abstractions(Authorization) · Applies to: class (single, not inherited) - Triggers: marks an
IAuthorizationPolicyimplementation as a named, auto-registered policy (per module, like[Service]), referenced by[RequirePolicy("name")]. DiagnosticsELPOL001/ELPOL002.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string (ctor) → Name | required | The policy name [RequirePolicy] resolves. |
[ElarionAuthorizationDefaults]
- Package:
Elarion.Abstractions(Authorization) · Applies to: assembly | class (single, not inherited) - Triggers: flips the in-scope handlers to deny-by-default — every handler requires authentication unless
[AllowAnonymous]. Resolved most-specific-wins (a[AppModule]-class scope overrides the assembly scope), like[DefaultPipeline].
| Parameter | Type | Default | Meaning |
|---|---|---|---|
RequireAuthenticated | bool (init) | true | Whether in-scope handlers require an authenticated principal. |
[RequireResource]
- Package:
Elarion.Abstractions(Authorization) · Applies to: class (AllowMultiple, inherited) - Triggers: the per-resource point check (the BOLA-class "may I touch this one?" gate). The decorator reads the resource id from the request by a compile-checked path (no reflection) and calls the
IResourceAuthorizerseam; the shipped default authorizes from the grants table and fails closed when no backend is registered. An unresolvableIdpath isELAUTH002.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
resourceType | Type (ctor) → ResourceType | required | The resource type being accessed, e.g. typeof(Contact). |
Operation | string (init) | "read" | The operation name (an open ResourceOperation value). |
Id | string (init) | "Id" | Request property path naming the resource id, e.g. nameof(Query.Id) or a dotted path of existing properties. |
[ResourceFilter<TEntity>]
- Package:
Elarion.Paging· Applies to: class (generic attribute,where TEntity : class) - Triggers: the data-level list filter (Leg B). Declared on a dedicated partial class (the entity stays clean, like
[Keyset<T>]); the EF Core generator completes it as anIQueryAuthorizer<TEntity>whose predicate composes the rules asAND(scope rules) AND OR(grant rules)and pushes it into SQL viasource.WhereAuthorized(authorizer, currentUser)before paging. DiagnosticsELRES001–ELRES005.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
TEntity | type parameter (where TEntity : class) | required | The entity being authorized. |
OwnerProperty | string? (init) | null | A grant (OR): the row is visible when this column equals the caller's UserId. Type Guid/string/int/long. |
TenantProperty | string? (init) | null | A scope (AND): every visible row's column must equal the caller's tenant claim. Type Guid/string/int/long. |
TenantClaimType | string (init) | "tenant" | The claim type the tenant value is read from. |
Shared | bool (init) | false | A grant (OR): a correlated EXISTS over the resource-grants table for the caller's user or any of their roles, for the operation WhereAuthorized asks (defaults to Read). Makes the filter a scoped service (no static Specification); requires ResourceTypeName (else ELRES005) and a reference to Elarion.Authorization.EntityFrameworkCore. |
ResourceTypeName | string? (init) | null | The resource-type discriminator stored in the grants table (e.g. "Contact"), used by the Shared EXISTS. |
IdProperty | string (init) | "Id" | The entity key property whose stringified value matches the grant's resource id. Type Guid/string/int/long. |
Consume the filter by injecting IQueryAuthorizer<TEntity> (the form that works for Shared). A field-only
filter (OwnerProperty/TenantProperty, no Shared) is stateless, so the generator also exposes a static
Specification singleton. Both are auto-registered as IQueryAuthorizer<TEntity>, module-feature-gated.
See Authorization and Resource authorization.
EF Core
[EntityConfiguration]
- Package:
Elarion.EntityFrameworkCore· Applies to: class (anIEntityTypeConfiguration<TEntity>implementation) - Triggers: marks an
IEntityTypeConfiguration<TEntity>implementation as the single source of truth for an entity's participation; it drives both the generatedDbSet<TEntity>and theConfigure(...)application. The entity itself carries no marker (previously[DbEntity]) — a configured entity is a discovered entity. A plainIEntityTypeConfiguration<T>with no attribute is ignored (no DbSet, no Configure). A single[EntityConfiguration]class may implementIEntityTypeConfiguration<T>more than once; each implemented entity gets its ownDbSetand its ownConfigure(...)call. A class implementing noIEntityTypeConfiguration<T>is reported (ELEFC001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
scopes | params string[] (ctor) → Scopes (IReadOnlyList<string>) | empty | Optional context scopes this configuration belongs to. Omit scopes to participate in unscoped/global contexts. |
// Entities/Invoice.cs — plain class, no attribute
public sealed class Invoice { public Guid Id { get; set; } }
// Invoicing/InvoiceConfiguration.cs — the single source of truth
using Elarion.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
[EntityConfiguration]
public sealed class InvoiceConfiguration : IEntityTypeConfiguration<Invoice> {
public void Configure(EntityTypeBuilder<Invoice> builder) { /* ... */ }
}[GenerateDbSets]
- Package:
Elarion.EntityFrameworkCore· Applies to: class - Triggers: applied to a partial DbContext class; generates a
DbSet<T>property for each[EntityConfiguration]entity andConfigureEntities(ModelBuilder)directly onto that class (reflection-free, AOT-friendly directApplyConfiguration<T>calls).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
scopes | params string[] (ctor) → Scopes (IReadOnlyList<string>) | empty | Optional context scopes generated for this class. Omit scopes to include every [EntityConfiguration]; a scoped class includes only configurations whose scopes intersect. |
[GenerateElarionIdentity<TUser, TRole, TKey>]
- Package:
Elarion.EntityFrameworkCore.Identity· Applies to: class (generic attribute;TUser : IdentityUser<TKey>,TRole : IdentityRole<TKey>,TKey : IEquatable<TKey>) - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits the seven IdentityDbSets and applies the self-contained snake_case Identity model (keys, composite keys, indexes, table/column names) through the EF generator's per-feature model-config seam — noIdentityDbContextinheritance and noEFCore.NamingConventionsdependency. The web-free Identity model; the host wiring isAddElarionIdentityinElarion.AspNetCore.Identity. Requires[GenerateDbSets](elseELIDN001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column/index names use snake_case. |
Schema | string? (init) | null | The schema for all seven Identity tables, or null for the provider's default schema. |
TablePrefix | string? (init) | null | Optional prefix prepended verbatim to every Identity table name (e.g. "auth_" → auth_users). Because Identity spans seven tables, the prefix is the table-name override — there is no per-table name parameter. |
[GenerateElarionResourceGrants]
- Package:
Elarion.Authorization.EntityFrameworkCore· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits theDbSet<ResourceGrantEntity>and applies the grants model configuration — the table backing[ResourceFilter(Shared = true)]and the[RequireResource]point check. The hand-written equivalent ismodelBuilder.ApplyElarionResourceGrants(). Requires[GenerateDbSets](elseELRG001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column/index names use snake_case. |
TableName | string? (init) | null | The table name, or null for the default (elarion_resource_grants / ElarionResourceGrants depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Resource authorization and Identity.
[GenerateElarionIdempotencyKeys]
- Package:
Elarion.Idempotency.EntityFrameworkCore· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits theDbSet<IdempotencyKeyEntity>and applies the idempotency-keys model configuration through the EF generator's model-config seam — the table backing the durable[Idempotent]store. The hand-written equivalent ismodelBuilder.ApplyElarionIdempotencyKeys(). Mirrors[GenerateElarionResourceGrants]. Requires[GenerateDbSets](elseELIDEMEF001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column/index names use snake_case. |
TableName | string? (init) | null | The table name, or null for the default (elarion_idempotency_keys / ElarionIdempotencyKeys depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Idempotency.
[GenerateElarionAuditing]
- Package:
Elarion.Auditing.EntityFrameworkCore· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits theDbSet<AuditLogEntry>and applies the audit-log model configuration through the EF generator's model-config seam — the append-only table backing the durableIAuditTrail. The hand-written equivalent ismodelBuilder.UseElarionAuditing(). Mirrors[GenerateElarionIdempotencyKeys]. Requires[GenerateDbSets](elseELAUD001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column/index names use snake_case. |
TableName | string? (init) | null | The table name, or null for the default (elarion_audit_log / ElarionAuditLog depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Audit trail.
[GenerateElarionOutbox]
- Package:
Elarion.Messaging.Outbox· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emitsDbSet<OutboxMessage>and applies the transactional-outbox model configuration through the EF generator's model-config seam. The hand-written equivalent ismodelBuilder.UseElarionOutbox(). Requires[GenerateDbSets](elseELOBX001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column/index names use snake_case. |
TableName | string? (init) | null | The table name, or null for the default (elarion_outbox_messages / ElarionOutboxMessages depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Event bus backends.
[GenerateElarionSchedulerClaims]
- Package:
Elarion.Scheduling.EntityFrameworkCore· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits theDbSet<SchedulerClaimEntity>and applies the scheduler-claims model configuration through the EF generator's model-config seam — the table that makes each recurring occurrence execute on exactly one node (ADR-0025). The hand-written equivalent ismodelBuilder.UseElarionSchedulerClaims(). Mirrors[GenerateElarionIdempotencyKeys]. Requires[GenerateDbSets](elseELSCH001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column/index names use snake_case. |
TableName | string? (init) | null | The table name, or null for the default (elarion_scheduler_claims / ElarionSchedulerClaims depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Scheduling.
[GenerateElarionSettings]
- Package:
Elarion.Settings.EntityFrameworkCore· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits theDbSet<Setting>and applies the settings model configuration through the EF generator's model-config seam — the table backing the EF CoreISettingsStore. The hand-written equivalent ismodelBuilder.UseElarionSettings(). Mirrors[GenerateElarionIdempotencyKeys]. Requires[GenerateDbSets](elseELSET001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column/index names use snake_case. |
TableName | string? (init) | null | The table name, or null for the default (elarion_settings / ElarionSettings depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Settings.
[GenerateElarionBlobStorage]
- Package:
Elarion.Blobs.PostgreSql· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits theDbSet<StoredBlob>and applies the blob model configuration — both the metadata table and the content table (the content row type isinternal: mapped, but noDbSet) — through the EF generator's model-config seam. The hand-written equivalent ismodelBuilder.UseElarionBlobStorage(). Mirrors[GenerateElarionIdempotencyKeys]. Requires[GenerateDbSets](elseELBLB001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column/index names use snake_case. |
TableName | string? (init) | null | The metadata table name, or null for the default (stored_blobs / StoredBlobs depending on SnakeCase). |
ContentTableName | string? (init) | null | The content table name, or null for the default (blob_contents / BlobContents depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Blob storage.
[GenerateElarionStagedUploads]
- Package:
Elarion.Blobs.PostgreSql· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits theDbSet<StagedUploadRow>and applies the staged-upload model configuration through the EF generator's model-config seam — the table backing the durableIStagedUploadStorethat resumable transports (tus) stage into. The hand-written equivalent ismodelBuilder.UseElarionStagedUploads(). Mirrors[GenerateElarionBlobStorage], which maps the blob tables a completed upload is written to. Requires[GenerateDbSets](elseELBLB002).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column/index names use snake_case. |
TableName | string? (init) | null | The table name, or null for the default (staged_uploads / StagedUploads depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Blob uploads.
[GenerateElarionActorSnapshots]
- Package:
Elarion.Actors.PostgreSql· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits theDbSet<ActorSnapshotEntity>and applies the actor snapshot model configuration through the EF generator's model-config seam — the table backing theIActorSnapshotStorebehind everyIActorState<TState>(ADR-0047). The hand-written equivalent ismodelBuilder.UseElarionActorSnapshots(). Mirrors[GenerateElarionSettings]/[GenerateElarionIdempotencyKeys]. Requires[GenerateDbSets](elseELASN001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column names use snake_case. |
TableName | string? (init) | null | The table name, or null for the default (elarion_actor_snapshots / ElarionActorSnapshots depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Actor state and placement.
[GenerateElarionRoleLeases]
- Package:
Elarion.Coordination.PostgreSql· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits theDbSet<RoleLeaseEntity>and applies the role-lease model configuration through the EF generator's model-config seam — the leader-election table behindIRoleLease(ADR-0049) and therefore behind[Actor(Placement = ActorPlacementMode.SingleHome)](ADR-0048). The hand-written equivalent ismodelBuilder.UseElarionRoleLeases(). Requires[GenerateDbSets](elseELROLE001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column names use snake_case. |
TableName | string? (init) | null | The table name, or null for the default (elarion_role_leases / ElarionRoleLeases depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Actors — multiple instances and single-homing.
[GenerateElarionDeviceIdentity]
- Package:
Elarion.Devices.EntityFrameworkCore· Applies to: class - Triggers: on a
[GenerateDbSets]partial DbContext; the bundled generator emits theDbSet<DeviceKeyEntity>/DbSet<DevicePairingCodeEntity>and applies the device identity model configuration through the EF generator's model-config seam — the tables backingIDeviceKeyStoreandIPairingCodeStore(ADR-0054). The hand-written equivalent ismodelBuilder.UseElarionDeviceIdentity(). Mirrors[GenerateElarionActorSnapshots]/[GenerateElarionRoleLeases]. Requires[GenerateDbSets](elseELDEV001).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
SnakeCase | bool (init) | true | Whether table/column names use snake_case. |
KeyTableName | string? (init) | null | The device key table name, or null for the default (elarion_device_keys / ElarionDeviceKeys depending on SnakeCase). |
PairingCodeTableName | string? (init) | null | The pairing code table name, or null for the default (elarion_device_pairing_codes / ElarionDevicePairingCodes depending on SnakeCase). |
Schema | string? (init) | null | The schema, or null for the provider's default schema. |
See Device identity.
[Keyset<TEntity>]
- Package:
Elarion.Paging· Applies to: class (generic attribute,where TEntity : class) - Triggers: declares an ordered keyset (seek) definition for
TEntityon the annotated partial class. The EF Core generator fills the class with a reflection-freeIKeysetDefinition<TEntity>implementation (ordering, seek predicate, opaque cursor codec) plus a staticDefinitionsingleton, so handlers page viasource.ToKeysetPageAsync(request, MyKeyset.Definition, selector). Declared on a dedicated partial class (not the entity), so an entity may have any number of orderings. Non-partial or nested keyset classes are reported (ELKEY005).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
TEntity | type parameter (where TEntity : class) | required | The entity type being paginated. |
columns | params string[] (ctor) → Columns (IReadOnlyList<string>) | empty | Ordered keyset column names in precedence order; each names a property on TEntity. Ascending by default; a leading - marks the column descending (e.g. "-CreatedAt"). The final column should be unique (typically the primary key) for determinism. |
See Entity Framework and Pagination.
SQL row mapping (EF-free tier)
[SqlRecord]
- Package:
Elarion.Sql· Applies to: class, struct (records included) - Triggers: marks a row type for SQL mapper generation (ADR-0058). The bundled generator emits a sealed
{Type}SqlMapper : ISqlRowMapper<T>— column ordinals resolved by name once per result set (Ordinalsstruct), synchronous typedGetFieldValue<T>row reads, typedBindParameters(parameters named like columns),TableName/Columns.*constants (includingColumns.AllandColumns.AllParameters), a staticInstance, and a per-assemblyAddElarionSqlMappers()DI registration. Column names default to the snake_case of the property name; the table name defaults to the snake_case of the type name (no pluralization). Positional records construct through the primary constructor; nominal records withrequired/initmembers through an object initializer. Derived (get-only) members are skipped. There is no reflection fallback: an unmapped type or unsupported shape is a compile error (ELSQL001–ELSQL007).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
tableName | string? (ctor) | null | The table name, or null for the snake_case of the type name. |
[SqlColumn]
- Package:
Elarion.Sql· Applies to: property - Triggers: overrides the column name of a
[SqlRecord]property (default: snake_case of the property name). On a non-writable property this is an error (ELSQL007).
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string (ctor) | required | The column name as it appears in SQL. |
[SqlIgnore]
- Package:
Elarion.Sql· Applies to: property - Triggers: excludes a
[SqlRecord]property from mapping (neither read nor bound). Arequiredproperty cannot be ignored (ELSQL003).
[SqlJson]
- Package:
Elarion.Sql· Applies to: property - Triggers: maps a
[SqlRecord]property as a JSON column, (de)serialized through the canonical JSON accessor'sJsonTypeInfo<T>(IElarionJsonSerialization, ADR-0023) — the property type must be in a registeredJsonSerializerContext. A mapper with JSON columns takes the accessor as a constructor parameter instead of exposingInstance; the generated DI registration wires it. Under[UseElarionSql(Provider = SqlProvider.Npgsql)], parameters bind asNpgsqlDbType.Jsonb.
[UseElarionSql]
- Package:
Elarion.Sql· Applies to: assembly - Triggers: provider trigger for the SQL mapper generator (precedent:
[UseElarionEntityFrameworkCore]).Provider = SqlProvider.Npgsqlemits PostgreSQL-specific parameter typing (jsonbfor[SqlJson]columns); without it, emission stays provider-neutral.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
Provider | SqlProvider (init) | Portable | The provider to emit provider-specific code for (Portable or Npgsql). |
See SQL mapping.
Cross-module communication
[ModuleContract]
- Package:
Elarion.Abstractions(Modules) · Applies to: interface | class - Triggers: marks a module's published cross-module surface — the stable type other modules may depend on. Use it sparingly and deliberately: it is for genuine cross-module domain calls, not shared infrastructure (use a port outside the modules) or shared data (use the shared kernel). The owning module keeps the implementation
internal; other modules inject the contract.ModuleBoundaryAnalyzer(ELMOD002) is location-based — it allows a[ModuleContract]but reports any other cross-module reference to a type placed inside another module (an entity, DTO,[Service], handler, or[EntityConfiguration]). Types outside every module — the shared kernel and platform ports — are never flagged. Marker only — no parameters.
[ModuleApi]
- Package:
Elarion.Abstractions(Modules) · Applies to: class - Triggers: configures how a handler participates in its module's generated typed in-process API (see
[GenerateModuleApi]). A configurator, never a gate: every handler is in the default facade automatically (opt-out). Apply only to exclude a handler or to tag it into named scopes (additive). Scope vocabulary mirrors[EntityConfiguration]/[GenerateDbSets].
| Parameter | Type | Default | Meaning |
|---|---|---|---|
scopes | params string[] (ctor) → Scopes (IReadOnlyList<string>) | empty | Named scopes this handler is tagged into so it also appears on matching scoped facades. Empty means the handler participates only in the module's default (unscoped) facade. |
Exclude | bool (init) | false | When true, the handler is excluded from every generated facade, scoped or default. |
[GenerateModuleApi]
- Package:
Elarion.Abstractions(Modules) · Applies to: interface - Triggers: marks a partial interface to be filled with a typed in-process API over the owning module's handlers — one method per handler, dispatched typed-direct to
IHandler<TRequest, TResponse>(full decorator pipeline, no serialization). Not a transport and absent from the JSON-RPC/MCP schema; module-internal (must not cross boundaries). The interface must be partial and at namespace scope. DiagnosticsELAPI001–ELAPI004.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
scopes | params string[] (ctor) → Scopes (IReadOnlyList<string>) | empty | The scopes this facade selects. Empty = default facade (every non-excluded handler in the owning module by longest-prefix namespace match); one or more = only handlers tagged with an intersecting scope via [ModuleApi]. |
See Cross-module communication.
Assembly opt-ins
[UseElarion]
- Package:
Elarion.Abstractions· Applies to: assembly - Triggers: enables the framework-owned assembly generators at once — handlers, services, scheduled jobs, event consumers, resilience policies, actor facades/registration, authorization-policy registration, client-event topics, the permission catalog, and the variant catalog. Application-owned policy attributes such as pipeline defaults remain explicit, as do host/provider triggers such as
[GenerateModuleBootstrapper],[GenerateDbSets], and[UseElarionSql]. Marker only — no parameters.
Individual generation triggers
All are Elarion.Abstractions, applied to the assembly, marker-only (no parameters), and subsumed by [UseElarion].
| Attribute | Triggers |
|---|---|
[GenerateModuleHandlers] | Per-module handler registration methods (and, when the compilation references Elarion.Validation, the per-module request-validation resolvers). |
[GenerateModuleServices] | Per-module service registration methods. |
[GenerateScheduledJobs] | Source-generated scheduler descriptor registration. |
[GenerateEventConsumers] | Source-generated event-consumer descriptor registration. |
[GenerateResiliencePolicies] | Source-generated resilience-policy registration. |
[GenerateActors] | Actor typed facades and per-module actor registration. |
[GenerateModuleAuthorizationPolicies] | Per-module registration for [AuthorizationPolicy] implementations. |
[GenerateClientEventTopics] | Per-module client-event topic registration and subscribe-time policy metadata. |
[GeneratePermissionCatalog] | The typed permission/role catalog harvested from handler requirements. |
[GenerateVariantCatalog] | The typed feature/configuration variant catalog. |
[GeneratePermissionCatalog]
- Package:
Elarion.Abstractions· Applies to: assembly - Triggers: runs the
PermissionCatalogGeneratorto harvest every[RequirePermission]/[RequireRole]in the assembly into a runtimeIPermissionCatalog(a K8s-style resource + verb catalog, withVerbs, inElarion.Abstractions.Authorization) — so admin UIs, role editors, and audits read straight from the handler attributes with no second source of truth to drift. Marker only — no parameters. Subsumed by[UseElarion], or apply it standalone. DiagnosticsELPERM001(handler with requirements outside any[AppModule]) /ELPERM002(two permissions colliding on the same typed accessor).
[assembly: GeneratePermissionCatalog]See Authorization.
[GenerateClientEventTopics]
- Package:
Elarion.Abstractions· Applies to: assembly - Triggers: runs the
ClientEventRegistrationGeneratorto register a topic perIClientEventcontract: grouped under its owning[AppModule]by longest-prefix namespace match, the topic named{module}.{name}(both camel-cased, a trailingEventsuffix stripped;[ClientEvent("…")]overrides the full name), with the contract's[RequirePermission]/[RequireRole]attributes becoming subscribe-time requirements and[AllowAnyResource]declaring the resource segment a routing key. EmitsAdd{Module}ClientEvents(IServiceCollection)wired into the module'sConfigureDefaultServices, so a topic exists just by declaring the contract and disappears with the module's feature gate. Marker only — no parameters. Subsumed by[UseElarion], or apply it standalone. DiagnosticsELCEV001(contract outside any[AppModule]) /ELCEV002(duplicate topic name) /ELCEV003(contracts declared butElarion.ClientEventsnot referenced).
[assembly: GenerateClientEventTopics]See Client events.
[ClientEvent]
- Package:
Elarion.Abstractions(ClientEvents) · Applies to: class (single, not inherited) - Triggers: overrides the topic name the generator infers for an
IClientEventcontract — the full name, like an explicit[Handler("module.action")]. Use it to keep a wire name stable across a type rename.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
name | string (ctor) → Name | required | The full topic name clients subscribe to (e.g. "invoicing.invoiceChanged"). |
[AllowAnyResource]
- Package:
Elarion.Abstractions(ClientEvents) · Applies to: class (single, not inherited) - Triggers: declares the resource segment of this
IClientEventcontract's topic a routing key, not an entitlement: any caller passing the topic's subscribe-time requirements (authenticated, plus any[RequirePermission]/[RequireRole]on the contract) may subscribe to any resource of this topic, without consulting theIClientEventSubscriptionAuthorizerseam. Without it, resource-scoped subscriptions stay fail-closed (denied unless a registered authorizer approves). Deliberately per-topic so a future entitlement-scoped topic is never silently opened by a global "allow everything" authorizer. Imperative form:AllowAnyResource()on the topic options inAddElarionClientEvents. Marker only — no parameters.
[AllowAnyResource] // the symbol selects which events to receive; it gates nothing
public sealed record QuoteChanged : IClientEvent { /* … */ }See Client events.
[SubscriptionObserver<TObserver>]
- Package:
Elarion.Abstractions(ClientEvents) · Applies to: class (single, not inherited) - Triggers: declares the topic's
IClientEventSubscriptionObserver— the producer-side subscription lifecycle.TObserveris called with a per-subscriber sink when a client subscribes (OnSubscribedAsync— the producer-controlled initial value: greet the new subscriber with the current value, so the stream is self-converging) and on debounced interest transitions (OnInterestChangedAsync—trueon the first watcher of a (topic, scope),falseonly after the last one leaves and the linger elapses, so a browser reload never bounces the producer). Resolved from a fresh DI scope per callback, detached from the subscribe path; the pull sibling isIClientEventInterest.HasSubscribers. Imperative form:ObserveSubscriptions<TObserver>()(+WithInterestLinger(...)) on the topic options.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
InterestLingerSeconds | double (init) | 5 | The last-watcher departure debounce before OnInterestChangedAsync(active: false) fires. |
[SubscriptionObserver<QuoteSubscriptionObserver>(InterestLingerSeconds = 30)]
public sealed record QuoteChanged : IClientEvent { /* … */ }See Client events.
[GenerateVariantCatalog]
- Package:
Elarion.Abstractions· Applies to: assembly - Triggers: runs the
VariantCatalogGeneratorto emit the assembly'sElarionVariantsregistry — the compile-time catalog of every[FeatureVariant]/[ConfigurationVariant]switch, aggregated across referenced assemblies from the Elarion manifest (the variant analog ofElarionPermissions). Per switch it emits an accessor class with the selectorKeyand oneconst stringper value (usable in[AllowedValues(...)]), plusVariantDescriptordata surfaced asAll/ByKey/ByModule/Platform(variants outside every module — the platform-adapter placement — carryModule = null). The host seeds runtime consumers explicitly:services.AddElarionVariantCatalog(ElarionVariants.All), optionally withAddElarionVariantValidation()for startup + reload validation. Marker only — no parameters. Subsumed by[UseElarion], or apply it standalone. DiagnosticELVAR010(two selectors/values colliding on the same typed accessor).
[assembly: GenerateVariantCatalog]See Feature flags.
[UseElarionEntityFrameworkCore]
- Package:
Elarion.EntityFrameworkCore· Applies to: assembly - Triggers: declares the target database provider so EF Core generators can emit provider-optimized variants (the EF Core analogue of
[assembly: UseElarion]). When omitted, generators behave asPortableand emit provider-neutral code.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
Provider | EfCoreProvider (init) | Portable (= 0) | Target database provider. |
EfCoreProvider: Portable (= 0, provider-neutral code), Npgsql (= 1, opts generated keyset seek predicates into PostgreSQL row-value comparisons; requires the Npgsql EF Core provider be referenced).
See Entity Framework.