Skip to content

Changelog

All notable changes to Tayra are recorded here, newest first. Tayra follows Semantic Versioning: major versions may break the public API, minor versions add backward-compatible features, and patch versions are bug fixes only.

The framework major lives in the package name (Tayra.Marten8 / Tayra.Marten9, Tayra.Wolverine5 / Tayra.Wolverine6); the version below is Tayra's own suite version, shared across every package. See Installation.

2.3.0 - 2026-06-10

Core hardening: encryption now fails closed, tampering is surfaced, and crypto-shredding stays complete after key rotation. Annotations now work on public instance fields as well as properties.

Changed (breaking)

  • Encryption fails closed. EncryptAsync now throws an InvalidOperationException when a [DataSubjectId] value is null, or when no encryption key can be resolved for a field's group. Previously the affected fields were silently persisted as plaintext with only a warning log. Decryption tolerance is unchanged: a missing (shredded) key still follows the replacement-value path.
  • The metadata API no longer exposes a PropertyInfo. The reflection model now describes annotated members through a PersonalDataMember accessor (with Name, MemberType, CanWrite, GetValue, SetValue, and Underlying) that backs both properties and fields. PersonalDataFieldInfo.Property becomes PersonalDataFieldInfo.Member, DataSubjectInfo exposes Member, and the blind-index metadata exposes SourceMember / IndexMember. Code that read .Property off the public metadata types must move to the new Member accessor.
  • Non-writable annotated members now fail with a clear error at first use instead of being silently skipped. A member Tayra writes back to (Text and Serialized kinds, plus their companions) must be writable: a settable property (init-only is fine) or a non-readonly, non-const field. A get-only property or a readonly/const field throws an InvalidOperationException at first encrypt. Similarly, read-only string collection instances (e.g. ImmutableList<string>) now throw instead of being silently skipped.
  • The AAD decrypt path rejects legacy v1 ciphertexts unconditionally. Version 0x01 payloads predate associated-data binding, so they cannot be context-verified - a database-level attacker could splice a v1 ciphertext into a different field of the same subject. AesGcmEncryptor.DecryptWithAssociatedData / DecryptStringWithAssociatedData now throw a CryptographicException on v1 payloads; at the field-encrypter level a v1 value is left untouched and surfaces as an IntegrityCheckFailed audit event. The v1 format remains valid only for the no-AAD AesGcmEncryptor.Encrypt / Decrypt primitive pairing.

Added

  • All seven attributes now annotate properties or public instance fields.[PersonalData], [DeepPersonalData], [SerializedPersonalData], [DataSubjectId], [BlindIndex], [CompoundBlindIndex], and [ArrayBlindIndex] are recognized on public instance fields in addition to properties; field usage compiles and works. Only public instance members are scanned (private and static members are ignored), mirroring the existing property scan rule. The fluent API expression selectors now accept field expressions (x => x.SomeField) as well as property expressions.
  • Tamper detection is surfaced. A CryptographicException during decryption (tampered ciphertext or context mismatch) is now logged at Warning and emits a new TayraAuditEventType.IntegrityCheckFailed audit event; the field value is left untouched. Legacy-plaintext tolerance (FormatException) stays quiet at Debug.
  • HashSet<string> and other mutable ICollection<string> properties are now encrypted (previously only IList<string> / string[] worked; other collection types were silently skipped). A new fluent overload EntityTypeBuilder<T>.PersonalData(Expression<Func<T, IEnumerable<string>>>) configures string-collection properties directly.

Fixed

  • ShredAsync(subjectId) now deletes the subject's base key and every {subjectId}:-prefixed key - rotated versions (:vN) and group keys - so crypto-shredding remains complete after key rotation. Previously only the exact base key was deleted, leaving data encrypted under rotated keys readable. Neighboring subjects that merely share an ID prefix are unaffected.
  • [DeepPersonalData] object graphs with cycles no longer cause a StackOverflowException, and shared (diamond) references are processed exactly once instead of being double-encrypted.
  • DeepPersonalData configured on a collection property via the fluent API now encrypts the elements (previously silently skipped).
  • Eliminated a key-creation race that could encrypt data with a never-persisted key: IKeyStore.StoreAsync is now documented as first-writer-wins, and the crypto engine and blind-index key provider read the key back after storing and serialize per-key-ID creation in-process.
  • Blind-index HMAC key rotation now actually replaces the stored key (delete, store, then read-back verify); previously the first-writer-wins store made rotation a silent no-op. The HMAC key cache also gained a TTL (default 5 minutes) so rotations and deletions performed elsewhere are observed.

