Skip to content

Release v16.34.0

Choose a tag to compare

@github-actions github-actions released this 15 Aug 21:39
55461ce

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)