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.
| Direction | Methods wrapped | Tayra behavior |
|---|---|---|
| Write (JSON) | ToJson, ToCleanJson, ToJsonWithTypes | compute blind indexes → encrypt [PersonalData] → delegate |
| Write (Marten 9 UTF-8 / buffer) | WriteTo, WriteToParameter, WriteToCleanJson, WriteToJsonWithTypes | same, on the IBufferWriter<byte> / NpgsqlParameter fast paths |
| Read | FromJson (6 overloads), FromJsonAsync | delegate → decrypt [PersonalData] |
| Pass-through | EnumStorage, Casing, ValueCasting | forwarded 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/StartStreamFetchStreamAsync,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):
| Lifecycle | Coverage |
|---|---|
| Inline | Automatic |
| Live aggregation | Automatic |
| Async daemon | Automatic |
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 anyIEventBinarySerializer.- Auto-wrap of
opts.Events.DefaultBinarySerializerinUseTayra(). - Startup guard - throws if a binary event with
[PersonalData]is not Tayra-wrapped. TAYRA011analyzer - compile-time warning.
See Binary events.
Querying encrypted fields
| Mechanism | Status |
|---|---|
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 itself | Blocked 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)
| Component | Purpose |
|---|---|
ITayraMartenMigrationService | EncryptExistingDocumentsAsync<T> (encrypt pre-existing plaintext documents in batches, tenant-aware) and VerifyDocumentEncryptionAsync<T> |
ITayraMartenBlindIndexRecomputeService | RecomputeAsync<T> - rebuild blind-index companions after key or algorithm changes |
MartenAuditTrailStore / MartenAuditTrailDocument | Marten-backed IAuditTrailStore - hash-chained, tamper-evident audit log in PostgreSQL (AppendAsync, QueryAsync, VerifyIntegrityAsync) |
AddTayraMartenMigrations(), AddTayraMartenAuditTrail() | DI registration for the above |
Guardrails
| Guard | Catches |
|---|---|
TayraSerializerGuard (startup IHostedService) | a custom serializer or later IConfigureMarten that dropped the Tayra wrapper |
| Binary-event startup guard | a 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 (viaIConfigureMarten), 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
UseTayrais applied last. See Custom serializers.
Coverage summary
| Surface | Level |
|---|---|
| Documents, events, all 3 projection lifecycles, blind indexes, crypto-shred | Automatic |
| Binary events, custom serializers | Supported + startup guard |
| Migration, blind-index recompute, audit trail | Explicit opt-in |
| Duplicated fields / flat-table projections | Companion 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.