2.2.0 - 2026-06-10

Changed

  • Updated the integration targets to Marten 9.7.1 and Wolverine 6.6.0. The dependency ranges still cap the next framework major, so a consumer that pulls Marten 10 or Wolverine 7 fails fast at restore instead of at runtime.
  • Wolverine 6 dead-letter exception redaction now uses a supported hook. When RedactExceptionMessages is enabled, Tayra.Wolverine6 scrubs the exception text on Wolverine's IDeadLetterInterceptor (added in WolverineFx 6.6.0) instead of reaching into private exception internals. The redacted dead letter now preserves the original exception type, so you can still filter and triage dead letters by type while the message is replaced with Exception details redacted by Tayra. The behavior of the option is otherwise unchanged. Tayra.Wolverine5 keeps its existing redaction mechanism.

No public API changes.

2.1.0 - 2026-06-09

Added

  • Per-major integration packages. Pick the package that matches your framework version: Tayra.Marten8 / Tayra.Marten9 and Tayra.Wolverine5 / Tayra.Wolverine6. Both lines ship from a single trunk at the same suite version, so you are never forced onto a new framework major to stay current with Tayra. See Installation.

Fixed

  • HashiCorp Vault key store: keys are now read back correctly from Vault KV v2.
  • Wolverine 5: the integration's source generator is kept enabled so its handlers (such as the built-in GDPR erasure handler) are discovered at runtime.

2.0.0 - 2026-06-07

Tayra 2.0 moves the default integrations onto the current framework majors.

Changed (breaking)

  • The Marten integration now targets Marten 9 and the Wolverine integration targets Wolverine 6. Marten 8 / Wolverine 5 support continues through the per-major packages introduced in 2.1.0.
  • The Wolverine integration now encrypts at the message serializer rather than via handler middleware. PII is therefore ciphertext at rest in the durable outbox/inbox and in dead-letter storage (the durable outbox could previously hold cleartext). UseTayraMiddleware() is now an [Obsolete] alias for UseTayra(), and the RedactDeadLetterQueues option was removed because dead-letter bodies are encrypted automatically.

Added

  • Searchable encrypted collections via [ArrayBlindIndex], with fluent configuration.
  • New Roslyn analyzers: TAYRA007 (PII on Marten binary events), TAYRA008 (flat-table PII guard), TAYRA009 (duplicated-field PII guard), and TAYRA010 (PII on a Wolverine saga).
  • Marten: fail fast at startup if a custom serializer drops the Tayra wrapper.

Performance

  • Core encrypt/decrypt dispatch is cached as compiled delegates.

1.4.0 - 2026-05-05

Added

  • Envelope encryption: two-tier encryption with pluggable master and data key stores and pluggable master-key id resolution.
  • AWS Aurora (PostgreSQL with IAM authentication) key store.
  • Persistent, hash-chained audit trail (Marten-backed), plus signed (ECDSA P-256) and scheduled compliance reports with a pluggable archive.
  • Date-only perpetual-fallback licensing model (the major-version gate was dropped).

Changed

  • [DeepPersonalData] now recurses transitively through nested annotations.

Removed

  • The Tayra.MediatR, Tayra.MassTransit, and Tayra.NServiceBus integrations were dropped because their upstreams moved to commercial or copyleft licensing. Tayra ships no copyleft or commercial-by-default dependencies.

1.3.0 - 2026-05-01

Changed

  • Compliance features were extracted into a dedicated Tayra.Compliance package.

1.2.0 and earlier - 2026-03 to 2026-04

Initial public releases established the core library and ecosystem:

  • AES-256-GCM field-level encryption with a versioned wire format and embedded key version for rotation, plus GDPR crypto-shredding.
  • The attribute model ([PersonalData], [DataSubjectId], [DeepPersonalData], [SerializedPersonalData]) and Roslyn analyzers TAYRA001 to TAYRA006.
  • Key stores: In-Memory, SQLite, PostgreSQL, HashiCorp Vault, Azure Key Vault, AWS Parameter Store, and AWS Secrets Manager.
  • Integrations for Entity Framework Core, Marten, MongoDB, Serilog, System.Text.Json, and ASP.NET Core, plus the dotnet tayra CLI.