You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
Reactors can take a ReactorDelivery parameter on a handler method to get a stable identity for that delivery of that event — the same value when a failed partition is recovered and the event is delivered again, and different for every distinct delivery. Use delivery.Id as the key for your own record of completed side effects (#3643)
Read model members can be cleared. [ClearWith<TEvent>] on a scalar property or record parameter, [SetValue<TEvent>(null)], and the fluent .Clear(m => m.Property) all write the member back to no value every time the event is observed, replay included — at the root, on a child item, and on a nested object's members (#3711)
The fluent projection builder gained .Clear(m => m.Property), available everywhere .Set(...) is. It is the recommended way to clear; .Set(...).ToValue(null) means the same thing and keeps working (#3711)
The projection declaration language gains a clear statement. clear <property> takes a value away, alongside increment, decrement and count, and accepts a property path so clear owner.note clears a member of a nested object. property = null still compiles to exactly the same mapping (#3711)
CHR0048 reports a clear declared for a member that cannot hold null — for [ClearWith], a null [SetValue], .Clear(...) and .Set(...).ToValue(null) alike — as a warning. It is scheduled to become an error in the next major; building the projection refuses the declaration outright, so an unaddressed warning fails at startup rather than at build time (#3711)
A unique constraint can be released by more than one event. Declare RemovedWith<T>() once for each way the lifecycle ends — accepted, revoked, expired — and every one of them frees the value on its own. The same holds for the attribute form: several event types may carry [RemoveConstraint] for one constraint name (#3697)
Encryption certificates can be rotated without downtime: encryptionCertificate.previous holds an ordered list of previously active certificates, kept for decryption while the active one protects everything new (#3690)
GET /diagnostics/encryption-certificates reports the certificate ring, which certificate is active, and how many stored Data Protection keys still depend on each — including keys that depend on a certificate no longer in the ring (#3690)
The encryption-certificate ring is logged at every boot, one line per certificate, with a warning for any that has expired (#3690)
IEventStore.PII.AllowNewEncryptionKeyFor(subject) authorizes a fresh encryption key for a subject whose key was erased, so a later lawful lifecycle can protect their data again under an independent key (#3600)
Encryption key stores record erasures beside the keys — a revision floor, the fingerprints of the destroyed key material, and whether a new lifecycle is authorized — so "erased" is a state Chronicle can tell apart from "never provisioned" (#3600)
Erasing a subject and authorizing a new encryption key for one are recorded in Chronicle's log — the completion, the namespace, and every event store reached, with the subject as a one-way binding rather than by name and never any key material. An erasure that did not reach every event store is logged as an error saying so (#3600)
ConcurrencyOptions.CheckFirstAppendIntoAScope opts in to checking the first append into a concurrency scope. With it off, the default, the first append into a narrowed scope is unchecked as before; with it on, the kernel rejects the append if a matching event appeared between resolving the scope and the append arriving (#3635)
ConcurrencyScopeBuilder.ExpectingNoMatchingEvent() asks for that check on a single append without changing the application-wide default (#3635)
IAppendResult.ConcurrencyCheckPerformed reports whether the concurrency check actually ran, so a skipped check can be told from a passing one in a test rather than only in the server log. It has a default implementation, so existing implementers are unaffected (#3635)
Changed
Re-run the erasure for every subject erased before upgrading. A pre-upgrade erasure left the same absence as "never provisioned" — that is the defect — so nothing can recognize it retroactively and those subjects stay resurrectable until erased again. The call is idempotent and costs one round trip per subject (#3600)
Upgrade every silo together while an erasure is in force. The erasure fence is enforced by the key store, so a silo that predates it provisions straight over one (#3600)
DeleteEncryptionKeyFor erases across every event store in its namespace instead of the one it was resolved from, which is exactly how far a subscription can copy a subject's key; it never crosses a namespace (#3600)
Appending a [PII] value for an erased subject fails with EncryptionKeyErased instead of silently minting a new key; authorize a new key first when the person has a lawful basis to be protected again (#3600)
An erasure records itself in every event store before destroying key material in any of them, and reports every store it could not reach as EncryptionKeyErasureIncomplete (#3600)
Values encrypted by Chronicle carry the key id of the certificate that protected them, so a value needing a retired certificate is reported as unreadable and names the key id required, instead of failing as if it were corrupt. Values written before this are still read (#3690)
OpenIddict and ASP.NET Core Data Protection are configured with the whole certificate ring, so tokens and keys written under a previous certificate keep working across a rotation (#3690)
A unique event type constraint violation no longer carries the event source id, in the default message or in the violation details. It reaches callers as a validation result, and an event source id is the stream identity and compliance subject and cannot be encrypted. The event type and the conflicting sequence number are unchanged, and a caller that needs the id already holds it — it is the event source it just attempted to append for (#3632)
Reading RemovedWith on UniqueConstraintDefinition or UniqueEventTypeConstraintDefinition returns a collection rather than a single event type. Constructing them is unaffected — the previous signatures are kept as obsolete constructors — but code compiled against the old property will need recompiling (#3697)
[SetValue<TEvent>] accepts a nullable value, so a clear is written as a plain null instead of null! (#3711)
[ClearWith<TEvent>] on the property holding a [Nested] object clears that object, the same as declaring it on the nested type — useful when the nested type is shared and should not know the owner's events (#3711)
CHR0024 no longer treats a null [SetValue] as a mapping source, so a member that one event clears and nothing ever populates is reported instead of silently accepted (#3711)
A clear round-trips as a clear. Generating a declaration from a projection definition writes clear <property> whichever spelling it was authored in, and generated code renders .Clear(...) in fluent form and [ClearWith<TEvent>] in model-bound form (#3711)
ReactorScenario gives every event it feeds a reactor its own event sequence number instead of Unavailable, so a reactor keyed on the delivery identity behaves in a scenario the way it behaves in production (#3643)
Fixed
An erasure no longer leaves the subject's key readable in the other event stores a subscription had copied it into (#3600)
A forwarded event no longer restores an erased subject's original key material, which used to make already crypto-shredded personal data readable again (#3600)
A composed encryption key store no longer heals a surviving copy of an erased key back into the store it was erased from, and no longer serves that copy (#3600)
Repeated RemovedWith declarations no longer overwrite each other. A constraint declaring several terminal events previously released on only the last one declared; the others registered and did nothing, so the value stayed claimed forever while every append succeeded (#3697)
Constraints declared with [RemoveConstraint] on more than one event type honor all of them, instead of the first one found (#3697)
Unique event type constraints declared under one shared name carry every removal event declared across those declarations (#3697)
The fluent .Set(...).ToValue(null) wrote the literal string "null" into the member instead of clearing it (#3711)
A [SetValue<TEvent>(null)] was discarded when the projection was built, leaving the member with whatever it last held, across replay (#3711)
A [ClearWith<TEvent>] on a property or record parameter bound to nothing at all (#3711)
A property path through a nullable nested object never resolved during projection-declaration validation, so any dotted mapping onto a nested read model member was rejected as "property not found" (#3711)
An unrecognized projection mapping silently skipped property validation and then failed as an unhandled exception; it is now reported as a compiler error on the declaration, with a line and column (#3711)
An encryption certificate configured with a path that holds no file no longer starts silently: it used to leave Data Protection writing its keys unencrypted and OpenIddict reporting that no certificate was configured when one was. It now stops startup naming the path (#3690)
Removed
CHR0047 is retired: both declarations it reported as inert now work. Remove any suppression of it — the identifier stays reserved and will never be reused (#3711)
Security
Crypto-shredding is irreversible within a Chronicle cluster: an erased key cannot be re-provisioned, copied between event stores, healed between composed stores, or restored by writing the same key material back — at any revision, and including after a new lifecycle is authorized (#3600)
A key store that cannot record an erasure fails loudly when one is attempted, instead of reporting a reversible erasure as complete (#3600)
A misconfigured encryption-certificate ring fails closed rather than degrading quietly: a missing file, a previous entry without a path, previous certificates without an active one, the same key pair in two positions, and a certificate without a private key are all rejected at startup (#3690)
Encryption-certificate diagnostics and failure messages name certificate thumbprints and paths only — never protected values or key material (#3690)