Code Analysis Rules
This section documents the code analysis rules provided by the Chronicle Code Analyzer for the .NET client.
All rules follow the identifier format CHR####. Numbers are assigned sequentially; an occasional id is reserved for a rule still in progress, so a gap in the published set is expected.
Rules Overview
Section titled “Rules Overview”| Rule ID | Title | Severity | Description |
|---|---|---|---|
| CHR0001 | Event type must have [EventType] attribute | Error | Types appended to event sequences must be marked with [EventType] attribute |
| CHR0002 | Declarative projection event type must have [EventType] attribute | Error | Declarative projection generic arguments must reference types with [EventType] attribute |
| CHR0003 | Model bound projection attribute must reference event type with [EventType] attribute | Error | Model bound projection attributes must reference types with [EventType] attribute |
| CHR0004 | Reactor method has a parameter that cannot be resolved | Warning | Reactor method dependencies must be resolvable from event context, read models, or services |
| CHR0005 | Reactor event parameter must have [EventType] attribute | Error | Event parameters in reactor methods must be marked with [EventType] attribute |
| CHR0006 | Reducer method signature must match allowed signatures | Warning | Reducer methods must follow allowed signatures |
| CHR0007 | Reducer event parameter must have [EventType] attribute | Error | Event parameters in reducer methods must be marked with [EventType] attribute |
| CHR0012 | Event types should avoid nullable properties | Warning | Nullable properties are supported on events but are often better modeled as separate event types |
| CHR0013 | Reactor cannot combine EventStore with explicit event sequence | Error | Reactors with [EventStore] must not also configure an explicit event sequence |
| CHR0014 | Reducer cannot combine EventStore with explicit event sequence | Error | Reducers with [EventStore] must not also configure an explicit event sequence |
| CHR0015 | Projection must not have side effects | Error | Projections must not inject ICommandPipeline or IEventLog |
| CHR0016 | Projection Define() must not contain imperative code | Error | Projection Define() must only contain builder calls, not imperative statements |
| CHR0017 | Constraint must not have side effects | Error | Constraints must not inject ICommandPipeline or IEventLog |
| CHR0018 | Constraint Define() must not contain imperative code | Error | Constraint Define() must only contain builder calls, not imperative statements |
| CHR0019 | Projection expression lambda must only access members | Error | Expression lambdas in projection builder methods must be simple member-access expressions |
| CHR0020 | Constraint expression lambda must only access members | Error | Expression lambdas in constraint builder methods must be simple member-access expressions |
| CHR0021 | Event types should be record types | Warning | Event types should be declared as record types for immutability |
| CHR0022 | Reactor methods returning event side effects must be marked with [OnceOnly] | Warning | Reactor methods that return events must be [OnceOnly] to avoid appending duplicates during replay |
| CHR0023 | Ambiguous parent key for [ChildrenFrom] collection | Warning | Parent-key inference is ambiguous when the child event has more than one property of the parent identifier type; specify parentKey |
| CHR0024 | Read model property has no mapping source | Warning | A model-bound read model property has no mapping attribute and no subscribed event carries a same-named property, so AutoMap can never populate it |
| CHR0025 | Explicitly sourced read model property may be overwritten by AutoMap | Info | A property set with [SetFrom]/[SetValue]/… collides by name with another referenced event that AutoMap writes on top; add [NoAutoMap] or accept the update |
| CHR0026 | [Key] or [Subject] on an EventSourceId<T> is redundant | Warning | An EventSourceId<T>-derived property already is the key and compliance subject, so [Key]/[Subject] on it is redundant — remove the attribute |
| CHR0027 | Ambiguous event stream id | Error | A type both implements ICanProvideEventStreamId and declares a non-null [EventStreamId]; this throws at startup |
| CHR0028 | Redundant .AutoMap() call | Warning | .AutoMap() has no effect because AutoMap is enabled by default on projection builders; remove the call |
| CHR0029 | Redundant .Set().To() with matching property names | Warning | A .Set(x => x.P).To(e => e.P) mapping with identical names duplicates what AutoMap already does; remove it |
| CHR0030 | [ChildrenFrom] child collection property auto-maps to nothing | Warning | A [ChildrenFrom] child collection property matching no event property and with no explicit mapping always projects empty; rename it or bridge with [SetFrom<T>] |
| CHR0031 | Reactor must not have mutable state | Warning | Reactors are re-created and replayed, so mutable instance state is unreliable; use readonly, primary-constructor-injected dependencies |
| CHR0032 | Reactor must not access storage directly | Warning | Injecting a storage primitive like IMongoCollection<T> couples the reactor to a sink; read state via a read model or IReadModels.GetInstanceById |
| CHR0034 | [PII] cannot be applied to an EventSourceId<T> | Error | The event source id is the encryption-key lookup identity, so it cannot be encrypted; [PII] on it throws PIINotSupportedOnEventSourceId at runtime |
| CHR0035 | Read model declares a reserved compliance subject property | Error | Chronicle reserves _subject, __subject, and __subjects for internal compliance-subject tracking; a same-named property collides with stored metadata |
| CHR0036 | Reducer must not have mutable state | Warning | Reducers are re-created and replayed, so mutable instance state or direct storage injection makes the fold non-deterministic; keep them stateless |
| CHR0037 | Event type migration generations must belong to one event type | Warning | The two generations referenced by an EventTypeMigration must resolve to the same event type id and differ only by generation, or the migration never applies — checked for both EventTypeGenerationFor<T> and the older explicit-id [EventType] style |
| CHR0039 | Assertion result is discarded and can never fail | Warning | An awaitable-returning Should* assertion on a Cratis testing surface whose result is discarded throws on an awaitable nobody observes, so the assertion silently passes regardless of behavior and CS4014 does not fire outside an async method |
| CHR0040 | Several [SetFromContext] for the same event type on one member | Warning | They write the same property in the definition, so only the last declared is kept and the earlier capture is silently dropped; capturing from several different event types is the supported use and is not flagged |
| CHR0041 | Event filter attribute on a projection has no effect | Warning | A projection observes every event of the types it declares and cannot filter on event metadata — only reactors and reducers can — so [EventStreamType], [EventSourceType], and [FilterEventsByTag] on a projection are silently ignored |
| CHR0042 | A joined property is also written locally | Warning | A property written by both a local mapping and a join — model-bound or fluent — always ends up with the joined value regardless of arrival order, so a local write can never reset it; a flag latched from both sides silently sticks |
| CHR0043 | Key redirection carries a [PII] value across the compliance subject | Warning | A root key or child parent-key redirect carries PII onto a document whose resolved compliance subject is not provably the value owner’s |
| CHR0045 | Event stream metadata attribute on an event type has no effect | Warning | An append resolves its event source type and event stream type from the append itself, never from the event’s CLR type, so [EventStreamType] and [EventSourceType] on an [EventType] are read by nothing — declare them on the appending command, the observing reactor/reducer, or (for [EventStreamType]) the aggregate root whose appends they identify |
| CHR0046 | Passive read model redirects the document key away from the event’s own stream | Warning | A passive read model has no sink, so a read replays one event stream constrained to the requested key; a root key redirection points that replay at a stream the events were never appended to and the read returns a default-initialized model |
| CHR0047 | A clear declaration is never applied by projection construction | Retired | Both declarations it reported - a null [SetValue] and a member-level [ClearWith] - are a working scalar clear now, so the rule was removed; the id stays reserved and is never reused |
| CHR0048 | A clear is declared for a member that cannot hold null | Warning | Clearing means returning a member to no value; a non-nullable member has no such state, so the only thing the projection could write is the type default - a different fact the read model cannot tell apart from a real value |
| CHR0049 | EventTypeGenerationFor<T> must reference a type marked with [EventType] | Error | A type marked with EventTypeGenerationFor<T> references a type that is not itself marked with [EventType], so there is no id to resolve the previous generation’s identity from |
Quick Fixes
Section titled “Quick Fixes”- CHR0001, CHR0002, CHR0003, CHR0005, and CHR0007 provide a code fix that adds the missing
[EventType]attribute to the referenced type. - CHR0026 provides a code fix that removes the redundant
[Key]/[Subject]attribute. - CHR0028 provides a code fix that removes the redundant
.AutoMap()call. - CHR0029 provides a code fix that removes the redundant
.Set(...).To(...)mapping.
Installation
Section titled “Installation”The analyzer is automatically included when you reference the Cratis.Chronicle NuGet package.