Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/data/search-index.json

Large diffs are not rendered by default.

263 changes: 191 additions & 72 deletions docs-src/adr/037-field-level-encryption-at-rest.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs-src/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pattern they describe: they capture context and trade-offs that aren't obvious f
| [034](034-generic-entity-query-layer.md) | Generic entity controllers + dynamic query contract | Every entity inherits a generic REST surface (`EntityControllerBase` / `AggregateRootEntityControllerBase`: list/paged/lookup/by-id + create/delete) plus an OData-lite query contract: sparse fieldsets (`fields`), per-type `IFilterStrategy` filtering via `QueryFilterModelBinder`, sort, pagination + `X-Pagination`, a `MaxUnboundedResultLimit` ceiling, and the two-path include strategy. Write-once over bespoke endpoints; the wire contract tracks the entity model (DTO-mediated). Composes with ADR-001 / ADR-002 / ADR-013 / ADR-017. |
| [035](035-optimistic-concurrency.md) | Optimistic concurrency via RowVersion round-trip | Every auditable entity carries a `RowVersion` concurrency token (SQL Server `rowversion`, `IsConcurrencyToken` elsewhere) that round-trips through the client on `IConcurrencyAware` DTOs/update requests; update handlers stamp it back via `IWriteRepository.SetOriginalRowVersion` so a stale write surfaces as a concurrency conflict that `DbUpdateExceptionHandler` maps to HTTP 409. Build-gated by the `UpdateRequestsAreConcurrencyAware` fitness rule (subclassed in ADC and Store); adopted in both via `AddRowVersionToAllEntities` migrations. Distinct from request idempotency (ADR-017) and inbox dedup (ADR-021). |
| [036](036-external-oauth-login.md) | External OAuth login (Google/GitHub) | `AddExternalAuthProviders` federates third-party sign-in behind a short-lived `ExternalLogin` cookie; `OAuthControllerBase` completes the handshake and swaps a single-use, 2-minute cached code for the app's local JWT pair (tokens never ride the redirect URL). The local `User` links by provider+key, by email (validated through `Email.Create` first, rejecting an unparseable provider email with `ExternalEmailInvalid`, then guarded since 2026-07-19: ADC rejects the link with `ExternalEmailNotVerified` when the provider did not assert the email verified), or is created externally (`CreateExternal`, `LoginProvider`/`ProviderKey` fields). Config-gated per provider (`OAuth:<Provider>:ClientId`), inert until configured; adopted by MMCA.ADC only (MMCA.Store does not wire it). |
| [037](037-field-level-encryption-at-rest.md) | Field-level encryption at rest (AES-256-GCM EF converter) | An `EncryptedStringConverter` transparently encrypts string columns with authenticated AES-256-GCM (random 12-byte nonce, 128-bit tag, Base64 `nonce+ct+tag` layout; consumer supplies the 32-byte key). Shipped and unit-tested but **unadopted**: no entity configuration wires it yet (the shipped-but-latent posture ADR-018 also records). |
| [037](037-field-level-encryption-at-rest.md) | Field-level encryption at rest (AES-256-GCM EF converter) | An `EncryptedStringConverter` transparently encrypts string columns with authenticated AES-256-GCM (random 12-byte nonce, 128-bit tag, Base64 `version+nonce+ct+tag` envelope; consumer supplies the 32-byte keys). A versioned key ring makes rotation zero-downtime: writes stamp the current version, reads resolve their key from the version byte in the stored value, and AES-GCM authenticates that byte as associated data so it cannot be rewritten. Shipped and unit-tested but **unadopted**: no entity configuration wires it yet (the shipped-but-latent posture ADR-018 also records), which is exactly what made the un-versioned format free to replace. |
| [038](038-supply-chain-provenance.md) | Supply-chain provenance (SBOM gate + lock files + vuln audit) | Four build-gating controls for a published framework: a CycloneDX SBOM as a hard release gate, committed NuGet lock files, a CI `--vulnerable --include-transitive` audit that fails on any row except `NuGetAuditSuppress`-accepted advisories (single source of truth, re-applied in CI; zero suppressions active since the 2026-07-20/21 SQLite direct-pin fix), and `packageSourceMapping` pinning every package to nuget.org. Extends ADR-016 from versioning/licensing into provenance. |
| [039](039-live-channel-push.md) | Live channel push (ephemeral events over the notification hub) | `NotificationHub` gains `JoinChannel`/`LeaveChannel` group membership (keys validated against `PushNotificationSettings.ChannelKeyPattern`) and a `ReceiveChannelEvent` client method; a new `ILiveChannelPublisher` Application abstraction (`Null` default, SignalR group-send impl swapped by `AddPushNotifications`, ADR-024 pattern) publishes ephemeral `(channelKey, eventName, payloadJson)` events. One WebSocket carries durable notifications and lossy live events; the durable-vs-ephemeral split lives at the publisher boundary. Client side: multicast `OnChannelEvent` subscriptions + automatic channel re-join on reconnect. |
| [040](040-authenticated-output-caching-for-public-reads.md) | Authenticated output caching for public reads | `PublicEndpointOutputCachePolicy` (+ `AddPublicEndpointPolicy` extension) replaces the built-in default policy on `[AllowAnonymous]`, user-independent GET endpoints so an `Authorization` header no longer bypasses the output cache. The UI attaches a Bearer token to every request, so the default policy served 0% cache hits to logged-in users and every read landed on the database; this policy keeps the GET/HEAD-only, no-Set-Cookie, 200-only guards and takes expiration + eviction tags per named policy. Strict contract: never apply to identity-dependent payloads. |
Expand Down
Loading