Skip to content

Marten Coverage Reference

A complete map of how Tayra.Marten integrates with Marten's document store and event store, and at what level of protection each surface is covered. For setup and examples, see the Marten integration guide.

The foundation: one serializer seam

Almost everything is transparent because Marten routes all document and event (de)serialization through a single ISerializer, and Tayra wraps it with TayraSerializer - a decorator that adds field-level PII handling and delegates the rest.

DirectionMethods wrappedTayra behavior
Write (JSON)ToJson, ToCleanJson, ToJsonWithTypescompute blind indexes → encrypt [PersonalData] → delegate
Write (Marten 9 UTF-8 / buffer)WriteTo, WriteToParameter, WriteToCleanJson, WriteToJsonWithTypessame, on the IBufferWriter<byte> / NpgsqlParameter fast paths
ReadFromJson (6 overloads), FromJsonAsyncdelegate → decrypt [PersonalData]
Pass-throughEnumStorage, Casing, ValueCastingforwarded to the inner serializer

Documents - automatic

[PersonalData] fields are encrypted in the JSONB data column and decrypted on load, across the full Marten document surface:

  • All session types - Lightweight / Identity / DirtyTracked
  • Store / Load / LINQ Query, compiled queries
  • Bulk insert, optimistic concurrency
  • Soft-delete (deleted docs still decrypt)
  • Multi-tenancy (conjoined) - works with Tayra's per-tenant key store
  • Record types, nullable fields, deep/nested [DeepPersonalData] graphs

Event store - automatic

[PersonalData] on event payloads is encrypted in mt_events.data and decrypted on read:

  • Append / StartStream
  • FetchStreamAsync, AggregateStreamAsync (live aggregation), FetchForWriting
  • Polymorphic events (inherited PII on derived types)

Projections - automatic, no special API

All three lifecycles decrypt through TayraSerializer (EventMapping.ReadEventData is the single deserialization seam used by every projection style):

LifecycleCoverage
InlineAutomatic
Live aggregationAutomatic
Async daemonAutomatic

Projected read-model documents with their own [PersonalData] are re-encrypted at rest (they round-trip as JSONB).

Binary events (Marten 9) - supported + guarded Tayra.Marten9

Binary events (mt_events.bdata) bypass ISerializer, so Tayra provides a parallel seam:

  • TayraBinaryEventSerializer - encrypts/decrypts [PersonalData] around any IEventBinarySerializer.
  • Auto-wrap of opts.Events.DefaultBinarySerializer in UseTayra().
  • Startup guard - throws if a binary event with [PersonalData] is not Tayra-wrapped.
  • TAYRA011 analyzer - compile-time warning.

See Binary events.

Querying encrypted fields

MechanismStatus
Blind indexes ([BlindIndex])Computed during serialization; companion populated in place; stored as deterministic HMAC in JSONB; queryable
Duplicate the companion - Duplicate(x => x.EmailIndex)Supported - indexed relational column, queryable, no plaintext
Index on the companion - Index(x => x.EmailIndex)Supported - indexes data->>'EmailIndex', no extra column
Duplicating the PII field itselfBlocked by TAYRA008

See Duplicated fields.

Crypto-shredding

session.ShredDataSubjectAsync(...) and session.IsDataSubjectShreddedAsync(...) (in MartenSessionExtensions). After a subject's key is deleted, their documents and replayed events surface mask values instead of the original data, automatically.

Operational tooling (explicit opt-in)

ComponentPurpose
ITayraMartenMigrationServiceEncryptExistingDocumentsAsync<T> (encrypt pre-existing plaintext documents in batches, tenant-aware) and VerifyDocumentEncryptionAsync<T>
ITayraMartenBlindIndexRecomputeServiceRecomputeAsync<T> - rebuild blind-index companions after key or algorithm changes
MartenAuditTrailStore / MartenAuditTrailDocumentMarten-backed IAuditTrailStore - hash-chained, tamper-evident audit log in PostgreSQL (AppendAsync, QueryAsync, VerifyIntegrityAsync)
AddTayraMartenMigrations(), AddTayraMartenAuditTrail()DI registration for the above

Guardrails

GuardCatches
TayraSerializerGuard (startup IHostedService)a custom serializer or later IConfigureMarten that dropped the Tayra wrapper
Binary-event startup guarda binary [PersonalData] event not Tayra-wrapped
TAYRA011 / TAYRA007 / TAYRA008[PersonalData] on binary events / flat-table columns / duplicated fields (compile-time)

Registration

  • DI (recommended): services.AddMarten(...); services.UseTayra(); - wraps the serializer (via IConfigureMarten), auto-wraps + guards binary serializers, and registers the startup serializer guard. Robust to ordering.
  • Explicit: opts.UseTayra(tayraInstance) - for non-DI scenarios; must be called last, after all serializer configuration.
  • Custom serializers compose - Tayra wraps yours - as long as UseTayra is applied last. See Custom serializers.

Coverage summary

SurfaceLevel
Documents, events, all 3 projection lifecycles, blind indexes, crypto-shredAutomatic
Binary events, custom serializersSupported + startup guard
Migration, blind-index recompute, audit trailExplicit opt-in
Duplicated fields / flat-table projectionsCompanion pattern supported; PII blocked by analyzer
PII written directly into non-JSONB relational columns (flat-table projections)Not protectable - no serializer seam; advisory + TAYRA007

The one genuine hole is personal data written directly into non-JSONB relational columns (flat-table projections), which no serializer-level approach can reach - it is caught at compile time only.