From c1ebc01c4ae0014aa518b15fa080d66313006676 Mon Sep 17 00:00:00 2001
From: Ivan Ball-llovera
Date: Sat, 15 Aug 2026 13:12:20 -0400
Subject: [PATCH 1/2] docs: ADR-037 revision, versioned envelope and key ring
Records the new storage format for EncryptedStringConverter: Base64 of
[key version (1)][nonce (12)][ciphertext][tag (16)], replacing the
un-versioned layout with no legacy decode path (affordable precisely
because adoption is still zero). Adds the key-ring constructor, the
current-version write / data-driven read model, the version byte
authenticated as AES-GCM associated data, and the stateless,
context-free stance that puts per-tenant key selection out of scope.
Rewrites the "no rotation story", "no key identifier or version" and
28-byte overhead statements (now 29 bytes before Base64), rebases every
line citation against the branch source, and adds a 2026-08-15 revision
note. Documents work landing via MMCA.Common PR #247, unreleased.
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01KwnaQjfkHbHqoMm4Pec3oE
---
assets/data/search-index.json | 2 +-
.../adr/037-field-level-encryption-at-rest.md | 263 +++++++++++++-----
.../037-field-level-encryption-at-rest.html | 230 +++++++++++----
sitemap.xml | 2 +-
4 files changed, 365 insertions(+), 132 deletions(-)
diff --git a/assets/data/search-index.json b/assets/data/search-index.json
index b487421..b0c18d1 100644
--- a/assets/data/search-index.json
+++ b/assets/data/search-index.json
@@ -1 +1 @@
-{"v":1,"n":1211,"r":[{"u":"/docs/adr/index.html","d":"Architecture Decision Records","k":"Architecture Decision Records","x":"Accepted ADRs explaining why the core cross-cutting patterns exist. Read these before changing a pattern they describe: they capture context and trade-offs that aren't obvious…","i":"ApplicationLayer_DoesNotUseRawQueryableSurfaces PushNotificationSettings.ChannelKeyPattern ApiParameterDescriptorBackfillProvider IWriteRepository.SetOriginalRowVersion ServiceInfoVersioningContractTestsBase MMCA.Common.LayerEnforcement.targets SoftDeletedUserCache.MarkerDuration SessionCookieAuthenticationHandler AggregateRootEntityControllerBase ConfigureEndpointsWithHealthProbe OAuthControllerBase.CompleteAsync UpdateRequestsAreConcurrencyAware"},{"u":"/docs/adr/index.html#writing-a-new-adr","d":"Architecture Decision Records","k":"Architecture Decision Records","t":"Writing a new ADR","x":"Copy the structure of an existing record: Status (Proposed / Accepted / Superseded, date and link when superseding), Context (the forces and the problem), Decision (what we…"},{"u":"/docs/adr/001-manual-dto-mapping.html","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records"},{"u":"/docs/adr/001-manual-dto-mapping.html#status","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Status","x":"Accepted. Mechanism clarified 2026-06-26: the per-entity mappers are Riok.Mapperly source-generated (compile-time), not hand-written line by line. The decision to avoid runtime…"},{"u":"/docs/adr/001-manual-dto-mapping.html#context","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Context","x":"Domain entities must be mapped to DTOs for API responses. The two common approaches are: 1. Manual mapping classes (IEntityDTOMapper ) 2. Convention-based reflection mapping…","i":"IEntityDTOMapper TEntity TDTO TId"},{"u":"/docs/adr/001-manual-dto-mapping.html#decision","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Decision","x":"Use explicit, per-entity DTO mappers (each a Riok.Mapperly [Mapper] partial class whose MapToDTO body is source-generated at compile time) registered via Scrutor assembly…","i":"IEntityRequestMapper IEntityDTOMapper SpeakerDTOMapper TIdentifierType TCreateRequest UserMapping TEntityDTO MapToDTOs UseMapper MapToDTO partial TEntity"},{"u":"/docs/adr/001-manual-dto-mapping.html#rationale","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Rationale","x":"- Compile-time safety: Mapping errors surface at build time, not runtime. Property renames break the build rather than silently mapping null. - Testability: Each mapper is a…","i":"SpeakerDTOMapper MapToDTO null"},{"u":"/docs/adr/001-manual-dto-mapping.html#trade-offs","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Trade-offs","x":"- More files (30 DTO mappers across Store + ADC: 19 in ADC, 11 in Store, plus the parallel IEntityRequestMapper classes). The interface's default MapToDTOs implementation is…","i":"IEntityRequestMapper MapToDTOs"},{"u":"/docs/adr/002-navigation-populators.html","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records"},{"u":"/docs/adr/002-navigation-populators.html#status","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Status","x":"Accepted"},{"u":"/docs/adr/002-navigation-populators.html#context","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Context","x":"The application supports multiple database backends (SQL Server, Cosmos DB, SQLite). EF Core's .Include() works for SQL Server but fails for Cosmos DB cross-container…","i":"IDataSourceService.HaveIncludeSupport NavigationMetadataProvider declaringType IsCollection Navigation targetType Include"},{"u":"/docs/adr/002-navigation-populators.html#decision","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Decision","x":"Each entity that has unsupported navigations gets a INavigationPopulator implementation. A DeclarativeNavigationPopulator base class (added in MMCA.Common) allows populators to…","i":"DeclarativeNavigationPopulator ChildNavigationDescriptor FKNavigationDescriptor INavigationDescriptor INavigationPopulator NavigationLoader Product.Category Event.Rooms TEntity WHERE"},{"u":"/docs/adr/002-navigation-populators.html#rationale","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Rationale","x":"- Multi-DB support: The query pipeline automatically falls back from Include to NavigationPopulator when the data source reports navigations as unsupported. - Batch efficiency:…","i":"DeclarativeNavigationPopulator"},{"u":"/docs/adr/002-navigation-populators.html#trade-offs","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Trade-offs","x":"- Extra abstraction layer for SQL Server (where Include works fine). Mitigated: the populator is only called when the query pipeline's metadata says navigations are unsupported.…","i":"NullNavigationPopulator"},{"u":"/docs/adr/003-outbox-dual-dispatch.html","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#status","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-19 (integration-event routing via IMessageBus, lease-based claims for safe scale-out, dead-letter visibility, post-commit dispatch; see Revision below).…","i":"IMessageBus"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#context","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Context","x":"Domain events must be reliably published after aggregate changes are persisted. Two failure modes exist: 1. In-process dispatch fails (e.g., handler throws): the event is lost if…"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#decision","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Decision","x":"Use a dual-dispatch strategy: 1. Outbox persistence: Domain events are serialized into OutboxMessage rows within the same database transaction as the aggregate changes. This…","i":"DomainEventDispatcher BackgroundService SaveChangesAsync OutboxProcessor OutboxMessage"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#rationale","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Rationale","x":"- Guaranteed delivery: The outbox table is written atomically with the aggregate changes. Even if the process crashes after persistence, the background processor catches up. -…","i":"OutboxPollFilterProcessor ProcessingDelaySeconds BrokerMessageBus OutboxProcessor BrokerEventBus IMessageBus OutboxPoll"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#trade-offs","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Trade-offs","x":"- Domain event handlers must be idempotent (this is a good practice regardless). - The outbox table grows until processed entries are cleaned up: OutboxCleanupService purges rows…","i":"OutboxCleanupService HasMoreEligibleWork ProcessedOn MaxRetries RetryCount"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-07-19","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-07-19)","x":"Four changes from the 2026-07-19 full review: 1. Integration events route through the outbox to IMessageBus, never local dispatch. An IIntegrationEvent raised via AddDomainEvent…","i":"DomainEventSaveChangesInterceptor outbox.dead_letter.count OutboxCleanupService ExecuteUpdateAsync IIntegrationEvent type_unresolvable integrationEvent OutboxProcessor AddDomainEvent OutboxMessage IMessageBus LockedUntil"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-07-24","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Three capture-side corrections found in a code review. None change the dual-dispatch decision; they close gaps between what it promised and what the interceptor did. 1. Capture…","i":"ExecuteInTransactionAsync RemoveDomainEvents IAggregateRoot SavingChanges RetryCount DbContext LastError catch"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-08-01","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"One retry-pacing correction. The dual-dispatch decision is unchanged; the Trade-offs above described a cadence the processor no longer has. 1. Retry backoff is explicit, and it…","i":"RetryBackoffBaseSeconds"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-08-07","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"One retry-pacing refinement. The decision and the curve are unchanged; the waits are no longer identical across a batch. 1. The retry backoff carries random jitter. The…"},{"u":"/docs/adr/004-authentication-dual-fetch.html","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records"},{"u":"/docs/adr/004-authentication-dual-fetch.html#status","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Status","x":"Accepted."},{"u":"/docs/adr/004-authentication-dual-fetch.html#context","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Context","x":"When the modular monolith is extracted into per-module service hosts behind a gateway (ADR-008), every service must authenticate the same end-user JWT, but only one service…"},{"u":"/docs/adr/004-authentication-dual-fetch.html#decision","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Decision","x":"Validate cross-service tokens with asymmetric (RS256) signatures plus JWKS / OIDC discovery, keeping the symmetric (HS256) shared-secret path as the in-process monolith default.…","i":"TokenValidationParameters.ValidAlgorithms id_token_signing_alg_values_supported OpenIdConnectMetadataWarmupTask JwtSettings.SigningAlgorithm BuildValidationParameters MapOidcDiscoveryEndpoint response_types_supported AddCommonAuthentication subject_types_supported AddForwardedJwtBearer WithJwksDiscovery RsaPublicKeyPath"},{"u":"/docs/adr/004-authentication-dual-fetch.html#rationale","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Rationale","x":"- No shared signing key. Only Identity can mint tokens; every other service holds only the public key it fetched, so a compromised non-Identity service cannot forge tokens, and…"},{"u":"/docs/adr/004-authentication-dual-fetch.html#trade-offs","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Trade-offs","x":"- More moving parts than a shared secret. RS256 needs key generation, distribution of the public half, a JWKS endpoint, and discovery wiring, versus one symmetric string. -…"},{"u":"/docs/adr/004-authentication-dual-fetch.html#related","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Related","x":"ADR-007 (gRPC calls forward the validated JWT downstream via JwtForwardingClientInterceptor), ADR-008 (the extraction that split issuer and validator into separate processes),…","i":"JwtForwardingClientInterceptor"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html#status","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Status","x":"Accepted. Updated 2026-06-27 (documented the [Pii] → IAnonymizable build-time guard and PiiRedactor).","i":"IAnonymizable PiiRedactor Pii"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html#context","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Context","x":"The framework's default deletion model is soft-delete: AuditableBaseEntity.Delete() sets IsDeleted = true and EF Core global query filters exclude the row from normal queries.…","i":"AuditableBaseEntity.Delete OutboxMessage IsDeleted true"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html#decision","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Decision","x":"Separate the two concerns and provide an extension point for each, rather than overloading soft-delete: 1. Soft-delete stays the default for lifecycle/state management (hide +…","i":"MMCA.Common.Domain.Attributes.PiiAttribute MMCA.Common.Domain.Interfaces MMCA.Common.Domain.Privacy EncryptedStringConverter PiiConventionTestsBase OutboxCleanupService IAnonymizable PiiRedactor Anonymize Result User Pii"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html#rationale","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Rationale","x":"- Right tool per concern: soft-delete answers \"is this record active?\"; erasure answers \"has this person's data been removed?\". Conflating them (e.g. hard-deleting inside…","i":"Delete"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html#trade-offs","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Trade-offs","x":"- Erasure is opt-in per entity, but the opt-in is guarded: an aggregate exposing a [Pii]-marked property that does not implement IAnonymizable fails the architecture fitness…","i":"IAnonymizable Pii"},{"u":"/docs/adr/006-database-per-service.html","d":"ADR-006: Database per Service","k":"Architecture Decision Records"},{"u":"/docs/adr/006-database-per-service.html#status","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-07). Supersedes the earlier \"deliberately one shared database\" stance. Clarified 2026-06-27: the single context class became one sealed context class per engine…","i":"Name"},{"u":"/docs/adr/006-database-per-service.html#context","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Context","x":"When the modules were first extracted into independently-deployable services, all services in an app still pointed at a single shared SQL database with a single OutboxMessages…","i":"CrossDataSourceDegradeConvention EntityDataSourceRegistry DataSourceResolver DbContextFactory OutboxProcessor OutboxMessages"},{"u":"/docs/adr/006-database-per-service.html#decision","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Decision","x":"Adopt database-per-service: each service owns its own physical database with its own OutboxMessages table. - One sealed concrete context class per engine, one instance per…","i":"CrossDataSourceDegradeConvention PhysicalDbContextFactory ApplicationDbContext INavigationPopulator DataSourceResolver SQLServerDbContext ADC_Notification CosmosDbContext OutboxProcessor SqliteDbContext ADC_Conference ADC_Engagement"},{"u":"/docs/adr/006-database-per-service.html#rationale","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Rationale","x":"- Removes the shared-outbox race (the sharpest cost of the shared DB) without an OriginService filter: physical isolation is simpler and stronger than a logical filter. - Real…","i":"OriginService"},{"u":"/docs/adr/006-database-per-service.html#trade-offs","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Trade-offs","x":"- No cross-database FKs or transactions. Relationships that span services degrade to scalar IDs; consistency across services is eventual (outbox + broker), not transactional. -…"},{"u":"/docs/adr/007-grpc-extraction.html","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records"},{"u":"/docs/adr/007-grpc-extraction.html#status","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Status","x":"Accepted."},{"u":"/docs/adr/007-grpc-extraction.html#context","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Context","x":"Once modules became separate service processes, the in-process interface calls between them (e.g. Conference → Engagement's IBookmarkCountService, Engagement → Conference's…","i":"ISessionBookmarkValidationService IBookmarkCountService Result"},{"u":"/docs/adr/007-grpc-extraction.html#decision","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Decision","x":"Use gRPC, exposed through MMCA.Common.Grpc, with a contract-package convention: - .Contracts projects hold the .proto definitions plus a gRPC adapter that implements the same…","i":"SessionBookmarkValidationServiceGrpcAdapter GrpcResultExceptionInterceptor JwtForwardingClientInterceptor Directory.Build.props AddTypedGrpcClient SocketsHttpHandler MMCA.Common.Grpc HandleFailure IReadOnlyList RpcException serviceName Contracts"},{"u":"/docs/adr/007-grpc-extraction.html#rationale","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Rationale","x":"- No business-logic rewrite: the gRPC adapter implements the interface modules already depend on; swapping in-process for cross-process is a registration change. - Transport…","i":"MicroserviceExtractionTests ServiceContract MassTransit version proto"},{"u":"/docs/adr/007-grpc-extraction.html#trade-offs","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Trade-offs","x":"- Bidirectional pairs need care. Conference ↔ Engagement is a mutual gRPC pair; the AppHost deliberately omits a reciprocal WaitFor to avoid a startup deadlock: transient \"peer…","i":"Http1AndHttp2 WaitFor Http2"},{"u":"/docs/adr/008-service-extraction-topology.html","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records"},{"u":"/docs/adr/008-service-extraction-topology.html#status","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Status","x":"Accepted."},{"u":"/docs/adr/008-service-extraction-topology.html#context","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Context","x":"ADC began as a modular monolith: one MMCA.ADC.WebAPI host loaded every module (Identity, Conference, Engagement, Notification) in-process via the ModuleLoader, sharing one…","i":"MMCA.ADC.WebAPI ModuleLoader"},{"u":"/docs/adr/008-service-extraction-topology.html#decision","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Decision","x":"Extract one service host per module: MMCA.ADC.{Identity,Conference,Engagement,Notification}.Service and front them with a single YARP reverse-proxy Gateway (MMCA.ADC.Gateway,…","i":"MicroserviceExtractionTests MMCA.ADC.Gateway MMCA.ADC.WebAPI ModuleLoader Notification Conference Engagement Identity MMCA.ADC Modules Service Module"},{"u":"/docs/adr/008-service-extraction-topology.html#rationale","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Rationale","x":"- No business-logic rewrite. Because a service is just the monolith with one module enabled, extraction was a hosting/wiring change, not a domain change, and the module-isolation…","i":"ModuleLoader"},{"u":"/docs/adr/008-service-extraction-topology.html#trade-offs","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Trade-offs","x":"- Operational complexity. Four deployables plus a Gateway, service discovery, a broker, and per-service databases, versus one process. Mitigated locally by Aspire orchestration…","i":"MMCA.Common.API ServiceDefaults Http1AndHttp2 Http2"},{"u":"/docs/adr/008-service-extraction-topology.html#applicability","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Applicability","x":"This ADR is framed around ADC (the first repo extracted), but the same topology is now the framework's standard extraction shape, not an ADC-only choice. MMCA.Store followed it:…","i":"MMCA.Store.Gateway MMCA.Store.WebAPI MMCA.Store Identity Catalog Service Sales"},{"u":"/docs/adr/008-service-extraction-topology.html#related","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (outbox dual dispatch), ADR-004 (cross-service token validation via JWKS), ADR-006 (database per service), and ADR-007 (gRPC cross-service calls) are the facet decisions…"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#status","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-14)"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#context","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Context","x":"The framework already supplies the mechanisms for surviving partial failure: a standard Polly resilience handler (timeout / retry / circuit breaker), the outbox for at-least-once…","i":"ConfigureHttpClientDefaults AddTypedServiceClient AddTypedGrpcClient MMCA.Common.Aspire"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#decision","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Decision","x":"1. Resilience is a framework invariant, not a per-call choice. Every outbound HttpClient and gRPC client registered through the framework's extension methods (AddTypedGrpcClient,…","i":"MMCA.Common.Grpc.Tests ResilienceHandlerTests AddTypedServiceClient AddTypedGrpcClient MMCA.Common.Aspire HttpClient"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#rationale","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Rationale","x":"- Invariant over discipline. A fitness function turns \"remember to add resilience\" into a build gate: the same approach the framework already uses for the layer rules and the…"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#trade-offs","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Trade-offs","x":"- The named gate (ResilienceHandlerTests, MMCA.Common.Grpc.Tests) asserts that the gRPC client path (AddTypedGrpcClient) registers the standard handler, not the runtime behavior…","i":"ResilienceCircuitBreakerFaultInjectionTests MMCA.Common.Grpc.Tests ResilienceHandlerTests AddTypedServiceClient AddTypedGrpcClient"},{"u":"/docs/adr/010-integration-event-schema-versioning.html","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records"},{"u":"/docs/adr/010-integration-event-schema-versioning.html#status","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-19). Updated 2026-06-27 (Helpdesk enforcement gap closed; all three consumers now gate the convention). Updated 2026-08-14 (ADC now gates seven events, and a…"},{"u":"/docs/adr/010-integration-event-schema-versioning.html#context","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Context","x":"Integration events cross service boundaries (Identity → Conference, Conference ↔ Engagement, …) and are resolved by consumers solely by their type string: the outbox serializes…","i":"OutboxMessage.FromDomainEvent DateOccurred EventType MessageId"},{"u":"/docs/adr/010-integration-event-schema-versioning.html#decision","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Decision","x":"1. Every integration event carries an explicit SchemaVersion. BaseIntegrationEvent exposes public virtual int SchemaVersion = 1;. It is serialized with the payload…","i":"MMCA.Common.Testing.Architecture EventConventionTestsBase BaseIntegrationEvent IIntegrationEvent UserRegisteredV2 SchemaVersion virtual public int"},{"u":"/docs/adr/010-integration-event-schema-versioning.html#rationale","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Rationale","x":"- A signal, enforced. A version field plus a build-gating convention test turns \"remember the contract\" into something the tooling checks: the same invariant-over-discipline…","i":"virtual"},{"u":"/docs/adr/010-integration-event-schema-versioning.html#trade-offs","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Trade-offs","x":"- SchemaVersion is a signal, not a mechanism: by itself it does not stop a consumer breaking on a real reshape. The load-bearing half is the discipline (new type + upcaster); the…","i":"MMCA.Helpdesk.Architecture.Tests EventVersioningConventionTests ProductCreatedIntegrationEvent MMCA.Store.Architecture.Tests TicketOpenedIntegrationEvent MMCA.ADC.Architecture.Tests OrderPlacedIntegrationEvent EventConventionTestsBase CommonArchitectureMap ArchitectureTests.cs EventConventionTests MMCA.ECommerce"},{"u":"/docs/adr/011-single-locale-i18n.html","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records"},{"u":"/docs/adr/011-single-locale-i18n.html#status","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Status","x":"Superseded by ADR-027 (2026-06-27). Originally Accepted (2026-06-19). The \"if multi-locale is ever required\" scope below is the blueprint ADR-027 implements; this record is…"},{"u":"/docs/adr/011-single-locale-i18n.html#context","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Context","x":"The MMCA applications (the ADC conference app, the Store) and the MMCA.Common.UI library currently ship a single locale (en-US). The architecture rubric scores…","i":"MMCA.Common.UI"},{"u":"/docs/adr/011-single-locale-i18n.html#decision","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Decision","x":"1. Single-locale (en-US) is an explicit non-goal for now. User-facing strings are inline in markup; dates/numbers use invariant or fixed formatting where appropriate. 2. The…","i":"RequestLocalization"},{"u":"/docs/adr/011-single-locale-i18n.html#rationale","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Rationale","x":"- Recording the decision converts an implicit rubric-zero into a conscious, revisitable choice: the same posture as the single-region DR acceptance in ADR-009. - Premature i18n…"},{"u":"/docs/adr/011-single-locale-i18n.html#trade-offs","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Adding a locale later touches every view plus the formatting paths: a real but bounded effort, accepted. - Hard-coded strings make a future extraction larger; mitigated by the…"},{"u":"/docs/adr/012-grpc-host-transport.html","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records"},{"u":"/docs/adr/012-grpc-host-transport.html#status","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Status","x":"Accepted (re-verified against source 2026-08-14)."},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-06-22-store-converged-to-profile-a","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-06-22): Store converged to Profile A","x":"Store originally chose Profile B, but its cross-service gRPC failed in Azure Container Apps. With Http1AndHttp2 Kestrel + transport: 'auto' ingress on a cleartext endpoint there…","i":"IProductVariantService.ExistsAsync IUserSalesExportService HTTP_1_1_REQUIRED WithJwksDiscovery AddItemCommand Http1AndHttp2 transport identity gateway httpGet Http2"},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-09-adc-notification-adds-a-mixed-endpoint-profile-per-endpoint-protocols","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-09): ADC Notification adds a mixed-endpoint profile (per-endpoint protocols)","x":"The live-channel push pipeline (ADR-039) gave ADC's Notification service an inbound cleartext gRPC server (LiveChannelPushService.PushToChannel, called best-effort by Engagement…","i":"LiveChannelPushService.PushToChannel engagementService.WithReference services__notification__grpc__0 appsettings.Development.json additionalPortMappings notificationService Http1AndHttp2 httpGet WaitFor Http2 grpc http"},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-25-probe-listeners-are-adcs-answer-not-tcp-probes-and-gateway-routed-jwks-is-a-local-only-rule","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-25): probe listeners are ADC's answer, not TCP probes; and gateway-routed JWKS is a local-only rule","x":"Two claims above were written from an earlier state of the code and no longer describe either app. 1. ADC probes never touch the traffic endpoint; TCP probes were then…","i":"HTTP_1_1_REQUIRED WithJwksDiscovery identityApp.name Program.cs tcpSocket transport identity gateway httpGet Http1 grpc"},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-28-the-probe-listener-is-the-single-pattern-in-both-apps-no-tcp-probes-anywhere","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-28): the probe listener is the single pattern in both apps (no TCP probes anywhere)","x":"Store PR 55 (commit 297064bb, merged 2026-07-27) ported ADC's dedicated probe listener to Store, so the Store-only tcpSocket exception recorded in the 2026-07-25 update above is…","i":"HealthProbe__Port Http1AndHttp2 tcpSocket httpGet Http1 Http2"},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-07-the-probe-listener-moved-into-mmcacommon-and-notifications-grpc-endpoint-carries-a-second-service","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-07): the probe listener moved into MMCA.Common, and Notification's gRPC endpoint carries a second service","x":"1. One shared framework method, not a per-service file. The KestrelConfiguration.cs copies the two updates above cite no longer exist in either app. The pattern was extracted…","i":"UserNotificationExportGrpcService MMCA.ADC.Notification.Contracts services__notification__grpc__0 identityService.WithReference appsettings.Development.json HttpProtocols.Http1AndHttp2 redeclareCleartextEndpoint ConfigureEndpointDefaults KestrelConfiguration.cs additionalPortMappings ASPNETCORE_ENVIRONMENT LiveChannelGrpcService"},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-14-stores-sales-runs-the-mixed-endpoint-profile-too-so-no-pure-profile-b-host-remains","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-14): Store's Sales runs the mixed-endpoint profile too, so no pure Profile B host remains","x":"Sales gained an inbound gRPC edge of its own (IUserSalesExportService, the Identity-driven data-subject export), and it resolved that the same way ADC's Notification did: not by…","i":"identityService.WithReference appsettings.Development.json UserSalesExportGrpcService AddSalesUserExportClient services__sales__grpc__0 IUserSalesExportService additionalPortMappings RequireAuthorization HealthProbe__Port Http1AndHttp2 salesService _grpc.sales"},{"u":"/docs/adr/012-grpc-host-transport.html#context","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Context","x":"Once modules were extracted into separate service hosts (ADR-008) that call each other synchronously over gRPC (ADR-007), each service's Kestrel had to serve both REST traffic…","i":"HTTP_1_1_REQUIRED Http1AndHttp2 HttpClient Http2"},{"u":"/docs/adr/012-grpc-host-transport.html#decision","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Decision","x":"Pick one of two coherent transport profiles per app, and wire the gateway forwarder and JWKS discovery to match. Use when services must serve gRPC on cleartext (any bidirectional…","i":"builder.ConfigureEndpointsWithHealthProbe UserNotificationExportGrpcService HttpProtocols.Http1AndHttp2 ConfigureEndpointDefaults LiveChannelGrpcService HttpVersion.Version20 RequestVersionOrLower HttpProtocols.Http2 RequestVersionExact HTTP_1_1_REQUIRED WithJwksDiscovery Http1AndHttp2"},{"u":"/docs/adr/012-grpc-host-transport.html#rationale","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Rationale","x":"- The Kestrel protocol choice is the root constraint; the gateway-forward mode and the JWKS authority are downstream consequences, not independent knobs. Documenting them as a…","i":"HTTP_1_1_REQUIRED Http2"},{"u":"/docs/adr/012-grpc-host-transport.html#trade-offs","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two profiles to keep straight. A service that gains an inbound gRPC edge must migrate from Profile B to Profile A and flip ForwardHttp2 and the JWKS wiring together, or it…","i":"appsettings.Development.json additionalPortMappings appsettings.json Http1AndHttp2 ForwardHttp2 transport http2"},{"u":"/docs/adr/012-grpc-host-transport.html#related","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Related","x":"- ADR-004 (cross-service token validation via JWKS / OIDC discovery), ADR-007 (gRPC cross-service calls), ADR-008 (monolith → services + gateway topology), ADR-039 (live-channel…"},{"u":"/docs/adr/013-result-pattern.html","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records"},{"u":"/docs/adr/013-result-pattern.html#status","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-21 (exception-handler chain / ProblemDetails edge contract documented)."},{"u":"/docs/adr/013-result-pattern.html#context","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Context","x":"Operations at every layer fail in expected ways: input is invalid, a domain invariant is broken, a requested entity is missing, a uniqueness conflict occurs, the caller lacks…"},{"u":"/docs/adr/013-result-pattern.html#decision","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Decision","x":"Model expected failures as values using Result / Result (MMCA.Common.Shared.Abstractions), not exceptions. - A Result is either success or failure; a failure carries one or more…","i":"OperationCanceledExceptionHandler ApiControllerBase.HandleFailure MMCA.Common.Shared.Abstractions GrpcResultExceptionInterceptor AddCommonExceptionHandlers OperationCanceledException ValidationExceptionHandler DbUpdateExceptionHandler DomainExceptionHandler GlobalExceptionHandler UnprocessableEntity ValidationException"},{"u":"/docs/adr/013-result-pattern.html#rationale","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Rationale","x":"- Failures are in the signature. A method that can fail returns Result , so the caller cannot silently ignore the failure path the way an uncaught exception allows. - Category,…","i":"Result.Failure HandleFailure ErrorType IsFailure requestId Result"},{"u":"/docs/adr/013-result-pattern.html#trade-offs","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Trade-offs","x":"- More ceremony at call sites than letting an exception bubble; the combinators absorb most of it. - Two error channels coexist (Result for expected, exceptions for exceptional).…","i":"GlobalExceptionHandler ErrorType Result"},{"u":"/docs/adr/013-result-pattern.html#related","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Related","x":"ADR-007 (Result over the wire via gRPC), ADR-014 (the decorator pipeline returns Result.Failure to short-circuit a command before it reaches the handler).","i":"Result.Failure"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#status","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-19 (Transactional semantics: rollback on business failure + post-commit event dispatch; see Revision below)."},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#context","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Context","x":"Commands and queries share cross-cutting concerns: validation, transactions, cache invalidation, logging / timing, and feature gating. Putting that logic inside each handler…"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#decision","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Decision","x":"Use single-responsibility handlers behind a Scrutor-composed decorator pipeline. - ICommandHandler and IQueryHandler (MMCA.Common.Application) are one handler per use case, each…","i":"ModuleLoader.DiscoverAndRegister ScanModuleApplicationServices ProfilingCommandDecorator AddApplicationDecorators AddApplicationProfiling MMCA.Common.Application ProfilingQueryDecorator ICacheInvalidating AddInfrastructure ICommandHandler IQueryCacheable AddApplication"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#rationale","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Rationale","x":"- Thin, testable handlers. A handler has no transaction, logging, or caching plumbing, so it is unit-tested in isolation. - One place to read and change the pipeline. The order…"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#trade-offs","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Trade-offs","x":"- Registration order is the reverse of execution order (a Scrutor foot-gun), mitigated by the inline ordering comments in AddApplicationDecorators(). - Decorators must be…","i":"AddApplicationDecorators"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-07-19","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-07-19)","x":"Two Transactional-decorator semantics changed with the 2026-07-19 full review: - A returned business failure now rolls the transaction back. Previously a handler returning…","i":"DbContextFactory.ExecuteInTransactionAsync DomainEventSaveChangesInterceptor RollbackTransaction Result.Failure IsFailure Result"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#related","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Related","x":"ADR-013 (Result, the short-circuit currency of the pipeline), ADR-003 (handlers raise domain events that the outbox drains after SaveChanges; its 2026-07-19 revision pairs with…","i":"SaveChanges"},{"u":"/docs/adr/015-architecture-fitness-functions.html","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records"},{"u":"/docs/adr/015-architecture-fitness-functions.html#status","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Status","x":"Accepted"},{"u":"/docs/adr/015-architecture-fitness-functions.html#context","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Context","x":"The codebase rests on invariants that are easy to state and easy to erode by accident: clean- architecture layer flow (Domain depends on nothing above it), module isolation (no…","i":"SchemaVersion"},{"u":"/docs/adr/015-architecture-fitness-functions.html#decision","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Decision","x":"Enforce architectural invariants as automated checks that gate the build, in two layers. 1. Compile-time guard. MMCA.Common.LayerEnforcement.targets (imported for every Source/…","i":"MMCA.Common.LayerEnforcement.targets MMCA.Common.Testing.Architecture HelpdeskArchitectureMap CommonArchitectureMap StoreArchitectureMap AdcArchitectureMap IArchitectureMap ProjectReference dotnet test"},{"u":"/docs/adr/015-architecture-fitness-functions.html#rationale","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Rationale","x":"- Invariant over discipline. Turning \"do not do X\" into a red build is the only enforcement that scales. It is the same lever used by the layer rules, the resilience gate…","i":"IArchitectureMap"},{"u":"/docs/adr/015-architecture-fitness-functions.html#trade-offs","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Trade-offs","x":"- The tests assert structure / registration, not runtime behavior. ADR-009's test proves a client wires resilience, not that its policy values are correct; parameter tuning stays…","i":"FrameworkSanityTests IArchitectureMap"},{"u":"/docs/adr/015-architecture-fitness-functions.html#related","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Related","x":"ADR-009 (resilience gate), ADR-010 (event-version gate), ADR-016 (MassTransit pin gate), and ADR-006/007/008 (the transport and module-isolation rules the suite enforces)."},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#status","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Amended (2026-07-28): the fitness function now gates two commercial-license majors (MassTransit and SixLabors.ImageSharp), so the decision is restated as…","i":"MassTransit.Azure.ServiceBus.Core Directory.Packages.props SixLabors.ImageSharp"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#context","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common publishes its MMCA.Common. NuGet package set (see FACTS.md for the authoritative list and count) consumed by three downstream repos: the two production apps (Store,…","i":"Directory.Packages.props Infrastructure MassTransit MT_LICENSE FACTS.md Domain"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#decision","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Decision","x":"1. Version the whole MMCA.Common. package set in lockstep. All packages share one version (MinVer, derived from a single vX.Y.Z git tag); a release tags every package (see…","i":"MassTransit.Azure.ServiceBus.Core RestorePackagesWithLockFile DependencyVersionTestsBase MMCA.Common.Infrastructure Directory.Packages.props MassTransit.RabbitMQ SixLabors.ImageSharp MassTransit MT_LICENSE FACTS.md Obsolete vX.Y.Z"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#rationale","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Rationale","x":"- One version, one compatibility story. Lockstep removes the N-package matrix: \"everything on vX.Y.Z\" is the only supported combination, which is the right trade for a small…","i":"vX.Y.Z"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#trade-offs","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Trade-offs","x":"- A consumer cannot adopt a single package in isolation: it takes the whole set at the new version. - Lockstep will bump a package whose code did not change (acceptable: the…","i":"MassTransit.Azure.ServiceBus.Core Directory.Packages.props dependabot.yml"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#related","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the fitness function that enforces the pins), ADR-003 / ADR-006 (MassTransit is the broker transport behind the outbox and database-per-service flows)."},{"u":"/docs/adr/017-request-idempotency.html","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records"},{"u":"/docs/adr/017-request-idempotency.html#status","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-01: the guard around execute-and-store is now an IDistributedLock resolved from DI (Redis-backed wherever a connection multiplexer is registered, which…","i":"IDistributedLock ObjectResult NoContent"},{"u":"/docs/adr/017-request-idempotency.html#context","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Context","x":"Write endpoints (POST / PUT / PATCH) are exposed to client retries and double-submits: a flaky network, an impatient user double-clicking, or a resilience pipeline re-issuing a…","i":"Result"},{"u":"/docs/adr/017-request-idempotency.html#decision","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Decision","x":"Provide opt-in, client-driven request idempotency as an MVC action filter in MMCA.Common.API. - Opt-in per action. [Idempotent] (IdempotentAttribute, a ServiceFilterAttribute…","i":"IdempotencySettings.CacheExpirationHours InProcessDistributedLock IConnectionMultiplexer ServiceFilterAttribute KeyedSemaphoreStripe RedisDistributedLock IdempotentAttribute AddInfrastructure IdempotencyFilter IDistributedLock StatusCodeResult MMCA.Common.API"},{"u":"/docs/adr/017-request-idempotency.html#rationale","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Rationale","x":"- Safety at the edge, not in every handler. Deduplication lives in one filter, so a handler stays a thin use case (ADR-014) and does not grow ad-hoc \"did I already do this?\"…","i":"Idempotent"},{"u":"/docs/adr/017-request-idempotency.html#trade-offs","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cross-instance mutual exclusion follows Redis, so it is a deployment property, not a guarantee. Every ADC and Store service host registers a Redis IConnectionMultiplexer when a…","i":"IConnectionMultiplexer StatusCodeResult IAnonymizable ObjectResult Idempotent Location redis"},{"u":"/docs/adr/017-request-idempotency.html#related","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (handler idempotency for outbox/event consumers, a distinct concern), ADR-013 (Result is the response the filter caches/replays), ADR-014 (the filter keeps the handler…","i":"ICacheService"},{"u":"/docs/adr/018-polyglot-persistence.html","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records"},{"u":"/docs/adr/018-polyglot-persistence.html#status","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Status","x":"Accepted. The framework plumbing is complete, covered by unit and integration tests (DataSourceResolverTests, CrossDataSourceDegradeConventionTests, EntityTypeConfigurationTests,…","i":"CrossDataSourceDegradeConventionTests CosmosConfigurationPortabilityTests MultiSourceSqliteIntegrationTests EntityTypeConfigurationTests DataSourceResolverTests FACTS.md Session Room"},{"u":"/docs/adr/018-polyglot-persistence.html#context","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Context","x":"ADR-006 (database-per-service) splits storage along the Name axis: several physically separate databases, all on the same engine (SQL Server), one per service. A second,…","i":"DataSourceKey Engine Name"},{"u":"/docs/adr/018-polyglot-persistence.html#decision","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Decision","x":"Support three storage engines behind one entity model and one set of repository abstractions, selected per entity configuration. 1. DataSource engine enum: SQLServer (full…","i":"CrossDataSourceDegradeConvention EntityTypeConfigurationSQLServer EntityTypeConfigurationCosmos EntityTypeConfigurationSqlite SQLServerMigrationsAssembly CosmosIntIdValueGenerator SQLServerConnectionString EntityDataSourceRegistry EntityTypeConfiguration CosmosConnectionString SqliteConnectionString ApplicationDbContext"},{"u":"/docs/adr/018-polyglot-persistence.html#rationale","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Rationale","x":"- Right store per access pattern, as a configuration decision. The engine becomes an attribute on a configuration class, not a rewrite. The same domain entity, application…"},{"u":"/docs/adr/018-polyglot-persistence.html#trade-offs","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Trade-offs","x":"- No cross-engine JOINs, FKs, or transactions. This is the ADR-006 cost made sharper: across engines it is a hard limit, not a deployment choice. A query spanning engines (for…","i":"SpecificationsDoNotNavigateToOtherEntities CrossSourceSpecification specifications keys"},{"u":"/docs/adr/018-polyglot-persistence.html#related","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (database-per-service: the Name axis this ADR's Engine axis is orthogonal to; they share DataSourceKey), ADR-002 (navigation populators bridge the relationships the…","i":"DataSourceKey"},{"u":"/docs/adr/019-rate-limiting.html","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records"},{"u":"/docs/adr/019-rate-limiting.html#status","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-01 (the auth-ip per-IP anonymous-authentication limiter, which the shared auth controller applies to login and register by default, is recorded as the…"},{"u":"/docs/adr/019-rate-limiting.html#context","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Context","x":"Every service exposes read and write endpoints to the public internet through the gateway (ADR-008). Abusive or runaway clients (scrapers, credential stuffing, retry storms, a…"},{"u":"/docs/adr/019-rate-limiting.html#decision","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Decision","x":"Rate limiting is layered, and the always-on global limiter is authenticated-only. 1. A global limiter that only caps authenticated callers. AddCommonRateLimiting…","i":"HttpContext.Connection.RemoteIpAddress EnableRateLimitingAttribute UseCommonMiddlewarePipeline AttributeUsage.Inherited LoginProtectionService AddCommonRateLimiting RateLimitPolicyAuthIp GetCustomAttributes UseForwardedHeaders AuthControllerBase EnableRateLimiting EndpointDataSource"},{"u":"/docs/adr/019-rate-limiting.html#rationale","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Rationale","x":"- Limit the traffic that is both attributable and expensive. An authenticated request is tied to a principal and usually drives the database; capping per-principal stops a single…"},{"u":"/docs/adr/019-rate-limiting.html#trade-offs","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Trade-offs","x":"- The global limiter only protects the authenticated surface. The anonymous surface is covered endpoint by endpoint instead: login and register carry the auth-ip limiter by…","i":"ForwardLimit"},{"u":"/docs/adr/019-rate-limiting.html#related","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the JWKS/discovery traffic the limiter exempts, and the authenticated principal it keys on), ADR-008 (the gateway edge this protects), ADR-017 (request idempotency, the…"},{"u":"/docs/adr/020-permission-based-authorization.html","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records"},{"u":"/docs/adr/020-permission-based-authorization.html#status","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-25, amended 2026-07-10)."},{"u":"/docs/adr/020-permission-based-authorization.html#context","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Context","x":"Authorization started as pure role-based access control (RBAC). Endpoints declared the role they required with [Authorize(Policy = ...)] against named policies: RequireOrganizer,…","i":"RequireAuthenticatedUser RequireAuthenticated RequireOrganizer RequireAttendee RequireAdmin RequireRole Authorize Policy"},{"u":"/docs/adr/020-permission-based-authorization.html#decision","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Decision","x":"Add a permission (capability) layer over RBAC, opt-in and backward-compatible. - A central registry maps roles to permissions. IPermissionRegistry / PermissionRegistry…","i":"DefaultAuthorizationPolicyProvider PermissionAuthorizationHandler AuthClaimTypes.Permission PermissionRegistryBuilder AddAuthorizationPolicies PermissionPolicyProvider RequireAuthenticatedUser MMCA.Common.Shared.Auth RoleNames.ContentEditor HasPermissionAttribute PermissionRequirement IPermissionRegistry"},{"u":"/docs/adr/020-permission-based-authorization.html#rationale","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Rationale","x":"- Capabilities decouple endpoints from roles. A route says what it does (conference:sessions:manage), and who may do it is a registry decision, so adding ContentEditor with a…","i":"ContentEditor"},{"u":"/docs/adr/020-permission-based-authorization.html#trade-offs","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Trade-offs","x":"- It is still RBAC, not ABAC. The model resolves role to permission; it does not evaluate resource or attribute conditions. Per-resource ownership (\"a customer may read only…","i":"ConferencePermissions OwnerOrAdminFilter AddPermissions IAnonymizable Idempotent Grant"},{"u":"/docs/adr/020-permission-based-authorization.html#related","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the authenticated principal and claims this keys on, including the optional permission claim), ADR-008 (each extracted service authorizes independently, so the registry…","i":"permission"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#status","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-09; adoption reviewed 2026-07-15)."},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#context","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Context","x":"ADR-003 makes integration-event delivery at-least-once: the outbox guarantees a published event is not lost, and the MassTransit broker redelivers on consumer failure.…"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#decision","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Decision","x":"Add an opt-in inbox that records each successfully-processed integration event by its MessageId and skips redeliveries. - Every event carries a MessageId. BaseDomainEvent stamps…","i":"IX_InboxMessages_MessageId IntegrationEventConsumer SessionFeedbackSubmitted SpeakerUnlinkedFromUser EventFeedbackSubmitted AlreadyProcessedAsync ProductVariantChanged OutboxCleanupService SpeakerLinkedToUser AddBrokerMessaging MarkProcessedAsync AttendeeCheckedIn"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#rationale","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Rationale","x":"- Dedup once, not in every handler. A single consume-edge check turns \"every handler author must remember to be idempotent against redelivery\" into a framework guarantee for the…","i":"NoOpInboxStore"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#trade-offs","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Trade-offs","x":"- Not exactly-once. The crash-after-handler-before-inbox window reprocesses once, so handlers must stay idempotent for it; the inbox narrows the duplicate window, it does not…","i":"InboxMessages EnableInbox MessageId"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#related","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox and at-least-once delivery whose consumer side this deduplicates; handler idempotency is still required for the crash window), ADR-006 (the inbox lives in the…","i":"OutboxCleanupService"},{"u":"/docs/adr/022-browser-session-cookie-auth.html","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records"},{"u":"/docs/adr/022-browser-session-cookie-auth.html#status","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Status","x":"Accepted."},{"u":"/docs/adr/022-browser-session-cookie-auth.html#context","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Context","x":"The apps are Blazor Web Apps: a server-rendered (SSR) prerender pass runs on the first request, then an interactive phase (Blazor Server or WebAssembly) takes over.…","i":"Authorization localStorage Authorize"},{"u":"/docs/adr/022-browser-session-cookie-auth.html#decision","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Decision","x":"Carry the session in HttpOnly cookies and add an authentication scheme that reads them during SSR prerender. The mechanism ships in MMCA.Common.API (SessionCookies/) with a…","i":"SessionCookieAuthenticationHandler CookieSessionRefresher mmca_auth_refresh HttpContext.User mmca_auth_access SessionCookieJar MMCA.Common.API MMCA.Common.UI Authorize DELETE POST"},{"u":"/docs/adr/022-browser-session-cookie-auth.html#rationale","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Rationale","x":"- Fixes the fresh-GET prerender gap. Without a server-readable session, every deep-link or F5 to an [Authorize] page would redirect to /login despite a valid session; the cookie…","i":"Authorize"},{"u":"/docs/adr/022-browser-session-cookie-auth.html#trade-offs","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Trade-offs","x":"- A non-validating auth scheme exists. SessionCookieAuthenticationHandler trusts a cookie it does not cryptographically verify. This is sound only because (a) the cookie is…","i":"SessionCookieAuthenticationHandler ISessionCookieSync"},{"u":"/docs/adr/022-browser-session-cookie-auth.html#related","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the JWT/JWKS validation the API performs on every call, which is why the SSR handler can skip signature validation), ADR-008 (the gateway and topology the UI talks to),…"},{"u":"/docs/adr/023-security-response-headers.html","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records"},{"u":"/docs/adr/023-security-response-headers.html#status","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02)."},{"u":"/docs/adr/023-security-response-headers.html#context","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Context","x":"Every client-facing host (the YARP Gateway and the Blazor UI web host in each app) must stamp the same hardened HTTP response headers: X-Content-Type-Options, X-Frame-Options,…"},{"u":"/docs/adr/023-security-response-headers.html#decision","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Decision","x":"Ship one security-headers middleware in MMCA.Common.Aspire (MMCA.Common.Aspire.Security), registered with AddCommonSecurityHeaders(configuration?, configure?) and inserted early…","i":"SecurityHeadersSettings.ContentSecurityPolicy SecurityHeadersMiddlewareTests MMCA.Common.Aspire.Security SecurityHeadersMiddleware AddCommonSecurityHeaders MMCA.Common.Aspire.Tests UseCommonSecurityHeaders BlazorCspPolicyProvider SecurityHeadersSettings StaticCspPolicyProvider AddCommonBlazorCsp ICspPolicyProvider"},{"u":"/docs/adr/023-security-response-headers.html#rationale","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Rationale","x":"- One hardened default, defined once. Centralizing the header set removes per-host drift and makes a new edge host secure by default rather than by remembering to copy headers. -…","i":"ICspPolicyProvider"},{"u":"/docs/adr/023-security-response-headers.html#trade-offs","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Trade-offs","x":"- The baseline CSP is intentionally incomplete. An API/Gateway host gets default-src 'self'-style protection but no script-src/style-src discipline unless it registers a fuller…","i":"SecurityHeadersSettings.ContentSecurityPolicy AddCommonSecurityHeaders BlazorCspPolicyProvider AddCommonBlazorCsp ICspPolicyProvider MMCA.Common.UI.Web TryAddSingleton ApiSettings"},{"u":"/docs/adr/023-security-response-headers.html#related","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Related","x":"ADR-019 (rate limiting, the other always-on edge protection living in the same Aspire layer), ADR-022 (browser session-cookie auth, the other browser-edge security control),…"},{"u":"/docs/adr/024-push-notifications.html","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records"},{"u":"/docs/adr/024-push-notifications.html#status","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-15). Revised 2026-08-07 (transactional email recorded as an app-level concern outside the channel model; see Revision below). Revised…","i":"Enabled"},{"u":"/docs/adr/024-push-notifications.html#context","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Context","x":"The framework needs to deliver user-facing notifications (an organizer broadcasting a schedule change, a per-user alert). Two delivery models each fail on their own. A pure…"},{"u":"/docs/adr/024-push-notifications.html#decision","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Decision","x":"Deliver notifications over two channels from one application use case, with the transport and the recipient policy both behind abstractions. - A durable per-user inbox plus a…","i":"NullNotificationRecipientProvider PushNotificationSettings.Enabled INotificationRecipientProvider SignalRPushNotificationSender SendPushNotificationHandler SignalRLiveChannelPublisher MMCA.Common.Infrastructure NullPushNotificationSender IPushNotificationSender MMCA.Common.Application CancellationToken.None NotificationHubService"},{"u":"/docs/adr/024-push-notifications.html#rationale","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Rationale","x":"- Each channel covers the other's failure mode. The inbox guarantees eventual delivery to offline users; the push gives connected users immediacy. Persisting the inbox before…","i":"INotificationRecipientProvider IPushNotificationSender IMessageBus"},{"u":"/docs/adr/024-push-notifications.html#trade-offs","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Fan-out write amplification. One UserNotification row is written per recipient, so a broadcast to a large audience is a large insert. This is fine for the current per-event /…","i":"NullPushNotificationSender AddPushNotifications PushNotification UserNotification Authorization access_token IsRead ReadOn"},{"u":"/docs/adr/024-push-notifications.html#related","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox dual-dispatch path, which is distinct: that carries service-to-service integration events, this carries user-facing notifications), ADR-004 (the /hubs…","i":"MMCA.ADC.Notification.Service SendPushNotificationHandler NullNativePushSender Http1AndHttp2 access_token Http2"},{"u":"/docs/adr/024-push-notifications.html#revision-2026-08-07","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Records transactional email, a delivery path the channel model above never mentions. The decision is unchanged: this closes a documentation gap so the asymmetry reads as…","i":"OrderPaymentFailedSagaHandler SendPushNotificationHandler IPushNotificationSender ILiveChannelPublisher IPushDeviceRegistrar IDomainEventHandler AddInfrastructure INativePushSender OrderPaidHandler PushNotification UserNotification SmtpEmailSender"},{"u":"/docs/adr/025-startup-warmup-readiness.html","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records"},{"u":"/docs/adr/025-startup-warmup-readiness.html#status","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27). Revised 2026-07-28: /health/ready now excludes optional-tagged checks as well as live-tagged ones (see Decision), and the absence of a warm-up timeout was…","i":"optional live"},{"u":"/docs/adr/025-startup-warmup-readiness.html#context","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Context","x":"On the Azure Container Apps Consumption plan a replica that has been idle is CPU-throttled, and a scale-from-zero or scaled-out replica starts cold. The first authenticated…"},{"u":"/docs/adr/025-startup-warmup-readiness.html#decision","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Decision","x":"Ship a small warm-up subsystem in MMCA.Common.Aspire, wired into AddServiceDefaults() so every host gets it. - A readiness gate that starts closed. WarmupReadinessGate…","i":"OpenIdConnectMetadataWarmupTask OperationCanceledException WarmupReadinessHealthCheck MapDefaultEndpoints WarmupHostedService WarmupReadinessGate AddServiceDefaults AddWarmupReadiness IHttpClientFactory MMCA.Common.Aspire TaskTimeoutSeconds cancellationToken"},{"u":"/docs/adr/025-startup-warmup-readiness.html#rationale","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Rationale","x":"- Keep cold replicas out of rotation, briefly. Gating readiness on warm-up means the platform does not send a user request to a replica that is still doing its first handshakes,…","i":"AddServiceDefaults"},{"u":"/docs/adr/025-startup-warmup-readiness.html#trade-offs","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Trade-offs","x":"- A replica can enter rotation not fully warm. The gate is opened in a finally once the Task.WhenAll over every registered task returns, that is, once each task has completed,…","i":"ConfigurationManager TimeoutException stoppingToken Task.WhenAll WaitAsync finally"},{"u":"/docs/adr/025-startup-warmup-readiness.html#related","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the OIDC discovery document the built-in task pre-fetches, and the auth-side view of the same cold-start), ADR-009 (the Polly resilience pipeline that absorbs the lazy…"},{"u":"/docs/adr/026-caching-strategy.html","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","i":"ICacheService"},{"u":"/docs/adr/026-caching-strategy.html#status","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-10, 2026-07-23, 2026-07-25, 2026-08-14). Amended by ADR-077 (2026-08-13): Tier 1's substrate gains a third, opt-in implementation…","i":"HybridCacheService"},{"u":"/docs/adr/026-caching-strategy.html#context","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Context","x":"The framework needs caching in two distinct places. Inside the application pipeline, query results are memoized and invalidated on mutation (the Caching decorators of ADR-014,…","i":"ICacheInvalidating IQueryCacheable"},{"u":"/docs/adr/026-caching-strategy.html#decision","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Decision","x":"Cache in two tiers, each with its own substrate. - One abstraction. ICacheService (MMCA.Common.Application/Interfaces/ICacheService.cs) exposes GetAsync / SetAsync / RemoveAsync…","i":"builder.Services.AddStackExchangeRedisOutputCache OutputCacheOptions.AddPublicEndpointPolicy AbsoluteExpirationRelativeToNow PublicEndpointOutputCachePolicy CacheOptions.DefaultExpiration CacheOptions.DefaultDuration DistributedCacheEntryOptions DistributedCacheService IConnectionMultiplexer LoginProtectionService MemoryDistributedCache AddCommonHybridCache"},{"u":"/docs/adr/026-caching-strategy.html#rationale","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Rationale","x":"- One substrate, swapped by environment. Keeping ICacheService as the only thing application code sees lets the deployment decide memory vs distributed. The auto-swap (presence…","i":"ICacheInvalidating IDistributedCache ICacheService"},{"u":"/docs/adr/026-caching-strategy.html#trade-offs","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Trade-offs","x":"- Memory mode is per-replica. In the in-process store each replica caches independently; a scaled-out deployment that did not wire Redis would see cross-replica staleness bounded…","i":"ICacheService.IncrementAsync AddRedisDistributedCache DistributedCacheService StackExchangeRedisCache IConnectionMultiplexer AddOutputCache AddRedisClient RemoveAsync WRONGTYPE NoCache absexp sldexp"},{"u":"/docs/adr/026-caching-strategy.html#related","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the Caching decorators and IQueryCacheable / ICacheInvalidating markers that consume this substrate), ADR-019 (output caching as the anonymous-traffic lever, and…","i":"LoginProtectionService HybridCacheService ICacheInvalidating IQueryCacheable IncrementAsync ICacheService WRONGTYPE"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-24","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Three substrate corrections from a code review. 1. An optional key namespace (Cache:KeyPrefix). Services sharing one cache instance also share one keyspace, and nothing stopped…","i":"RedisCacheOptions.InstanceName ICacheService.IncrementAsync DistributedCacheService EvictionReason.Replaced KeyedSemaphoreStripe RemoveByPrefixAsync MemoryCacheService IMemoryCache InstanceName CacheKey INCR"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-25","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"An audit against the code. No behavior changed; the ADR text did. 1. The IncrementAsync entry above was wrong. It described a Redis INCR override. There is no such override, and…","i":"DistributedCacheService StackExchangeRedisCache IncrementAsync AddCaching INCR"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-28","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-28)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. Tier 2. Store Catalog's…","i":"AddStackExchangeRedisOutputCache DistributedCacheService MMCA.Common.API ICacheService AddCaching"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-01","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. AddCaching. Now at…","i":"AddStackExchangeRedisOutputCache DistributedCacheService RemoveByPrefixAsync ScanAndDeleteAsync IncrementAsync AddCaching remarks returns"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-07","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. AddCaching. Now at…","i":"AddStackExchangeRedisOutputCache DistributedCacheService MemoryDistributedCache TimeSpan.FromSeconds MemoryCacheService IDistributedCache MMCA.Common.API CacheOptions AddCaching"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-13","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-13)","x":"Tier 1 is amended by ADR-077, which is where the decision and its trade-offs are recorded. The three points that change the reading of this record: 1. A third substrate, opted…","i":"Microsoft.Extensions.Caching.Hybrid DistributedCacheService StackExchangeRedisCache AddCommonHybridCache HybridCacheService MemoryCacheService IDistributedCache IncrementAsync AddCaching WRONGTYPE prefix INCR"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-14","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"One substrate correction plus a line-anchor re-verification. No decision and no behavior changed. 1. The 30-second default now has a named home, CacheOptions.DefaultDuration.…","i":"AddStackExchangeRedisOutputCache AbsoluteExpirationRelativeToNow CacheOptions.DefaultDuration DistributedCacheEntryOptions AddRedisDistributedCache DistributedCacheService HybridCacheEntryOptions TimeSpan.FromSeconds app.UseOutputCache HybridCacheService DefaultExpiration DefaultDuration"},{"u":"/docs/adr/027-multi-locale-i18n.html","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records"},{"u":"/docs/adr/027-multi-locale-i18n.html#status","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-02, 2026-07-03, 2026-07-09, and 2026-07-29; corrected 2026-08-01: the pseudo-locale CI gate is required on all three browser engines, and…"},{"u":"/docs/adr/027-multi-locale-i18n.html#context","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Context","x":"ADR-011 recorded single-locale (en-US) as a deliberate, revisitable non-goal and sketched what re-introducing i18n would entail. That revisit has now happened: the framework adds…","i":"InteractiveAuto Error Code"},{"u":"/docs/adr/027-multi-locale-i18n.html#decision","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Decision","x":"1. Supported cultures are an explicit allowlist: en-US (default) + es. Adding a locale is adding a .es.resx sibling set and one allowlist entry, not new infrastructure. 2.…","i":"MmcaCultureBootstrap.SetBrowserCultureAsync ErrorHttpMapping.BuildErrorsExtension DomainInvariantViolationException CultureInfo.DefaultThreadCurrent LocalizedTextConventionTestsBase MMCA.Common.Testing.Architecture SupportedCultures.ResolveClosest ApiControllerBase.HandleFailure ResourceTranslationsAreComplete SupportedCultures.PseudoLocale CookieRequestCultureProvider CultureInfo.InvariantCulture"},{"u":"/docs/adr/027-multi-locale-i18n.html#rationale","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Rationale","x":"- Keying error localization on the existing Error.Code is the cheapest correct extension point. The codes are already stable and already cross the wire; localizing at the edge…","i":"ResourcesPath Error.Code resx"},{"u":"/docs/adr/027-multi-locale-i18n.html#trade-offs","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every view and every user-facing message is touched: a large, mostly mechanical sweep, accepted as the cost ADR-011 always named. - WASM Spanish formatting needs ICU…","i":"InvariantGlobalization ResxMudLocalizer MudTranslations BlazorWebView MudLocalizer"},{"u":"/docs/adr/027-multi-locale-i18n.html#related","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Related","x":"ADR-011 (superseded), ADR-013 (the Error.Code this localizes on), ADR-015 (the i18n gates now live here: the MA0076 culture-less formatting build gate and the…","i":"ResourceTranslationsAreComplete BlazorWebView Error.Code MA0076"},{"u":"/docs/adr/028-dark-theme-mode.html","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records"},{"u":"/docs/adr/028-dark-theme-mode.html#status","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27; revised 2026-07-15)."},{"u":"/docs/adr/028-dark-theme-mode.html#context","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Context","x":"MMCATheme (MMCA.Common.UI/Theme/MMCATheme.cs) has always defined a complete, brand-tuned PaletteDark alongside PaletteLight, but MudThemeProvider was hard-wired to light: no…","i":"MudThemeProvider InteractiveAuto PaletteLight PaletteDark IsDarkMode MMCATheme ref"},{"u":"/docs/adr/028-dark-theme-mode.html#decision","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Decision","x":"1. Bind the existing theme. The shared MainLayout renders a single component (MMCA.Common.UI/Layout/MainLayout.razor:14), which owns the four Mud providers plus the Day/Dark…","i":"ThemeService.InitializeAsync User.PreferredCulture User.PreferredTheme MMCATheme.Instance MmcaThemeProviders OnAfterRenderAsync InteractiveServer systemPrefersDark window.matchMedia MudThemeProvider MMCA.Common.UI ThemeService"},{"u":"/docs/adr/028-dark-theme-mode.html#rationale","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Rationale","x":"- Reusing the i18n cookie/profile machinery means one persistence model for both user preferences, instead of two subtly different ones. Theme and locale are the same shape of…","i":"BrandColorTokenTests"},{"u":"/docs/adr/028-dark-theme-mode.html#trade-offs","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Trade-offs","x":"- The same FOUC hazard as locale is not yet closed for theme. The SSR data-theme/inline-script read is unimplemented (Decision 3), so the first paint can briefly flash the wrong…","i":"MainLayout User"},{"u":"/docs/adr/028-dark-theme-mode.html#related","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Related","x":"ADR-027 (shares the cookie source-of-truth and the User preference migration, and is the model for the theme no-flash SSR bootstrap that is not yet wired), ADR-022 (the SSR…","i":"User"},{"u":"/docs/adr/029-authentication-brute-force-protection.html","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#status","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27). Updated 2026-07-02 (the check/increment/reset call sequence was hoisted into AuthenticationServiceBase ; the adoption note and the \"convention the consumer…","i":"AuthenticationServiceBase TUser"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#context","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Context","x":"ADR-019's global rate limiter is authenticated-only: it caps requests per authenticated principal and deliberately exempts anonymous traffic. The highest-value anonymous attack…","i":"RateLimitPolicyAuthIp AuthControllerBase"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#decision","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Decision","x":"Provide a framework ILoginProtectionService (MMCA.Common.Application.Auth) with a single implementation LoginProtectionService (MMCA.Common.Infrastructure.Auth), registered…","i":"RegistrationRateLimitWindowMinutes CheckRegistrationRateLimitAsync IncrementRegistrationCountAsync MMCA.Common.Infrastructure.Auth ICacheService.IncrementAsync IncrementFailedAttemptsAsync MaxRegistrationsPerIpPerHour MMCA.Common.Application.Auth FailedAttemptWindowMinutes AuthenticationServiceBase ResetFailedAttemptsAsync DistributedCacheService"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#rationale","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Rationale","x":"- Complements ADR-019 rather than duplicating it. ADR-019 carries two limiter layers and this is the third on top of them: its global limiter caps authenticated throughput per…","i":"Result"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#trade-offs","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cache-scoped state weakens under scale-out without Redis. In memory mode the counters are per-replica and evaporate on restart, so a multi-replica deployment that did not wire…","i":"AuthenticationServiceBase ILoginProtectionService AuthenticationService TUser"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#related","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Related","x":"ADR-019 (the layered limiter: an authenticated-only global cap that exempts this anonymous surface, plus the per-IP auth-ip window that now sits on the same two endpoints),…","i":"ICacheService Result Error"},{"u":"/docs/adr/030-startup-sole-migrator.html","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records"},{"u":"/docs/adr/030-startup-sole-migrator.html#status","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27)."},{"u":"/docs/adr/030-startup-sole-migrator.html#context","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Context","x":"Under database-per-service (ADR-006), each service owns its own database and its own migrations project, so something must apply pending migrations on every deploy. The…","i":"ApplicationSettings.DatabaseInitStrategy DatabaseInitializationExtensions EnsureCreated"},{"u":"/docs/adr/030-startup-sole-migrator.html#decision","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Decision","x":"In Azure Container Apps, every service host runs ApplicationSettingsDatabaseInitStrategy = Migrate in production and is the sole migrator of its own database: it applies its…","i":"ApplicationSettings__DatabaseInitStrategy __EFMigrationsHistory DatabaseInitStrategy MigrateAsync minReplicas deploy.yml migrations database Migrate dotnet sqlcmd update"},{"u":"/docs/adr/030-startup-sole-migrator.html#rationale","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Rationale","x":"- One migrator, one mechanism. The code that owns the schema applies the schema; there is no second tool to keep in lockstep and no ordering race between a deploy step and…","i":"__EFMigrationsHistory"},{"u":"/docs/adr/030-startup-sole-migrator.html#trade-offs","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Auto-migrate-in-production is what \"None\" exists to prevent. An unintended or destructive migration would ship itself on the next deploy. The apps accept this; the build-time…","i":"minReplicas"},{"u":"/docs/adr/030-startup-sole-migrator.html#related","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (database-per-service: why each service owns and migrates its own database), ADR-025 (readiness gating keeps traffic off a still-migrating replica), ADR-009 (RTO/RPO +…"},{"u":"/docs/adr/030-startup-sole-migrator.html#revision-2026-08-07","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"The sole-migrator decision extends to seed data: the same startup owner that applies the schema also runs the module seeders, in the same call, on the same boot. The Decision…","i":"moduleLoader.SeedAllAsync ModuleLoader.SeedAllAsync ConferenceModuleDbSeeder InitializeDatabaseAsync __EFMigrationsHistory DatabaseInitStrategy builder.Build IModuleSeeder ExistsAsync DbSeeder Guid int"},{"u":"/docs/adr/031-feature-flag-management.html","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records"},{"u":"/docs/adr/031-feature-flag-management.html#status","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27)."},{"u":"/docs/adr/031-feature-flag-management.html#context","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Context","x":"The apps need to decouple release from deploy: ship code dark, flip a kill switch, or roll a feature out to a percentage of users without a redeploy. A flag has to be enforceable…","i":"FeatureGate"},{"u":"/docs/adr/031-feature-flag-management.html#decision","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Decision","x":"Standardize on Microsoft.FeatureManagement, configured from the \"FeatureManagement\" configuration section and registered once in AddAPI (services.AddFeatureManagement() +…","i":"ApiControllerBase.HandleFailure Microsoft.FeatureManagement.Mvc IFeatureManager.IsEnabledAsync services.AddFeatureManagement FeatureGateCommandDecorator Microsoft.FeatureManagement FeatureGateQueryDecorator Error.NotFoundError ConferenceFeatures EngagementFeatures ErrorType.NotFound CatalogFeatures"},{"u":"/docs/adr/031-feature-flag-management.html#rationale","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Rationale","x":"- Release decoupled from deploy. A kill switch or a percentage rollout becomes a configuration change, not a code change: the central reason feature management exists. - Two…"},{"u":"/docs/adr/031-feature-flag-management.html#trade-offs","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Trade-offs","x":"- The two enforcement points must agree. A flag gated on the controller but not the handler (or vice versa) is a half-protected feature; no fitness rule asserts both are wired,…","i":"IsEnabledAsync"},{"u":"/docs/adr/031-feature-flag-management.html#related","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the decorator pipeline whose outermost slot FeatureGate fills, and the ordering that puts it first), ADR-013 (the Result / Error and ProblemDetails edge the disabled…","i":"FeatureGate Result Error"},{"u":"/docs/adr/032-password-hashing.html","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records"},{"u":"/docs/adr/032-password-hashing.html#status","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-29, adoption note revised 2026-07-06, registration note revised 2026-08-01)."},{"u":"/docs/adr/032-password-hashing.html#context","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Context","x":"Identity stores a credential as a (salt, hash) pair, never plaintext. The framework needs one canonical hasher that every consuming Identity flow shares, so the key-derivation…"},{"u":"/docs/adr/032-password-hashing.html#decision","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Decision","x":"Provide a single IPasswordHasher (MMCA.Common.Application.Interfaces.Infrastructure, IPasswordHasher.cs:6) with one implementation PasswordHasher…","i":"MMCA.Common.Application.Interfaces.Infrastructure CryptographicOperations.FixedTimeEquals RandomNumberGenerator.GetBytes AuthenticationServiceBase Rfc2898DeriveBytes.Pbkdf2 HashAlgorithmName.SHA512 IdentityModuleDbSeeder AuthenticationService ChangePasswordHandler LegacyHmacSaltSize AddInfrastructure ComputeLegacyHash"},{"u":"/docs/adr/032-password-hashing.html#rationale","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Rationale","x":"- One framework-owned primitive, not per-app crypto. Putting the algorithm, work factor, salt size, and comparison in a single shared type means a future hardening (raising…","i":"IsLegacy"},{"u":"/docs/adr/032-password-hashing.html#trade-offs","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Trade-offs","x":"- The legacy branch is a permanent correctness dependency that looks deletable. Its load-bearing role is invisible from the method body alone, so it is the single most…","i":"VerifyPassword Iterations"},{"u":"/docs/adr/032-password-hashing.html#related","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (cross-service JWT / JWKS authentication: the hasher gates credential verification that issues the tokens that ADR-004 then validates across services), ADR-005…","i":"EncryptedStringConverter"},{"u":"/docs/adr/033-resource-ownership-authorization.html","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records"},{"u":"/docs/adr/033-resource-ownership-authorization.html#status","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02, revised 2026-07-25)."},{"u":"/docs/adr/033-resource-ownership-authorization.html#context","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Context","x":"ADR-020 added a permission (capability) layer over RBAC: it answers \"what may this role do\", resolving a role to a permission so an endpoint can require a capability instead of a…","i":"OwnerOrAdminFilter GET"},{"u":"/docs/adr/033-resource-ownership-authorization.html#decision","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Decision","x":"Provide a row/resource-level ownership axis in MMCA.Common.API (the Authorization folder), with two enforcement points keyed on the caller's owner claim (customerid by default)…","i":"ShoppingCartsController.GetAllForLookupAsync ShoppingCartByCustomerSpecification ShoppingCartsController.GetAllAsync AggregateRootEntityControllerBase CustomersController.CreateAsync CustomersController.GetAllAsync OrdersByCustomerSpecification GetOwnershipSpecification OwnerOrAdminFilterOptions ICurrentUserService.Role OwnershipHelper.IsAdmin settings.OwnerClaimType"},{"u":"/docs/adr/033-resource-ownership-authorization.html#rationale","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Rationale","x":"- Reject-one and filter-many are genuinely two mechanisms. A single-resource route has an id to compare, so a short action filter that 403s on a mismatch is the cheapest correct…","i":"IEntityQueryService Specification Criteria TEntity And TId"},{"u":"/docs/adr/033-resource-ownership-authorization.html#trade-offs","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per controller/handler. Neither point is automatic: a controller that forgets the [ServiceFilter] or omits the ownership spec from a query leaks across customers, the…","i":"OwnerOrAdminFilter ServiceFilter customer_id null"},{"u":"/docs/adr/033-resource-ownership-authorization.html#related","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Related","x":"ADR-020 (the role/permission RBAC layer this complements, and whose explicit 020-permission-based-authorization.md:74 scope-out this fills), ADR-034 (the generic entity query…","i":"IEntityQueryService Specification ForbidResult Result"},{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-07-25","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"An audit against the code. No behavior changed; the ADR text did. 1. The per-mutation check's failure shape was described as one branch, and it is two. ValidateOwnershipAsync was…","i":"ICurrentUserService.Role ValidateOwnershipAsync OwnerOrAdminFilter AllowMissingOwner OrdersController Error.Forbidden"},{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-08-01","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Anchor-only correction. No behavior changed; OrdersController was refactored (a constructor parameter added, GetOwnershipSpecification() and the IsAdmin property extracted,…","i":"GetOwnershipSpecification ValidateOwnershipAsync OrdersController Error.Forbidden Error.NotFound IsAdmin"},{"u":"/docs/adr/034-generic-entity-query-layer.html","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/034-generic-entity-query-layer.html#status","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-30). Amended (2026-07-23): the filter strategy registry now also covers long/long? via LongFilterStrategy. Amended (2026-07-25): the keyed by-id fast path is…","i":"TryGetFastPathIncludes LongFilterStrategy long"},{"u":"/docs/adr/034-generic-entity-query-layer.html#context","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Context","x":"Every module exposes many entities, and most of them need the same read and write surface: list, page, look up for a dropdown, fetch by id, create, delete. Hand writing a…"},{"u":"/docs/adr/034-generic-entity-query-layer.html#decision","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Decision","x":"Give every entity a generic REST resource surface and a bounded dynamic query contract, supplied by two controller bases over a shared query pipeline. 1. Generic read controller.…","i":"EntityQueryPipeline.MaxUnboundedResultLimit QueryFieldService.ApplyFieldSelection QueryFilterService.RegisterStrategy IApplicationSettings.MaxPageSize QueryFilterService.ApplyFilters QueryFieldService.ApplySorting MaxUnboundedResultLimit QueryFilterModelBinder INavigationPopulator EntityQueryPipeline SupportedOperators IFilterStrategy"},{"u":"/docs/adr/034-generic-entity-query-layer.html#rationale","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Rationale","x":"- Write the resource surface once, inherit it everywhere. The verbs, routes, filter contract, pagination, and header are defined once on the two bases; a concrete controller…","i":"QueryFilterService.ValidateFilters MaxUnboundedResultLimit DTOToEntityPropertyMap INavigationPopulator DTOMapper.MapToDTOs SupportedOperators IEntityDTOMapper IFilterStrategy MaxPageSize"},{"u":"/docs/adr/034-generic-entity-query-layer.html#trade-offs","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- The wire contract tracks the entity model. Filterable, sortable, and projectable surface is the entity's property set. A model change is an API change unless mediated by the…","i":"QueryFilterService.ValidateFilters MaxUnboundedResultLimit DTOToEntityPropertyMap IFilterStrategy virtual"},{"u":"/docs/adr/034-generic-entity-query-layer.html#related","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-001 (manual DTO mapping: the generic controllers project through IEntityDTOMapper), ADR-002 (navigation populators: the unsupported-include path), ADR-013 (Result pattern at…","i":"IEntityDTOMapper HandleFailure result.Errors Idempotent"},{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-07-24","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Filter and pagination corrections from a code review. The contract shape is unchanged; these close cases where the engine widened a result set instead of narrowing it, or…","i":"IFilterStrategy.CanParseValue PaginationMetadata.PageSize MaxUnboundedResultLimit DTOToEntityPropertyMap Filter.Value.Invalid ValidateFilters FirstOrDefault TotalItemCount ApplyFilters GetByIdAsync int.MaxValue includeFKs"},{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-07-25","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"Citation maintenance from an ADR audit. No decision or behavior changed; the source anchors above were rebased to their current declaration and call-site lines. 1. The fast-path…","i":"IsPrimaryKeyOnlyLookup TryGetFastPathIncludes"},{"u":"/docs/adr/035-optimistic-concurrency.html","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records"},{"u":"/docs/adr/035-optimistic-concurrency.html#status","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02). Amended 2026-07-16: a child-entity overload of SetOriginalRowVersion was added (see Decision).","i":"SetOriginalRowVersion"},{"u":"/docs/adr/035-optimistic-concurrency.html#context","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Context","x":"Every mutable aggregate in the framework is edited through a load-modify-save handler: the update use case fetches the tracked entity, applies the request, and calls…","i":"SaveChangesAsync Conflict"},{"u":"/docs/adr/035-optimistic-concurrency.html#decision","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Decision","x":"Give every auditable entity a database-managed RowVersion concurrency token, round-trip it through the client on updates, and stamp the client's last-seen value as EF's original…","i":"MMCA.Common.Domain.Interfaces.IRowVersioned IWriteRepository.SetOriginalRowVersion ConcurrencyConventionTestsBase MMCA.Store.Architecture.Tests DbUpdateConcurrencyException MMCA.ADC.Architecture.Tests AddRowVersionToAllEntities ConfigureConcurrencyTokens DbUpdateExceptionHandler SetOriginalRowVersion AuditableBaseEntity ErrorType.Conflict"},{"u":"/docs/adr/035-optimistic-concurrency.html#rationale","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Rationale","x":"- Database-managed token over a hand-maintained version field. A SQL Server rowversion auto-increments on the server on every write; no domain code sets or reads it (the setter…","i":"DbUpdateExceptionHandler SetOriginalRowVersion DbUpdateException rowversion WHERE"},{"u":"/docs/adr/035-optimistic-concurrency.html#trade-offs","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in at the caller, not just the type. A null or empty RowVersion skips the check, so a client that never echoes the token still gets last-write-wins. The fitness function…","i":"AddRowVersionToAllEntities DbUpdateExceptionHandler IsConcurrencyToken DbUpdateException UpdateRequest rowversion RowVersion byte"},{"u":"/docs/adr/035-optimistic-concurrency.html#related","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Related","x":"ADR-017 (HTTP request idempotency, which dedups retries of the same request, the mirror-image concern to two distinct edits racing here), ADR-021 (consumer-side inbox, which…","i":"AuditableBaseEntity RowVersion"},{"u":"/docs/adr/036-external-oauth-login.html","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records"},{"u":"/docs/adr/036-external-oauth-login.html#status","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02, migration attribution corrected 2026-07-06, native-callback redirect branch added 2026-07-17 per ADR-043, email-verified account-takeover guard before…"},{"u":"/docs/adr/036-external-oauth-login.html#context","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Context","x":"The framework's Identity story so far is entirely first-party: a user registers with an email and password, the credentials are hashed (ADR-032), and Identity mints its own RS256…","i":"AddPermissions User"},{"u":"/docs/adr/036-external-oauth-login.html#decision","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Decision","x":"Add an opt-in external-login path that federates Google/GitHub sign-in at the edge and immediately exchanges the external identity for the app's own local JWT pair, linking the…","i":"IAuthenticationService.ExternalLoginAsync OAuthControllerBase.CompleteAsync AddExternalLoginProviderFields Auth.ExternalLoginNotSupported Auth.ExternalEmailNotVerified ConfigurationOAuthUISettings Auth.ExternalEmailInvalid User.LinkExternalProvider AddExternalAuthProviders AddCommonAuthentication AuthenticationResponse IAuthenticationService"},{"u":"/docs/adr/036-external-oauth-login.html#rationale","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Rationale","x":"- Terminate federation at the edge, keep one internal identity. Exchanging the external principal for a local JWT the moment the callback returns means every downstream concern…","i":"ExternalLoginAsync ClientId POST User GET"},{"u":"/docs/adr/036-external-oauth-login.html#trade-offs","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per app, and easy to half-wire. The flow needs four cooperating pieces (scheme registration, the controller subclass, the service override, and the OAuthUIBaseUrl…","i":"Auth.ExternalLoginNotSupported Auth.ExternalEmailNotVerified IExternalLoginEmailVerifier OAuth__UIBaseUrl IsExternalLogin email_verified ExternalLogin LoginProvider ProviderKey ClientId User"},{"u":"/docs/adr/036-external-oauth-login.html#related","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the RS256/JWKS token this flow exchanges the external identity for, and validates everywhere after), ADR-022 (the browser cookies that carry the resulting session),…","i":"User.Anonymize CompleteAsync"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#status","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-06; revised 2026-07-24, 2026-07-25)."},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#context","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Context","x":"Transparent database encryption (TDE) protects the data files as a whole, but it decrypts transparently for anyone who can query the database, so a leaked backup restored on a…"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#decision","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Decision","x":"Provide a single framework-owned EF Core value converter that transparently encrypts string columns at rest with authenticated encryption, applied per property in an entity…","i":"MMCA.Common.Infrastructure.Persistence.Encryption ArgumentNullException.ThrowIfNull RandomNumberGenerator.GetBytes EncryptedStringConverterTests MMCA.Common.Infrastructure EncryptedStringConverter CryptographicException ArgumentException FromBase64String AesGcm.Decrypt AesGcm.Encrypt ValueConverter"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#rationale","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Rationale","x":"- Authenticated, not merely confidential. AES-GCM binds a 128-bit tag to the ciphertext (EncryptedStringConverter.cs:48, :85), so a tampered or truncated value fails to decrypt…","i":"AesGcm.Decrypt string"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#trade-offs","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Latent today, proven by tests rather than production. The plumbing is complete and unit-tested, but no entity configuration wires it, so the encrypt/decrypt round-trip, the…","i":"EncryptedStringConverterTests HasConversion"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#related","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (soft-delete vs erasure: the other sensitive-data control, which names this converter as the mechanism for erasure fields that must stay retrievable,…"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-07-24","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Documented a constraint the converter always had but did not state: the ciphertext is non-deterministic. Every write uses a fresh random nonce, which is the correct property for…","i":"Email Where"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-07-25","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"Documentation-only correction, no behavior change. Item 1 of the Decision still illustrated the converter with builder.Property(e = e.Email), contradicting the 2026-07-24…","i":"EncryptedStringConverter.cs SocialSecurityNumber builder.Property e.Email"},{"u":"/docs/adr/038-supply-chain-provenance.html","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records"},{"u":"/docs/adr/038-supply-chain-provenance.html#status","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-06; revised 2026-07-21)."},{"u":"/docs/adr/038-supply-chain-provenance.html#context","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common is a published framework: it packs its NuGet packages and pushes them to GitHub Packages on every v tag (release.yml:3-5), where the two production apps and the…","i":"Directory.Build.props nuget.config"},{"u":"/docs/adr/038-supply-chain-provenance.html#decision","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Decision","x":"Treat supply-chain integrity as a set of build-gating controls, the same invariant-over-discipline posture ADR-015 applies to architecture rules. Four controls, each a hard gate:…","i":"SQLitePCLRaw.bundle_e_sqlite3 RestorePackagesWithLockFile MMCA.Common.Infrastructure Directory.Packages.props Directory.Build.props TreatWarningsAsErrors packageSourceMapping NuGetAuditSuppress packages.lock.json MMCA.Common.slnx nuget.config NuGetAudit"},{"u":"/docs/adr/038-supply-chain-provenance.html#rationale","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Rationale","x":"- Provenance is a gate, not a document. A hard-failing SBOM step means the bill of materials cannot silently go missing on a release: the artifact is produced or the release…","i":"Directory.Build.props NuGetAuditSuppress dotnet list"},{"u":"/docs/adr/038-supply-chain-provenance.html#trade-offs","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The SBOM is generated and archived, not yet signed or attested. The gate proves a bill of materials exists for each release (release.yml:58); it does not add cryptographic…","i":"NuGetAuditSuppress nuget.config dotnet list"},{"u":"/docs/adr/038-supply-chain-provenance.html#related","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Related","x":"ADR-016 (lockstep versioning + the MassTransit-v8 license pin; this record extends dependency governance from versioning and licensing into supply-chain provenance and…"},{"u":"/docs/adr/039-live-channel-push.html","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records"},{"u":"/docs/adr/039-live-channel-push.html#status","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-09)."},{"u":"/docs/adr/039-live-channel-push.html#context","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Context","x":"Conference-day features (live polls, session Q&A, live result counters) need sub-second fan-out of small events to whoever is looking at a page right now. The existing…"},{"u":"/docs/adr/039-live-channel-push.html#decision","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Decision","x":"One realtime transport, two publisher boundaries: - NotificationHub stays the single hub and gains its first client-invokable methods: JoinChannel / LeaveChannel map the calling…","i":"PushNotificationSettings.ChannelKeyPattern SignalRLiveChannelPublisher NullLiveChannelPublisher IPushNotificationSender NotificationHubService ILiveChannelPublisher AddPushNotifications ReceiveChannelEvent LeaveChannelAsync JoinChannelAsync NotificationHub OnChannelEvent"},{"u":"/docs/adr/039-live-channel-push.html#rationale","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Rationale","x":"- One WebSocket per client keeps connection management, token refresh, reconnect, and backplane behavior in one place; channel membership is a property of the existing…","i":"IMessageBus"},{"u":"/docs/adr/039-live-channel-push.html#trade-offs","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Ephemeral means lossy: a client that connects after an event was published never sees it. Features must treat channel events as cache-invalidation hints over fetchable state,…","i":"NotificationCallback"},{"u":"/docs/adr/039-live-channel-push.html#revision-2026-07-24","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Two corrections from a code review; the best-effort, per-session-ordered decision is unchanged. 1. Broadcasts are enqueued after commit, not during the command. CastVoteHandler…","i":"BoundedChannelFullMode.DropOldest SessionQuestionUpvoteChanged LivePollVoteChanged ToggleUpvoteHandler CastVoteHandler DroppedCount itemDropped TryWrite"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#status","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10). Amended (2026-07-10): explicit query-string variance parity with the built-in default policy (the initial release accidentally dropped it, collapsing every…","i":"ContentEditor SponsorsCache NowNextCache bypassRoles Organizer"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#context","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Context","x":"The framework's read-scaling design leans on ASP.NET Core output caching: anonymous-readable endpoints ([AllowAnonymous] GETs like event/session/speaker catalogs) carry named…","i":"AuthDelegatingHandler BookmarkCountsCache AllowAnonymous Authorization NowNextCache"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#decision","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Decision","x":"MMCA.Common.API ships PublicEndpointOutputCachePolicy, an IOutputCachePolicy that mirrors the built-in default policy with one deliberate difference: it does not disable cache…","i":"ConferenceReadAudience.PrivilegedRoles PublicEndpointOutputCachePolicy DbUpdateConcurrencyException IOutputCachePolicy MMCA.Common.API AllowAnonymous Authorization ContentEditor NowNextCache extension Organizer reference"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#rationale","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Rationale","x":"- The response payload, not the request's auth state, is what determines cacheability. For a user-independent payload, Authorization is noise; refusing to cache on it turns the…","i":"Authorization"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#trade-offs","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Trade-offs","x":"- Consumers must audit which named policies move to AddPublicEndpointPolicy. Policies on permission-gated endpoints (e.g. an organizer dashboard) must NOT move; if such an…","i":"AddStackExchangeRedisOutputCache AddRedisDistributedCache AddPublicEndpointPolicy BookmarkCountsCache IDistributedCache EvictByTagAsync AddOutputCache NowNextCache maxReplicas minReplicas TryAdd"},{"u":"/docs/adr/041-observability-and-telemetry.html","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records"},{"u":"/docs/adr/041-observability-and-telemetry.html#status","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10). Amended (2026-07-23) to document the Telemetry:DisableHttpClientMetrics and Telemetry:DisableRuntimeMetrics cost knobs and to correct the…","i":"OutboxProcessor RecordDuration OutboxMetrics OutboxProcess HttpClient finally reason"},{"u":"/docs/adr/041-observability-and-telemetry.html#context","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Context","x":"The framework is a modular monolith whose modules extract into standalone services (ADR-008), so the same telemetry has to make sense whether a request stays in one process or…","i":"HttpClient"},{"u":"/docs/adr/041-observability-and-telemetry.html#decision","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Decision","x":"Standardize telemetry in the shared Aspire service defaults, add framework-specific instrumentation for the CQRS and outbox paths, and expose cost knobs with fail-safe defaults.…","i":"APPLICATIONINSIGHTS_CONNECTION_STRING CqrsMetrics.CommandDuration.Record HttpContext.TraceIdentifier OTEL_EXPORTER_OTLP_ENDPOINT AddOpenTelemetryExporters IsInstrumentationDisabled OutboxPollFilterProcessor outbox.dead_letter.count TraceIdRatioBasedSampler CorrelationIdMiddleware ConfigureOpenTelemetry TryGetTraceSampleRatio"},{"u":"/docs/adr/041-observability-and-telemetry.html#rationale","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Rationale","x":"- Instrument only where auto-instrumentation is blind. The CQRS RED histograms and the outbox dead-letter counter cover the two framework-owned hot paths; everything else (HTTP,…","i":"ParentBased HttpClient true"},{"u":"/docs/adr/041-observability-and-telemetry.html#trade-offs","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Trade-offs","x":"- Custom instrumentation carries a maintenance cost. The Aspire package has no reference to Application or Infrastructure by design, so the meter and activity-source names are…","i":"OutboxProcess ParentBased"},{"u":"/docs/adr/041-observability-and-telemetry.html#related","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox whose dead-letter counter and poll-span filtering this defines), ADR-014 (the CQRS decorator pipeline that emits the RED histograms as a byproduct of its…","i":"AddServiceDefaults"},{"u":"/docs/adr/042-device-capability-abstraction.html","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records"},{"u":"/docs/adr/042-device-capability-abstraction.html#status","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10, amended 2026-07-17, 2026-07-23 and 2026-08-14)."},{"u":"/docs/adr/042-device-capability-abstraction.html#context","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Context","x":"The consumer apps ship the same Blazor component set through three heads: MAUI Blazor Hybrid (Android/iOS/MacCatalyst/Windows), Blazor Server SSR, and WebAssembly. Native device…","i":"builder.Services.AddCommonMauiTokenStorage ITokenStorageService navigator.clipboard navigator.onLine navigator.share MMCA.Common.UI AddUIShared"},{"u":"/docs/adr/042-device-capability-abstraction.html#decision","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Decision","x":"Add a per-capability contract layer to MMCA.Common.UI and a fifteenth package, MMCA.Common.UI.Maui, carrying the native implementations. - One small interface per capability, no…","i":"IExternalLinkService.InterceptsLinks AddBrowserDeviceCapabilities AddDeviceCapabilityDefaults EnforceUIMauiLayerBoundary IConnectivityStatusService AddMauiDeviceCapabilities ILocalNotificationService UseMauiDeviceCapabilities Directory.Packages.props IPushDeviceTokenProvider IPushRegistrationService MauiBackNavigationBridge"},{"u":"/docs/adr/042-device-capability-abstraction.html#rationale","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Rationale","x":"- A god IDeviceCapabilities interface would force every head to implement everything and turn each new capability into a breaking change; per-capability contracts are open/closed…","i":"IDeviceCapabilities AddUIShared"},{"u":"/docs/adr/042-device-capability-abstraction.html#trade-offs","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A fifteenth package raises release surface: two runners must both succeed for a whole release. Accepted; the publish-maui job is gated by the same tag and SBOM discipline. -…","i":"AddMauiDeviceCapabilities UseMauiDeviceCapabilities MauiExternalAuthBroker AddUIShared IsAvailable IsSupported false"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#status","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-07-28 (the Android https App Links leg is recorded as shipped, the outstanding Android item is restated as the served certificate fingerprint,…","i":"REPLACE_WITH_PLAY_APP_SIGNING_SHA256_FINGERPRINT WebAuthenticatorCallbackActivity MapAppAssociationEndpoints sha256_cert_fingerprints MauiExternalAuthBroker AppAssociationOptions IDeepLinkDispatcher assetlinks.json MMCA.ADC.UI.Web CompleteAsync MainActivity AutoVerify"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#context","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Context","x":"Three mobile flows all need a URL to leave the web world and land inside the MAUI app: 1. Shared links and QR codes. The share sheet and QR codes carry ordinary https web URLs.…","i":"OAuthControllerBase.CompleteAsync IDeepLinkDispatcher WebAuthenticator assetlinks.json CompleteAsync"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#decision","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Decision","x":"- Custom-scheme returnUrl allowlist in the framework. CompleteAsync consults OAuth:AllowedReturnUrlSchemes (a config array, default empty). When the challenge's stashed returnUrl…","i":"IAuthUIService.ExchangeOAuthCodeAsync WebAuthenticatorCallbackActivity ITokenStorageService IDeepLinkDispatcher IExternalAuthBroker Uri.OriginalString CFBundleURLTypes WebAuthenticator assetlinks.json CompleteAsync AutoVerify returnUrl"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#rationale","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Rationale","x":"- Reusing the single-use-code exchange keeps the token-never-in-URL invariant identical across web and native; the only new surface is WHERE the code lands. - A scheme allowlist…"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#trade-offs","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Trade-offs","x":"- The app-facing hostname is baked into store binaries (intent filters, entitlements). The apps currently ride the Azure Container Apps default domain, which changes if the…","i":"appsettings.json EmbeddedResource PublicWebHost"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-07-28","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-07-28)","x":"Correction pass from an ADR audit. No decision or behavior changed; the Status section had the Android leg backwards and the Decision section attributed the token exchange to the…","i":"IAuthUIService.ExchangeOAuthCodeAsync ITokenStorageService.SetTokensAsync MapAppAssociationEndpoints sha256_cert_fingerprints AndroidCertFingerprints BuildSuccessRedirectUrl IDeepLinkDispatcher WebAuthenticator CompleteAsync IntentFilter MainActivity OnNewIntent"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-08-01","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Status pass from an ADR audit. No decision and no behavior changed; the one item the previous revision left open is closed, and the anchor that revision itself introduced had…","i":"MapAppAssociationEndpoints sha256_cert_fingerprints AndroidCertFingerprints AndroidPackageName assetlinks.json ApplicationId Program.cs d5fd0e9"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-08-07","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Anchor and precision pass from an ADR audit. No decision and no behavior changed. 1. The two Program.cs anchors moved one line. MMCA.ADC commit 886fa189 (PR 100, merged…","i":"app.MapAppAssociationEndpoints AndroidCertFingerprints AppAssociationOptions AndroidPackageName PublicWebHost GetSection Program.cs new"},{"u":"/docs/adr/044-native-push-delivery.html","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records"},{"u":"/docs/adr/044-native-push-delivery.html#status","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-11). Amends ADR-024. The framework pipeline is implemented and inert by default; each consumer switches it on by provisioning a notification hub with platform…","i":"NativePush"},{"u":"/docs/adr/044-native-push-delivery.html#context","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Context","x":"ADR-024 established two notification channels: a durable per-user UserNotification inbox (the source of truth) and a transient SignalR push behind IPushNotificationSender. Both…","i":"IPushNotificationSender UserNotification"},{"u":"/docs/adr/044-native-push-delivery.html#decision","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Decision","x":"- Azure Notification Hubs as the delivery fan-out. One hub abstracts both platforms behind one API, holds the platform credentials outside our code, and its installation model…","i":"INativePushSender.SendToUsersAsync Notification.PushNotifications MauiPushRegistrationService NullPushDeviceTokenProvider SendPushNotificationHandler AddNativePushNotifications AddNotificationControllers AuthUIService.LogoutAsync IPushDeviceTokenProvider IPushRegistrationService PushRegistrationListener IPushDeviceRegistrar"},{"u":"/docs/adr/044-native-push-delivery.html#consequences","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Consequences","x":"- Sends fan out per 20-user chunk and per platform: an audience of N users costs ceil(N/20) 2 hub calls. Acceptable at conference scale; a template-based send can consolidate…","i":"SendPushNotificationHandler ceil"},{"u":"/docs/adr/045-managed-file-storage-and-avatars.html","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records"},{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#status","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-11). Records the BR-116 amendment (ADC): avatar photos are IN scope, powered by two new framework extension points. The framework legs are implemented; each…"},{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#context","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Context","x":"The MAUI capability program (ADR-042) brought MediaPicker/camera within reach, and ADC amended BR-116 to include user avatar photos. That needs binary blob storage (the databases…"},{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#decision","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Decision","x":"- IFileStorageService (Application): upload-by-blob-name returning the public URI, plus idempotent delete. Default is an unconfigured Null implementation whose uploads fail with…","i":"ImageSharpImageProcessor AddAzureBlobFileStorage IFileStorageService IMediaPickerService ConnectionString IImageProcessor configuration ContainerName FileStorage IsSupported ServiceUri InputFile"},{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#consequences","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Consequences","x":"- The avatars container is public-read by design: avatar URLs render in tags on anonymous-visible surfaces without SAS plumbing. The random blob suffix prevents enumeration; the…","i":"DefaultAzureCredential img"},{"u":"/docs/adr/046-http-api-versioning.html","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records"},{"u":"/docs/adr/046-http-api-versioning.html#status","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-08-01 (anonymity is granted by each per-service subclass, not by ServiceInfoControllerBase; corrected the ADR-034 cross-reference, which puts…","i":"ServiceInfoControllerBase AddCommonApiVersioning EntityControllerBase DefaultApiVersion Asp.Versioning"},{"u":"/docs/adr/046-http-api-versioning.html#context","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Context","x":"The framework's REST surface is served by controllers hosted in extracted service processes behind a YARP gateway. As those services evolve, a response shape has to be able to…","i":"Asp.Versioning SchemaVersion v1.0"},{"u":"/docs/adr/046-http-api-versioning.html#decision","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Decision","x":"Standardize one header-based API-versioning setup in MMCA.Common.API, adopt it in every service host through a single registration call, and keep it exercised by a shared fitness…","i":"ApiParameterDescription.ParameterDescriptor ApiParameterDescriptorBackfillProvider ServiceInfoVersioningContractTestsBase AssumeDefaultVersionWhenUnspecified ServiceInfoControllerBase SubstituteApiVersionInUrl IApiDescriptionProvider AddCommonApiVersioning Asp.Versioning.OpenApi HeaderApiVersionReader ServiceInfoController ServiceInfoV2Response"},{"u":"/docs/adr/046-http-api-versioning.html#rationale","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Rationale","x":"- Header selection keeps URLs stable. Routing stays version-free, so gateway route maps, client URL builders, and OpenAPI paths do not fork per version; a caller opts into a…","i":"AssumeDefaultVersionWhenUnspecified ServiceInfoControllerBase AddCommonApiVersioning ReportApiVersions ServiceInfo"},{"u":"/docs/adr/046-http-api-versioning.html#trade-offs","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Trade-offs","x":"- The class-level version attributes are not inherited. Each per-service subclass must repeat the [ApiVersion(...)] and routing attributes (the same inheritance caveat ADR-036…","i":"AddCommonApiVersioning MapCommonOpenApi OAuthController ApiVersion"},{"u":"/docs/adr/046-http-api-versioning.html#related","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Related","x":"ADR-010 (integration-event schema versioning: the asynchronous, SchemaVersion-carried, consumer-resolved axis this deliberately contrasts with; HTTP versioning here is…","i":"ServiceInfoVersioningContractTestsBase OAuthController ApiController SchemaVersion ApiVersion controller Route"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#status","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15)."},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#context","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Context","x":"Soft-delete is the framework's default deletion model (ADR-005): AuditableBaseEntity.Delete() sets IsDeleted = true and EF global query filters hide the row, but the record…","i":"AuditableBaseEntity.Delete HttpContext.User IsDeleted true"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#decision","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Decision","x":"Add a shared-pipeline middleware, SoftDeletedUserMiddleware (Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:31, BR-133), that rejects an…","i":"DeleteUserHandler.OnAfterSoftDeleteAsync SoftDeletedUserCache.MarkDeletedAsync SoftDeletedUserCache.MarkerDuration context.RequestServices.GetService SoftDeletedUserMiddlewareTests AuditableAggregateRootEntity SoftDeletedUserCache.KeyFor UseCommonMiddlewarePipeline ICurrentUserService.UserId ISoftDeletedUserValidator SoftDeletedUserMiddleware SoftDeletedUserValidator"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#rationale","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Rationale","x":"- Bounds the stateless-JWT revocation gap cheaply. Stateless JWT (ADR-004) has no built-in revocation, so a deactivated account would otherwise stay usable for the full remaining…","i":"ISoftDeletedUserValidator SoftDeletedUserValidator MMCA.Common.API TUser User"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#trade-offs","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Trade-offs","x":"- Revocation is bounded, not immediate. A soft-deleted user whose status is cached as not-deleted keeps passing until that cache entry expires (up to 30 seconds), unless the…","i":"SoftDeletedUserCache.MarkDeletedAsync SoftDeletedUserCache.MarkerDuration ISoftDeletedUserValidator DeleteUserHandler"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#related","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (soft-delete is the deletion model whose still-authenticated tokens this middleware revokes; deleting a user is a soft-delete, not a row removal), ADR-004 (the stateless…"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#revision-2026-08-07","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Re-verified against current source. The decision is unchanged, but three things it described have moved: the validator implementation, the home of the 30-second constant, and the…","i":"SoftDeletedUserCache.MarkerDuration SoftDeletedUserMiddleware SoftDeletedUserValidator TimeSpan.FromSeconds DeleteUserHandler CacheDuration UserId TUser true User"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#status","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-07-21 (corrected the empty-placeholder-folder inventory and the Directory.Build.props and ADC User source citations). Revised 2026-07-28…","i":"Directory.Build.props SponsorIdentifierType UserIdentifierType StronglyTypedIds User"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#context","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Context","x":"Every entity needs an identity type. The framework's base entity is generic over that type: BaseEntity constrains it to notnull and exposes a single required init Id…","i":"UserIdentifierType TIdentifierType IBaseEntity BaseEntity readonly required notnull record struct UserId Value Guid"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#decision","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Decision","x":"Model every identifier as a primitive named through a global-using alias, declared per module, not as a wrapper struct. - Identity is a primitive behind an alias. Each module…","i":"EntityTypeConfigurationSQLServer AuditableAggregateRootEntity AuthenticationServiceBase Directory.Build.props SpeakerIdentifierType AuditableBaseEntity UserIdentifierType LinkedSpeakerId IdentifierType LastModifiedBy GetRepository System.Guid"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#rationale","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Rationale","x":"- Readable signatures at zero runtime cost. GetRepository () reads as intent while the CLR sees a plain int. There is no allocation, boxing, or wrapper indirection per…","i":"UserIdentifierType IEntityDTOMapper System.Text.Json GetRepository JsonConverter IBaseEntity BaseEntity IBaseDTO Shared Guid User int"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#trade-offs","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Trade-offs","x":"- No compile-time protection against swapping same-typed identifiers. An alias is a type synonym, not a distinct type. Because most aliases resolve to int, the compiler will not…","i":"SessionIdentifierType SpeakerIdentifierType UserIdentifierType Shared Guid int"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#related","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Related","x":"ADR-001 (the per-entity DTO mappers are parameterized by this identifier type, IEntityDTOMapper ), ADR-034 (the generic entity controllers and query contract ride on the same…","i":"IEntityDTOMapper TIdentifierType TEntityDTO TEntity Shared"},{"u":"/docs/adr/049-library-configureawait-policy.html","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records"},{"u":"/docs/adr/049-library-configureawait-policy.html#status","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-20; measurements re-anchored 2026-08-07 and 2026-08-14)."},{"u":"/docs/adr/049-library-configureawait-policy.html#context","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common ships as NuGet packages consumed by host applications, not as an application itself. Library code that awaits without ConfigureAwait(false) captures the caller's…","i":"SynchronizationContext MMCA.Common.UI.Maui ConfigureAwait editorconfig VSTHRD111 RCS1090 CA2007 MA0004 false"},{"u":"/docs/adr/049-library-configureawait-policy.html#decision","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Decision","x":"Packaged non-UI framework code awaits with ConfigureAwait(false); UI component packages and application code do not. - Enforcement is a build gate, not a convention. The…","i":"TreatWarningsAsErrors MMCA.Common.UI.Maui MMCA.Common.UI.Web ConfigureAwait MMCA.Common.UI editorconfig VSTHRD111 RCS1090 warning CA2007 MA0004 false"},{"u":"/docs/adr/049-library-configureawait-policy.html#rationale","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Rationale","x":"- Correctness for the one consumer that already has a context. The MAUI head consumes Infrastructure/Application/API packages through DI; a sync-over-async call anywhere in that…","i":"ConfigureAwait GetAwaiter GetResult script batch false fixes place step but"},{"u":"/docs/adr/049-library-configureawait-policy.html#trade-offs","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Visual noise in framework source. Every await in Source/ (except UI packages) carries .ConfigureAwait(false) (324 sites at adoption; 693 gated sites as of the 2026-08-14…","i":"ConfigureAwait editorconfig false"},{"u":"/docs/adr/049-library-configureawait-policy.html#related","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Related","x":"ADR-042 (the MAUI package whose synchronization context motivates the policy), ADR-027 (the same \"machine-boundary hygiene as a build gate\" posture applied to culture-explicit…"},{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-07","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"An audit against the code. The policy did not change; three statements about it did. 1. The exemption covers three packages, not the two the Decision named. The glob is…","i":"CodeAnalysisTreatWarningsAsErrors ServerTokenStorageService TreatWarningsAsErrors MMCA.Common.UI.Maui MMCA.Common.UI.Web ConfigureAwait MMCA.Common.UI analyzers PackageId foreach warning CA2007"},{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-14","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"A re-measurement only. The policy, the gate and the exemption are unchanged; the counts the document quotes were a week old and had moved by roughly 9%. 1. Framework site counts,…","i":"CodeAnalysisTreatWarningsAsErrors ServerTokenStorageService TreatWarningsAsErrors MMCA.Common.UI.Maui MMCA.Common.UI.Web ConfigureAwait MMCA.Common.UI analyzers PackageId warning CA2007 dotnet"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#status","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-21)."},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#context","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Context","x":"Identity issues two credentials on every successful sign-in: a short-lived, stateless JWT access token that every service validates by signature and expiry (ADR-004), and a…","i":"AuthenticationServiceBase TUser"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#decision","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Decision","x":"Mint a stateless JWT access token plus a single, server-stored refresh token that rotates on every use, with a token mismatch triggering revocation. - Access token is stateless;…","i":"TokenService.GetPrincipalFromExpiredToken JwtSettings.AccessTokenExpirationMinutes JwtSettings.RefreshTokenExpirationDays TokenService.GenerateRefreshToken TokenService.RefreshTokenLifetime TokenService.GenerateAccessToken RandomNumberGenerator.GetBytes user.RevokeRefreshToken user.UpdateRefreshToken AuthenticationService RefreshTokenLifetime RefreshTokenExpiry"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#rationale","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Rationale","x":"- Short access token plus refresh keeps the hot path stateless. Every service validates the access token with no store lookup (ADR-004); the short exp bounds the revocation gap,…","i":"exp"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#trade-offs","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Trade-offs","x":"- One refresh token per user means one live session. A new login overwrites the single stored token (AuthenticationServiceBase.cs:298), so signing in on a second device…","i":"JwtSettings.RefreshTokenExpirationDays RefreshTokenExpirationDays RefreshTokenLifetime TimeSpan.Zero TokenService"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#related","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the stateless RS256/JWKS access token this refresh flow reissues, and the algorithm pinning GetPrincipalFromExpiredToken relies on), ADR-032 (the password hashing that…","i":"GetPrincipalFromExpiredToken AuthenticationServiceBase TUser"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#status","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-23). Revised 2026-08-14 (SetTokensAsync now writes the refresh token and the access token under one shared guard, so a failed refresh-token write also drops…","i":"SetTokensAsync"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#context","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Context","x":"ADR-022 and ADR-050 describe the two server halves of authentication: the Blazor host's HttpOnly session cookie that survives SSR prerender (ADR-022), and the Identity service's…","i":"HttpContext"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#decision","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Decision","x":"Model the client token lifecycle as two small abstractions (ITokenStorageService for persistence, ITokenRefresher for reacquisition) plus a shared bearer-attaching handler and a…","i":"AddClientAuthSessionCookieSync JwtAuthenticationStateProvider SameOriginProxyTokenRefresher ISessionCookieSync.SyncAsync AddCommonServerTokenStorage AddCommonMauiTokenStorage ServerTokenStorageService mmcaAuthSession.getToken NotifyUserAuthentication AcquireAccessTokenAsync DirectApiTokenRefresher WasmTokenStorageService"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#rationale","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Rationale","x":"- One application surface, three storage stories. Pages, services, and the HTTP pipeline talk to ITokenStorageService and AuthenticationStateProvider only; the head-specific…","i":"AuthenticationStateProvider ITokenStorageService"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#trade-offs","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Trade-offs","x":"- The browser heads depend on the same-origin UI host. SameOriginProxyTokenRefresher only works where the UI host serves the /auth/session/ endpoints; a browser head deployed…","i":"JwtAuthenticationStateProvider SameOriginProxyTokenRefresher MMCA.Common.UI.Maui MMCA.Common.UI.Web MMCA.Common.slnx MMCA.Common.UI AuthorizeView SecureStorage"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#related","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (the Blazor host's HttpOnly session cookie and the /auth/session/ endpoints the browser refresher proxies through), ADR-050 (the single rotating refresh token with reuse…","i":"DirectApiTokenRefresher MauiTokenStorageService"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-08-07","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"The MAUI half of ITokenStorageService is no longer app-local. The original Decision left the SecureStorage-backed implementation in each app because it depends on the MAUI…","i":"JwtAuthenticationStateProvider MauiTokenStorageService.cs AddCommonMauiTokenStorage DirectApiTokenRefresher MauiTokenStorageService SecureStorage.Default ITokenStorageService MMCA.Common.UI.Maui auth_refresh_token auth_access_token ClearTokensAsync MMCA.Common.slnx"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-08-14","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"SetTokensAsync closed a gap the original hoist left open. Point 3 above previously described the method as writing the refresh token first and dropping both tokens only when the…","i":"SetTokensAsync catch try"},{"u":"/docs/adr/052-background-job-execution.html","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records"},{"u":"/docs/adr/052-background-job-execution.html#status","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-24)."},{"u":"/docs/adr/052-background-job-execution.html#context","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Context","x":"Some requests trigger work that cannot run inside the request: an AI scoring pass over an event's sessions takes minutes and issues one paid API call per session, and a…","i":"RunScoringInBackgroundAsync IHostApplicationLifetime eventId"},{"u":"/docs/adr/052-background-job-execution.html#decision","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Decision","x":"In-process background work runs as a bounded queue plus a single-reader hosted drain. Nothing starts an untracked Task from a request. - A bounded Channel per job kind,…","i":"BoundedChannelFullMode.DropOldest LiveChannelPublishProcessor LiveChannelPublishQueue SessionScoringProcessor sp.GetRequiredService IServiceScopeFactory SessionScoringQueue BackgroundService TryAddSingleton stoppingToken ReadAllAsync SingleReader"},{"u":"/docs/adr/052-background-job-execution.html#rationale","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Rationale","x":"- The host lifetime is the point. A BackgroundService is the only in-process shape the host can cancel and await. Everything else in the decision follows from wanting that. - The…","i":"BackgroundService TryEnqueue"},{"u":"/docs/adr/052-background-job-execution.html#trade-offs","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Trade-offs","x":"- In-process only. The queue does not survive a restart and does not span replicas. Accepted because every current job is either ephemeral (a lost broadcast is a missed UI…","i":"DropOldest Wait"},{"u":"/docs/adr/052-background-job-execution.html#related","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox, for work that must be durable and cross-process, and the post-commit deferral this relies on), ADR-008 (the broker, for cross-service work), ADR-014 (the…"},{"u":"/docs/adr/053-dual-registry-package-publishing.html","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#status","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Amended (2026-07-25) to put the pre-decision Context statements in the past tense, to record the MMCA. ID prefix reservation as then-pending, to scope the…","i":"Directory.Build.props MMCA"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#context","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Context","x":"The fifteen MMCA.Common. packages have shipped to GitHub Packages since the first release. That was the right default while the framework had exactly one consumer group (this…","i":"MMCA.Common.API nuget.config local.props MMCA.Common totalHits package dotnet add"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#decision","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Decision","x":"Every release publishes to both registries, from the same tag, in the same workflow run. - release.yml keeps its existing dotnet nuget push to…","i":"github.repository_owner Directory.Build.props PackageProjectUrl PackageReadmeFile Description MMCA.Common PackageIcon PackageTags permissions release.yml README.md ivanball"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#rationale","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Rationale","x":"- The install line has to be true. Documentation that cannot be followed is worse than no documentation, because the reader concludes the project is broken rather than that the…","i":"MMCA"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#trade-offs","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A published version can never be withdrawn. nuget.org allows unlisting, not deletion. A bad release is now permanent public history, which raises the stakes on the release…","i":"release.yml ivanball"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#related","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Related","x":"ADR-016 (lockstep versioning: every package ships at one version, so both registries receive the same fifteen ids per release), ADR-038 (supply-chain provenance: the SBOM hard…"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#status","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Amended (2026-07-28): Store's reconciliation sweep now derives from PeriodicBackgroundService, so the shared-loop and adoption paragraphs are rewritten and…","i":"PeriodicBackgroundService SafeDomainEventHandler TDomainEvent IUnitOfWork maxReplicas"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#context","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Context","x":"Checkout spans a boundary no transaction covers. CheckOutHandler commits the order insert, the cart transition and the atomic conditional stock decrements in one local…","i":"PaymentInitiated CheckOutHandler"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#decision","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Decision","x":"Multi-step workflows are choreographed sagas: each step raises a domain event, and the follow-up or compensating action lives in its own handler. A periodic reconciliation sweep…","i":"OrderPaymentFailedSagaHandler DbUpdateConcurrencyException PaymentReconciliationService OperationCanceledException OrderCancelledSagaHandler PeriodicBackgroundService Order.InventoryRestored SafeDomainEventHandler MarkInventoryRestored IServiceScopeFactory IDomainEventHandler MarkAsPaymentFailed"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#rationale","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Rationale","x":"- No two-phase commit is available, and none is wanted. Transactions are per data source and best-effort sequential (ADR-006), and an external payment provider cannot enlist in a…","i":"Order.InventoryRestored Order.Status SaveChanges Result catch"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#trade-offs","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Trade-offs","x":"- Inconsistency is bounded, not eliminated. Between the cancellation commit and the compensation commit, stock is held against a cancelled order. Between a dropped webhook and…","i":"PaymentInitiated RestoreInventory InventoryItem maxReplicas"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#related","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox delivery and retry this leans on for compensation redelivery; this record says what the redelivered handler must do), ADR-006 (which accepts \"no…","i":"RowVersion Result"},{"u":"/docs/adr/055-repository-and-specification-contract.html","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/055-repository-and-specification-contract.html#status","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Revised 2026-08-01 (qualified the \"referenced nowhere\" claim about IEntityReader / IEntityQuerier: an ADC doc comment now names IEntityQuerier, though no…","i":"DependencyInjection.cs DependencyInjection EFReadRepository.cs IEntityQueryService SessionsController EFReadRepository IEntityQuerier IRepository.cs IEntityReader stage.ps1"},{"u":"/docs/adr/055-repository-and-specification-contract.html#context","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Context","x":"Every read an application handler performs has to come from somewhere, and the shape of that contract decides whether the module can still be lifted into its own service later…","i":"TIdentifierType IQueryable DbSet"},{"u":"/docs/adr/055-repository-and-specification-contract.html#decision","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Decision","x":"Data access is repository plus specification: interface-segregated read interfaces for the operations, expression-tree specifications for the predicates, and a build-failing…","i":"CrossSourceSpecification.BuildAsync OrdersByCustomerSpecification PublishedEventSpecification IEntityReader.GetByIdAsync TableNoTrackingSingleQuery TableNoTrackingSplitQuery OwnedByUserSpecification EntityQueryService.cs GetAllForLookupAsync IEntityQueryService InlineSpecification EntityQueryService"},{"u":"/docs/adr/055-repository-and-specification-contract.html#rationale","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A narrow interface is the enforcement, not a style preference. A handler that asks for IEntityReader cannot reach TableNoTracking, because the member is not on the interface.…","i":"GetProjectedAsync TableNoTracking IEntityReader IsSatisfiedBy AllowedFiles GetByIdAsync CountAsync IQueryable Criteria"},{"u":"/docs/adr/055-repository-and-specification-contract.html#trade-offs","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- The ISP split is guidance, not yet a wired dependency. Because the only accessors return the composites (IUnitOfWork.cs:19, :29), depending on IEntityReader today means…","i":"Expression.Invoke ISpecification IEntityReader Criteria"},{"u":"/docs/adr/055-repository-and-specification-contract.html#related","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-007 and ADR-008 (the extraction promise the queryable ban exists to protect), ADR-015 (the fitness-function machinery that runs this rule and its per-repo maps), ADR-014 (the…","i":"SpecificationsDoNotNavigateToOtherEntities TIdentifierType"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#status","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-14: re-anchored the host, base-class and AppHost citations to their current lines; scoped the \"only @rendermode attributes\" enumeration to…","i":"InteractiveServer rendermode MudTable"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#context","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Context","x":"Both web applications are Blazor Web Apps: a static server-rendered (SSR) prerender pass produces the first HTML, then an interactive runtime takes over, either a Blazor Server…","i":"InteractiveAuto App.razor Authorize"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#decision","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Decision","x":"Run one render mode for the entire routable component tree, chosen at the application root, default InteractiveAuto, with prerendering left on and the resulting double fetch…","i":"AddInteractiveWebAssemblyComponents AddInteractiveWebAssemblyRenderMode AddInteractiveServerComponents AddInteractiveServerRenderMode RendererInfo.IsInteractive RenderMode.InteractiveAuto PersistentComponentState PrerenderFetchTimeoutMs InteractiveWebAssembly DataGridListPageBase OnParametersSetAsync RegisterOnPersisting"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#rationale","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Rationale","x":"- InteractiveAuto gets both halves without asking page authors to choose. The first visit gets the Server circuit's immediate interactivity while the WASM bundle downloads in the…","i":"InteractiveServer InteractiveAuto CatalogBrowse Authorize"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#trade-offs","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Trade-offs","x":"- Everything shared has to run in both runtimes. The WASM-compatibility layer rule (MMCA.Common.LayerEnforcement.targets:75-88) forbids the shared UI package from touching…","i":"RendererInfo.IsInteractive AddAdditionalAssemblies DataGridListPageBase MMCA.Common.UI.Web OnAfterRenderAsync InteractiveServer InteractiveAuto CatalogBrowse AddUIShared Program.cs Routes"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#related","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (reads the HttpOnly session cookie during the SSR prerender pass this decision keeps enabled), ADR-027 (flows one culture through the SSR to Server to WASM sequence this…","i":"InteractiveAuto"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#status","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-01: the diff now fails closed in both repos (the true is gone and MMCA.Store's build-and-test checkout sets fetch-depth: 0), so the…","i":"true"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#context","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Context","x":"ADR-030 decides who applies a migration: every service host runs DatabaseInitStrategy = Migrate and self-applies its pending EF Core migrations at startup as the sole migrator,…","i":"DatabaseInitStrategy containerapp DropColumn migrations adee5058 revision Migrate dotnet sqlcmd copy"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#decision","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Decision","x":"Schema changes follow expand/contract, and a CI step enforces the contract half. - Expand now, contract later, as a written rule. Adding nullable columns, new tables and new…","i":"OutboxMessages InboxMessages pull_request CreateIndex DropColumn Migrations DropIndex DropTable IsDeleted base_ref release added"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#rationale","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Rollback is one-way for schema, so the check belongs where the drop is still cheap. The only moment a destructive migration can be reconsidered for free is the PR that adds it;…","i":"Down"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#trade-offs","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- Three operations, not a model of compatibility. AlterColumn narrowing a type or flipping a column to NOT NULL, DropForeignKey, DropPrimaryKey, DropSchema, RenameColumn and a…","i":"migrationBuilder.Sql DropForeignKey DropPrimaryKey RenameColumn AlterColumn DropSchema diff main true with git"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#related","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-030 (decides that each service self-applies its migrations at startup, which is precisely why a rolled-back revision meets the new schema; this ADR constrains what those…"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#status","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28; revised 2026-08-14)."},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#context","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Context","x":"ADR-015 turned the architecture invariants into build-gating tests, and drew its own boundary explicitly: the fitness suite asserts \"structure / registration, not runtime…"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#decision","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Decision","x":"Ship the runtime conformance suites in the MMCA.Common.Testing package as abstract behavioral bases that each consuming host subclasses, and run every one of them against a host…","i":"ServiceInfoVersioningContractTestsBase SqlServerIntegrationTestFixtureBase MMCA.Common.Testing.Architecture ProductionHostApplicationFactory DecoratorPipelineOrderTestsBase ProblemDetailsContractTestsBase AssertProblemDetailsShapeAsync GracefulShutdownTestsBase AddApplicationDecorators ChangePreferencesCommand OpenApiContractTestsBase SecurityHeadersTestsBase"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#rationale","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Rationale","x":"- Runtime conformance is the half ADR-015 excluded. Structural rules answer \"is the code shaped correctly\"; these suites answer \"does the composed host behave correctly\". A host…","i":"Development Production"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#trade-offs","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per host, exactly like ADR-015. The framework ships the suites; a host gets the gate only once someone writes the subclass. That is the same audit-the-inventory caveat,…","i":"CorePublicResources MinimumPathCount status title"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#related","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the structural / registration fitness layer this complements; its stated non-goal, \"not runtime behavior\", is exactly this ADR's scope, and the two tiers ship as two…","i":"DecoratorPipelineOrderTestsBase ProblemDetailsContractTestsBase"},{"u":"/docs/adr/059-module-contract-and-composition.html","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records"},{"u":"/docs/adr/059-module-contract-and-composition.html#status","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28; revised 2026-08-14)."},{"u":"/docs/adr/059-module-contract-and-composition.html#context","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Context","x":"The framework's headline claim is that an application is built as a modular monolith and later extracted into services without rewriting business logic. ADR-008 states the…","i":"ModuleLoader"},{"u":"/docs/adr/059-module-contract-and-composition.html#decision","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Decision","x":"Make IModule the single composition contract, discover implementations by reflection, register them in topological dependency order, and represent a disabled module by stub…","i":"DisabledSessionBookmarkValidationService AppDomain.CurrentDomain.GetAssemblies DisabledEventLiveValidationService ModuleControllerFeatureProvider DisabledUserSalesExportService DisabledProductVariantService SalesUserDataExportSection ValidateRemoteDependencies InvalidOperationException Activator.CreateInstance AddUserDataExportSection DisabledCustomerService"},{"u":"/docs/adr/059-module-contract-and-composition.html#rationale","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Rationale","x":"- Reflection discovery keeps hosts out of the module registry business. A host calls one method and gets whatever modules its assembly graph contains; adding a module is a…","i":"RequiresDependencies RemoteDependencies appsettings.json Dependencies Modules true"},{"u":"/docs/adr/059-module-contract-and-composition.html#trade-offs","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Trade-offs","x":"- The AppDomain scan is the fragile default and the one everybody uses. The loader's own documentation warns that the AppDomain scan sees only assemblies already loaded, so a…","i":"ModuleConformanceTestsBase ValidateRemoteDependencies Activator.CreateInstance IBookmarkCountService RegisterDisabledStubs RequiresDependencies AddTypedGrpcClient Dependencies ModuleName Complete Register Enabled"},{"u":"/docs/adr/059-module-contract-and-composition.html#related","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (the extraction topology that consumes this model: \"a service is the monolith with one module enabled\" is a statement about ModuleLoader plus the Disabled stubs, cited…","i":"AddApplicationDecorators ModuleLoader"},{"u":"/docs/adr/060-performance-regression-gate.html","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records"},{"u":"/docs/adr/060-performance-regression-gate.html#status","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-01 (corrected the count in Trade-offs: the single ratio floor names two of the eight benchmarks, so six, not seven, are gated on…","i":"ci.yml"},{"u":"/docs/adr/060-performance-regression-gate.html#context","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Context","x":"Rubric section 12 asks for hot-path efficiency that is measured, not assumed (Website/docs-src/governance/ArchitectureEvaluationCriteria.md:355). MMCA.Common has a…","i":"IsSatisfiedBy"},{"u":"/docs/adr/060-performance-regression-gate.html#decision","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Decision","x":"Measure the hot-path suite on every code PR and verify the results against a committed baseline that carries two rule kinds: absolute allocation ceilings where the measurement is…","i":"ApplyFilters_ThreeMixedOperators IsSatisfiedBy_RecompileEachCall IsSatisfiedBy_CachedCompile allocationCeilingsBytes MMCA.Common.slnx PackageReference System.Text.Json BenchmarkDotNet MemoryDiagnoser fastBenchmark slowBenchmark Performance"},{"u":"/docs/adr/060-performance-regression-gate.html#rationale","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Rationale","x":"- A ratio is a property of the code; an absolute nanosecond count is a property of the runner. Both benchmarks in a floor run in the same process, on the same machine, in the…","i":"MemoryDiagnoser Specification TEntity TId"},{"u":"/docs/adr/060-performance-regression-gate.html#trade-offs","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The Short job cannot see small latency regressions. Three warmup and three iterations (ci.yml:360) give wide confidence intervals: enough for a 1000x floor and for counting…","i":"ApplyFilters release.yml changes main push"},{"u":"/docs/adr/060-performance-regression-gate.html#related","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (structural fitness functions, which explicitly stop at structure and registration; this is their runtime-cost counterpart), ADR-038 (the other build-gating control set,…"},{"u":"/docs/adr/061-runtime-secret-management.html","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records"},{"u":"/docs/adr/061-runtime-secret-management.html#status","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01; citations re-anchored 2026-08-14)."},{"u":"/docs/adr/061-runtime-secret-management.html#context","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Context","x":"A Container App can hold a credential two ways: as a literal value in the app's own secrets collection, or as a reference to a Key Vault secret that the platform resolves at…","i":"DefaultAzureCredential secrets"},{"u":"/docs/adr/061-runtime-secret-management.html#decision","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Decision","x":"Every production secret lives in Azure Key Vault and reaches the app as a keyVaultUrl secret reference resolved by a user-assigned managed identity. SQL authentication is staged…","i":"azureADOnlyAuthentication USE_MANAGED_IDENTITY_SQL useManagedIdentitySql hasSmtpPassword MMCA.Templates keyVaultUrl claude.yml Directory hasStripe secretRef existing Identity"},{"u":"/docs/adr/061-runtime-secret-management.html#rationale","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Rationale","x":"- A reference has one home; a literal has as many homes as it has consumers. Three vault secrets in each repo are referenced by more than one app: Redis and the broker by all…"},{"u":"/docs/adr/061-runtime-secret-management.html#trade-offs","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Trade-offs","x":"- One identity means vault-wide read for every app that carries it. A Key Vault Secrets User grant is scoped to the vault, so any app running as the shared identity can read…","i":"main.bicep EXTERNAL listKeys PROVIDER secrets CREATE secure unused FROM USER"},{"u":"/docs/adr/061-runtime-secret-management.html#related","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Related","x":"ADR-037 (directs a consumer to keep the field-encryption key in Key Vault but decides no delivery mechanism, and nothing wires that converter today, so no such secret exists in…"},{"u":"/docs/adr/062-slo-alerting-as-code.html","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records"},{"u":"/docs/adr/062-slo-alerting-as-code.html#status","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01)."},{"u":"/docs/adr/062-slo-alerting-as-code.html#context","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Context","x":"ADR-041 standardized what the fleet emits: RED histograms off the CQRS pipeline, an outbox dead-letter counter, correlation ids, exporters, and the cost knobs that keep ingestion…"},{"u":"/docs/adr/062-slo-alerting-as-code.html#decision","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Decision","x":"Declare each consumer's SLO alerts as data in its Bicep template, materialize them as Log Analytics scheduled query rules, and make the alert-to-runbook pairing a build gate…","i":"EveryRunbookAlertSection_MapsToAProvisionedAlert SloAlertSpecs_AreDiscovered_GateIsNotVacuous ObservabilityConventionTestsBaseTests ObservabilityConventionTestsBase legacySloMetricAlertSpecs infra.OPERATIONS.md metricMeasureColumn alertEmailAddress MinimumAlertSpecs infra.main.bicep ResourceAssembly loadTextContent"},{"u":"/docs/adr/062-slo-alerting-as-code.html#rationale","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Alerts as data, not as portal state. One array is reviewable in a PR, diffable across environments, and re-deployable; the rules, the workbook, and the notification channel are…","i":"enabled false"},{"u":"/docs/adr/062-slo-alerting-as-code.html#trade-offs","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- It is a text gate over IaC, not a check against deployed state. The base matches literal anchors and regexes in the template and headings in markdown. It proves the two files…","i":"sloAlertSpecs metricAlerts prefix key"},{"u":"/docs/adr/062-slo-alerting-as-code.html#related","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-041 (the telemetry this alerts on top of: it defines emission, instrumentation and cost knobs and stops before thresholds, severities and runbooks), ADR-009 (recovery…"},{"u":"/docs/adr/063-accessibility-conformance-gate.html","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#status","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01). Revised 2026-08-14: refreshed the E2ETestBase helper line anchors (explanatory comments were added above ScanGridAsync), the two consumer suite scan counts…","i":"ScanGridAsync E2ETestBase"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#context","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Context","x":"Accessibility was documented before it was enforced. The narrative guide (common-ACCESSIBILITY.md, rubric section 21) named WCAG 2.1 AA as the target for the shared…","i":"MMCA.Common.UI"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#decision","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Decision","x":"Ship WCAG 2.1 AA as a named, versioned test contract in MMCA.Common.Testing.E2E, assert it from the package's own workflow bases, and wire it as a required merge check and a…","i":"AxeOptions.Wcag21AaExceptMudPagerCombobox AssertNoAccessibilityViolationsAsync AccessibilityViolationException MMCA.Common.Testing.E2E ProfileManagementTests AxeOptions.Wcag21Aa PrimaryContrastText WarningContrastText GalleryAxeTestBase ErrorContrastText AxeRunOptions MudTablePager"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#rationale","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Rationale","x":"- A named constant is the contract. Putting the rule set in a shipped, referenced symbol rather than in each repo's test setup means \"what WCAG 2.1 AA means here\" has exactly one…","i":"ProfileManagementTestsBase UserRegistrationTestsBase UserLoginTestsBase"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#trade-offs","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- Best-practice rules are out of scope, deliberately. Findings axe would classify as best practice (and anything WCAG AAA) are not measured at all, so the gate can be green on a…","i":"Wcag21AaExceptMudPagerCombobox AccessibilityTests ScanGridAsync skipped success deploy"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#related","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (architecture fitness functions: the structural tier this parallels at the browser tier, and the same invariant-over-discipline posture), ADR-058 (runtime conformance…"},{"u":"/docs/adr/064-deploy-recency-gates.html","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records"},{"u":"/docs/adr/064-deploy-recency-gates.html#status","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01). Revised 2026-08-07: the MMCA.Helpdesk workflow inventory below was corrected (it also carries release-templates.yml, and its ci.yml runs two jobs, not…","i":"ci.yml"},{"u":"/docs/adr/064-deploy-recency-gates.html#context","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Context","x":"A production rollout in both deployed apps waits on a list of jobs in deploy.needs (MMCA.ADC/.github/workflows/deploy.yml:866, MMCA.Store/.github/workflows/deploy.yml:862). Most…","i":"deploy.needs"},{"u":"/docs/adr/064-deploy-recency-gates.html#decision","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Decision","x":"A production deploy is blocked not only on green tests but on proof of recency for out-of-band verification: three gates assert that a real drill, a real load run and a real…","i":"skip_freshness_gates skip_justification github.event_name workflow_dispatch FRESHNESS_DAYS workflow_runs deploy.needs release.yml foundation updated_at cancelled contents"},{"u":"/docs/adr/064-deploy-recency-gates.html#rationale","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Rationale","x":"- A proof with no expiry date is documentation, not a control. ADR-009 already required the drill to be recorded, and recording it was the honest half of the problem; a record…"},{"u":"/docs/adr/064-deploy-recency-gates.html#trade-offs","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Trade-offs","x":"- An unrelated stale proof blocks an unrelated deploy. A one-line hotfix does not ship when the monthly k6 cron did not fire, and the failure surfaces after merge: the gate job…","i":"deploy"},{"u":"/docs/adr/064-deploy-recency-gates.html#related","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Related","x":"ADR-009 (states the recovery objectives and requires that a restore be drilled and recorded; this record decides that a deploy is blocked on how recently that drill, and the…"},{"u":"/docs/adr/065-scaffolding-templates.html","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","x":"Status: Accepted (2026-08-02). Revised 2026-08-07: the staged analyzer delta relaxes three rules rather than one; mmca-module prints seven wire-ups rather than five, and a…"},{"u":"/docs/adr/065-scaffolding-templates.html#context","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Context","x":"Build by hand is accurate and complete, and phases 1 through 6 of it are transcription work (common-BUILD-BY-HAND.md:96 through :1049). Its own instruction for the load-bearing…","i":"AddApplicationDecorators Directory.Packages.props Directory.Build.targets Directory.Build.props launchSettings.json IArchitectureMap MMCA.Templates editorconfig nuget.config global.json install WaitFor"},{"u":"/docs/adr/065-scaffolding-templates.html#decision","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Decision","x":"Ship a dotnet new template pack, MMCA.Templates, containing four templates: The template content is the MMCA.Helpdesk reference application itself, staged at pack time.…","i":"IntegrationEventContractTestsBase IntegrationEventContractTests SQLServerMigrationsAssembly WithSQLServerDataSource TreatWarningsAsErrors AddErrorResources appsettings.json IArchitectureMap Contoso.Support RequesterUserId MMCA.Templates MMCA.Helpdesk"},{"u":"/docs/adr/065-scaffolding-templates.html#rationale","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Rationale","x":"Deriving from the seed rather than maintaining a template tree is the whole design. A hand-maintained copy of a 12-project solution drifts within one release, and drift in a…","i":"MMCA.Common.Templates MMCA.Templates MMCA.Helpdesk sourceName Helpdesk install Tickets dotnet Ticket new"},{"u":"/docs/adr/065-scaffolding-templates.html#trade-offs","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two documented one-time fixups in every generated app, above (one of them covering all three relaxed rules). The alternative to the SA1210 half of the delta was moving every…","i":"IntegrationEventContractTestsBase MMCA.Common copyOnly dotnet SA1210 using Fact new"},{"u":"/docs/adr/066-broker-transport-selection.html","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records"},{"u":"/docs/adr/066-broker-transport-selection.html#status","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-14 (source citations re-anchored; the ADC AppHost comment that used to say no WithBroker() was wired has been corrected in code, so the…","i":"WithBroker"},{"u":"/docs/adr/066-broker-transport-selection.html#context","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Context","x":"ADR-003 decides that integration events leave an aggregate through the outbox and are published by OutboxProcessor via IMessageBus, and it settles the dispatch question…","i":"OutboxProcessor IMessageBus"},{"u":"/docs/adr/066-broker-transport-selection.html#decision","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Decision","x":"Keep one IMessageBus abstraction with a three-value transport selector, choose the value at the deployment edge (never in application code), configure both broker transports…","i":"Bus.Factory.CreateUsingAzureServiceBus ResolveBrokerConnectionString MessageBus__ConnectionString ConnectionStrings__rabbitmq RootManageSharedAccessKey ConfigureBrokerTransport RetryMaxIntervalSeconds RetryMinIntervalSeconds builder.Configuration UseDelayedRedelivery UsingAzureServiceBus cfg.UseMessageRetry"},{"u":"/docs/adr/066-broker-transport-selection.html#rationale","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Rationale","x":"- The transport is a deployment fact, so it lives at the deployment edge. The only difference between a laptop and production is two environment variables set by the AppHost or…","i":"Listen Send"},{"u":"/docs/adr/066-broker-transport-selection.html#trade-offs","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two brokers means two behaviors to keep aligned. Configuration parity is enforced by one code path, but the products still differ (Service Bus supports delayed redelivery…","i":"MessageBus__Provider ConfigureEndpoints WithBroker Manage rabbit"},{"u":"/docs/adr/066-broker-transport-selection.html#related","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox that feeds IMessageBus; this ADR picks the transport underneath it), ADR-016 (the MassTransit v8 pin the emulator tier must work within, which is why the…","i":"IMessageBus Host"},{"u":"/docs/adr/067-ui-module-shell-composition.html","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records"},{"u":"/docs/adr/067-ui-module-shell-composition.html#status","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07)."},{"u":"/docs/adr/067-ui-module-shell-composition.html#context","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Context","x":"ADR-059 decided how a module plugs into the server: an IModule implementation is discovered by reflection, registered in topological order, and a host composes an application out…","i":"MMCA.Common.UI IModule Routes App"},{"u":"/docs/adr/067-ui-module-shell-composition.html#decision","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Decision","x":"Ship the application shell in the framework package and let each module plug into it by implementing IUIModule, resolved from DI as IEnumerable . - The contract is four members,…","i":"AdditionalAssemblies AppBarComponentTypes LayoutComponentTypes AuthorizeRouteView MapRazorComponents DynamicComponent UIModules.Select RedirectToLogin DeviceUIModule RequiredClaim TitleResource AddSingleton"},{"u":"/docs/adr/067-ui-module-shell-composition.html#rationale","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Rationale","x":"- One composition model across both tiers. A module already declares its server-side surface through IModule (ADR-059); declaring its UI surface through IUIModule means \"add a…","i":"AppBarComponentTypes LayoutComponentTypes AuthorizeView Components IUIModule IModule NavMenu"},{"u":"/docs/adr/067-ui-module-shell-composition.html#trade-offs","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Trade-offs","x":"- Assembly is required even when it carries no route. A host-only module that contributes only a layout component still has to return an assembly, which then joins…","i":"AddAdditionalAssemblies AdditionalAssemblies AuthorizeRouteView RequiredClaim MauiUIModule RequiredRole Program.cs IUIModule Assembly NavItems NavMenu page"},{"u":"/docs/adr/067-ui-module-shell-composition.html#related","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Related","x":"ADR-059 (the server-side IModule contract this mirrors in the presentation layer), ADR-056 (the render-mode strategy for the web heads, which decides how these components render…","i":"TitleResource IModule"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#status","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07)."},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#context","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Context","x":"A domain model has two kinds of small type: the identity of a thing, and a value the thing carries. ADR-048 recorded the identity half: identifiers stay primitives named through…","i":"decimal string"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#decision","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Decision","x":"Model a domain value that carries an invariant as an immutable record value object with a Result-returning factory; keep identifiers primitive (ADR-048). - One abstract record…","i":"PhoneNumberInvariants.EnsurePhoneNumberIsValid ArchitectureRules.DomainFactoriesReturnResult AddressInvariants.EnsureAddressLine1IsValid EmailInvariants.EnsureEmailIsValid NullablePhoneNumberValueConverter NullableEmailValueConverter EmailInvariants.MaxLength PhoneNumberValueConverter DataContractSerializer GetEqualityComponents DateTimeRange.Create ProductVariant.Price"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#rationale","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Rationale","x":"- The invariant belongs to the type, not to every caller. A string email can be validated in one handler and not the next; an Email cannot exist unvalidated, because the only…","i":"NullReferenceException Currency.None Money.Zero OwnsMoney record Result string Email Money"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#trade-offs","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Trade-offs","x":"- The pattern is not uniformly applied. Only three of the seven types have a companion Invariants class; the rest inline their checks. Only Money has a shipped owned-type helper,…","i":"InvalidOperationException DateTimeRange Currency.All PhoneNumber DateRange Money.Add operator Address OwnsOne Create Result string"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#related","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Related","x":"ADR-048 (the deliberate opposite call for identifiers: primitives behind aliases, wrapper structs rejected, because identifiers cross process boundaries constantly and carry no…","i":"Create Result"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#status","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Updated 2026-08-14: Store's adoption has landed and is live (its own dedicated storage account, gated on dataProtectionStorageReady), and the ADC call-site…","i":"dataProtectionStorageReady AddServiceDefaults"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#context","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Context","x":"ASP.NET Core's DataProtection default keeps the key ring in memory, per process. That is correct for a single-process host and wrong for a scaled-out one: every replica generates…","i":"DefaultAzureCredential maxReplicas"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#decision","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Decision","x":"Add one opt-in registration call, AddCommonDataProtection, that persists the key ring to a single Azure blob so every replica of a host shares one ring…","i":"Azure.Extensions.AspNetCore.DataProtection.Blobs KeyManagementOptions.XmlRepository System.Security.Cryptography.Xml AddCommonKeyVaultConfiguration DataProtection__BlobStorageUri DataProtection__KeyVaultKeyUri grantDataProtectionStorageRole PersistKeysToAzureBlobStorage ProtectKeysWithAzureKeyVault dataProtectionStorageReady AddCommonDataProtection IDataProtectionProvider"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#rationale","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Rationale","x":"- The key ring is the smallest thing that has to be shared. Sticky sessions would paper over the symptom while making a replica restart a mass sign-out, and a shared cache would…"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#trade-offs","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Trade-offs","x":"- The key ring is not encrypted at rest today. Gate 2 is implemented but configured nowhere, so the ring is protected by the container being private and the account grant being…","i":"AddCommonDataProtection AZURE_CLIENT_ID MMCA.ADC"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#related","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (the browser session cookies whose decryption this makes replica-independent, together with the antiforgery tokens the SSR pages mint), ADR-008 (the multi-host topology…","i":"DefaultAzureCredential"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#status","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-14 (source citations re-anchored; the consumer-repo facade claim narrowed to production code, with the controller-test exception recorded)."},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#context","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Context","x":"Every host in the workspace reads a dozen or more configuration sections: connection strings, SMTP, JWT key material, outbox tuning, message-bus provider, module enablement,…","i":"IOptions"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#decision","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Decision","x":"Bind every settings section through a validating chain that runs at startup, and expose a settings type through a read-only interface when it must be read above Infrastructure. -…","i":"IConnectionStringSettings IPushNotificationSettings ConnectionStringSettings PushNotificationSettings LoginProtectionSettings ValidateDataAnnotations DependencyInjection.cs LoginProtectionService CacheKeyPrefixOptions AddPushNotifications EntityControllerBase IApplicationSettings"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#rationale","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A boot failure is cheaper than a first-use failure. A host that will not start is caught by the deployment, by a local dotnet run, or by CI. A host that starts and fails on the…","i":"Microsoft.Extensions.Options ValidateDataAnnotations EntityControllerBase IApplicationSettings ApplicationSettings IValidatableObject RepositoryFactory ValidateOnStart JwtSettings IOptions dotnet init"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#trade-offs","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing enforces it. There is no architecture fitness test asserting that a new AddOptions call carries ValidateDataAnnotations().ValidateOnStart(). The uniformity above is…","i":"ValidateDataAnnotations IValidatableObject IValidateOptions IOptionsMonitor ValidateOnStart JwtSettings AddOptions IOptions Value"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#related","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-025 (startup warm-up and readiness gating: this contract decides what happens before a host reaches that machinery), ADR-031 (feature flags read from configuration, whose…"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#status","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-12). Amended 2026-08-13: the composition-time string trade-off below was resolved in v1.147.0 by a deferred-resolution overload; see the updated trade-off…"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#context","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Context","x":"ADC's badge check-in feature (ADR-072) needs two things that look like one thing: an attendee's device has to show a QR code, and an organizer's device has to read one. They are…","i":"AddDeviceCapabilityDefaults NSCameraUsageDescription MMCA.Common.UI System.Drawing AddUIShared CAMERA"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#decision","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Decision","x":"Split the feature by what it actually depends on: QR display ships as a shared component, barcode scanning ships as an ADR-042 capability whose native half is opt-in per head. -…","i":"AddDeviceCapabilityDefaults DeviceInfo.Current.Platform MauiBarcodeScannerService NullBarcodeScannerService UseMauiDeviceCapabilities Permissions.RequestAsync ZXing.Net.Maui.Controls IBarcodeScannerService QrErrorCorrectionLevel ScanOnMainThreadAsync TaskCompletionSource MMCA.Common.UI.Maui"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#rationale","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Rationale","x":"- Rendering a QR is not a device concern, so making it one would have been ceremony. As a capability it would have needed an interface, a null fallback and a native override for…","i":"UseMauiDeviceCapabilities MMCA.Common.UI PngByteQRCode IsSupported MauiProgram null try"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#trade-offs","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Trade-offs","x":"- The scan page's strings were resolved at composition, not per call (resolved in v1.147.0). As shipped in v1.145.0, cancelText and cameraDescription were captured into the…","i":"UseCommonBarcodeScanner cameraDescription OnParametersSet MMCA.Common.UI IsSupported QrCodeImage cancelText QRCoder string catch false Func"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#related","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Related","x":"ADR-042 (the capability pattern this extends: contract in MMCA.Common.UI, native implementation in MMCA.Common.UI.Maui, override after AddUIShared), ADR-072 (the ADC feature that…","i":"MMCA.Common.UI.Maui MMCA.Common.UI AddUIShared"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#status","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Amended (2026-08-14): ADC shipped two attendee-self-recorded scan surfaces (sponsor booth visits and room self check-in), a third CheckInScope, a sixth…","i":"CheckInScope"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#context","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Context","x":"ADC wanted two conference-day capabilities that turn out to be one mechanism. Organizers want to know who actually attended which session, which the schedule cannot tell them: a…"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#decision","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Decision","x":"AttendeeBadge (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/Badges/AttendeeBadge.cs:17-24) is one row per user holding a single Guid Credential, minted on first…","i":"CheckInInvariants.EnsureTargetMatchesScope CheckInSettings.RoomCheckInGraceMinutes EngagementPermissions.CheckInManage CheckInProcessor.FindExistingAsync EngagementFeatures.SponsorVisits EngagementPointsEntryExportItem PointsActivityType.SponsorVisit EngagementFeatures.RoomCheckIn user_engagement_export.proto EngagementCheckInExportItem Engagement.SponsorVisits leaderboard_display_name"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#rationale","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Rationale","x":"- An opaque credential makes the server the only interpreter. A JWT or HMAC badge would verify offline, but the scanning device is online by necessity (it has to write a check-in…","i":"SessionCheckIn EventCheckIn Regenerate"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#trade-offs","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Trade-offs","x":"- The badge credential is a bearer value. Anyone who photographs an attendee's screen can be checked in as that attendee. The mitigations are that a badge scan is organizer-side,…","i":"DuplicateKeyDetection.IsDuplicateKey SetLeaderboardParticipationHandler GetLeaderboardHandler AttendeeCheckedIn Engagement.Points SessionFeedback SessionCheckIn activity_type IFeatureGated PointsAwarder QuestionAsked FeatureGate"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#related","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Related","x":"ADR-071 (the framework halves this consumes: the QR component on /my-badge and the scanner capability behind /check-in), ADR-003 (the outbox path AttendeeCheckedIn and the two…","i":"AttendeeCheckedIn EraseDisplayName"},{"u":"/docs/adr/073-multi-tenancy-model.html","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records"},{"u":"/docs/adr/073-multi-tenancy-model.html#status","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). The implementation lands in the MMCA.Common enterprise capability wave release, alongside the scheduler, audit trail, DSAR export, and CSV export work. It…","i":"ApplicationDbContext AddMultiTenancy configuration"},{"u":"/docs/adr/073-multi-tenancy-model.html#context","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common already partitions data along two axes and neither of them is a tenant. ADR-006 partitions by source name (every entity resolves to a DataSourceKey(Engine, Name),…","i":"ApplySoftDeleteFilters SoftDeleteFilterName modelBuilder.Entity OnModelCreating HasQueryFilter DataSourceKey OnConfiguring TenantId clrType Engine filter Where"},{"u":"/docs/adr/073-multi-tenancy-model.html#decision","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Decision","x":"Ship shared-schema tenancy as a second named query filter, with per-tenant database routing as a configuration override on the same source key, both opt-in and both inert until a…","i":"IPhysicalDbContextFactory.Create CosmosDbContext.OnModelCreating TenantSaveChangesInterceptor UseCommonMiddlewarePipeline TenantResolutionMiddleware CrossTenantWriteException DesignTimeDbContextHelper SoftDeletedUserMiddleware ITenantContext.SetTenant CachingCommandDecorator CorrelationIdMiddleware InitializeDatabaseAsync"},{"u":"/docs/adr/073-multi-tenancy-model.html#rationale","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Rationale","x":"- A query filter is the only place the rule cannot be forgotten. Per-handler Where clauses are correct until the tenth handler, and the tenth handler is a data leak rather than a…","i":"IgnoreQueryFilters DataSourceKey ICacheService RequireTenant Where"},{"u":"/docs/adr/073-multi-tenancy-model.html#trade-offs","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Reads are on discipline where writes are on an invariant. A consumer calling EF's own parameterless IgnoreQueryFilters() on a raw Table surface drops the tenant filter along…","i":"ApplicationLayer_DoesNotUseRawQueryableSurfaces DefaultSqlServerDbContextFactory TenantSaveChangesInterceptor IgnoreQueryFilters ICacheService ITenantEntity tenant_id Table"},{"u":"/docs/adr/073-multi-tenancy-model.html#related","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (the source-name axis this composes with: an override re-points a DataSourceKey without changing it, and the per-source outbox this record drains once per tenant),…","i":"IgnoreQueryFilters CosmosDbContext TenancySettings DataSourceKey tenantId TenantId string"},{"u":"/docs/adr/074-recurring-job-scheduler.html","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records"},{"u":"/docs/adr/074-recurring-job-scheduler.html#status","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13; revised 2026-08-14). The implementation lands in the MMCA.Common \"enterprise capability wave\" release and is opt-in: a host calls…","i":"AddScheduledJobs configuration"},{"u":"/docs/adr/074-recurring-job-scheduler.html#context","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Context","x":"The framework had two kinds of background work and neither of them is a schedule. OutboxProcessor…","i":"PeriodicBackgroundService OutboxProcessor"},{"u":"/docs/adr/074-recurring-job-scheduler.html#decision","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Decision","x":"A persistent job store plus a single-runner claim lease, reusing the exact idiom the outbox proved. The outbox claims a batch with an ExecuteUpdateAsync that sets LockedUntil and…","i":"DesignTimeDbContextOptions.EnableScheduler DesignTimeDbContextHelper PeriodicBackgroundService Directory.Packages.props EnsurePermissionRegistry ValidateDataAnnotations PollingIntervalSeconds AuditTrailCleanupJob DispatchLagHistogram IServiceScopeFactory ClaimEligibleAsync ConfigureScheduler"},{"u":"/docs/adr/074-recurring-job-scheduler.html#rationale","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Rationale","x":"- The lease is already proven under production load. Multi-replica correctness for recurring work is the hard part, and it was solved once for the outbox: an atomic claim update,…","i":"AddScheduledJobs IUnitOfWork LastRunOn NextRunOn DateTime"},{"u":"/docs/adr/074-recurring-job-scheduler.html#trade-offs","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A polling loop is not a real-time scheduler. Worst-case start lag is one polling interval, 30 seconds at the default, so sub-minute precision is not on offer. A job that must…","i":"LeaseSeconds"},{"u":"/docs/adr/074-recurring-job-scheduler.html#related","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox whose claim-lease idiom and smart wait this reuses verbatim, and whose at-least-once posture it inherits along with the idempotency obligation on job bodies),…","i":"SchedulerSettings SchedulerMetrics OutboxMetrics"},{"u":"/docs/adr/075-audit-trail.html","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records"},{"u":"/docs/adr/075-audit-trail.html#status","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13; corrected 2026-08-14: the adoption sweep and the ApplicationDbContext line citations). The implementation lands in the MMCA.Common \"enterprise capability…","i":"ApplicationDbContext IAuditedEntity AddAuditTrail configuration"},{"u":"/docs/adr/075-audit-trail.html#context","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Context","x":"The framework already answers \"who touched this row last\". Every AuditableBaseEntity carries CreatedOn/By and LastModifiedOn/By, stamped by AuditSaveChangesInterceptor on the way…","i":"AuditSaveChangesInterceptor AuditableBaseEntity SaveChangesAsync LastModifiedBy"},{"u":"/docs/adr/075-audit-trail.html#decision","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Decision","x":"AuditTrailSaveChangesInterceptor (Infrastructure Persistence/AuditTrail/) joins the interceptors ApplicationDbContext.OnConfiguring already passes to…","i":"ApplicationDbContext.OnModelCreating ApplicationDbContext.OnConfiguring DomainEventSaveChangesInterceptor AuditTrailSaveChangesInterceptor optionsBuilder.AddInterceptors TenantSaveChangesInterceptor AuditSaveChangesInterceptor DesignTimeDbContextHelper PeriodicBackgroundService PiiRedactor.RedactedToken DiscardAbandonedCapture DependencyInjection.cs"},{"u":"/docs/adr/075-audit-trail.html#rationale","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Rationale","x":"- IAuditableEntity is a statement about a business row, and an audit row is not one. The interface means \"this row stamps who created and last modified it and participates in…","i":"IAuditableEntity LastModifiedBy IScheduledJob OutboxMessage TenantId Pii"},{"u":"/docs/adr/075-audit-trail.html#trade-offs","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Write amplification is real and it is on the caller's latency path. An entity with twenty changed properties writes twenty rows inside the caller's transaction, so an audited…","i":"IAuditTrailReader AddAuditTrail RetentionDays Pii"},{"u":"/docs/adr/075-audit-trail.html#related","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the same-transaction write this copies wholesale, including the retry-discard and the Add-only mutation rule), ADR-005 (soft-delete, [Pii] and erasure: why the trail…","i":"AuditTrailSettings RowVersion TenantId Add Pii"},{"u":"/docs/adr/076-data-subject-export.html","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/076-data-subject-export.html#status","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Revised 2026-08-14 (the API-surface section corrected to the shipped mechanism, an abstract DataExportControllerBase a subclass mounts, not an…","i":"ExportUserDataHandlerBase DataExportControllerBase IUserDataExportSection"},{"u":"/docs/adr/076-data-subject-export.html#context","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Context","x":"A data-subject access request is a legal obligation with a clock on it: the person asks for a copy of the personal data held about them, and the operator has a deadline to hand…","i":"DeleteUserHandlerBase UserOwnershipRule IAnonymizable"},{"u":"/docs/adr/076-data-subject-export.html#decision","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Decision","x":"The framework takes the part that is the same in both apps; the app keeps the part that is not. A consumer's export handler becomes a subclass that supplies a role test and a set…","i":"AuthorizationPolicies.RequireAuthenticated EntitiesWithPiiImplementAnonymizable UserOwnershipRule.CheckOwnership AuditableAggregateRootEntity IUserEngagementExportService AddNotificationControllers PrivacyFeatures.DataExport AuthenticationServiceBase ExportUserDataHandlerBase DataExportControllerBase PiiEntitiesAreExportable IUserDataExportSection"},{"u":"/docs/adr/076-data-subject-export.html#rationale","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Rationale","x":"- The two halves of a handler have different owners. The ownership gate, the aggregate load, the fan-out, the per-section catch and the envelope are the same decisions in both…","i":"IUserEngagementExportService IUserSalesExportService ExportUserDataQuery UserOwnershipRule User"},{"u":"/docs/adr/076-data-subject-export.html#trade-offs","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Best-effort degradation can return a quietly incomplete package. Available = false is the only signal, and nothing forces a caller, a UI, or the subject to read it. A section…","i":"DataExportControllerBase UserDataExportDTO UserOwnershipRule CurrentUserId FeatureGate Available false"},{"u":"/docs/adr/076-data-subject-export.html#related","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (the erasure half of the same privacy obligation, whose IAnonymizable opt-in and [Pii] guard are this contract's mirror: one erases what the other copies), ADR-033 (the…","i":"PiiEntitiesAreExportable UserOwnershipRule IAnonymizable FeatureGate Result Pii"},{"u":"/docs/adr/077-hybridcache-substrate.html","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","i":"ICacheService"},{"u":"/docs/adr/077-hybridcache-substrate.html#status","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Amends ADR-026: Tier 1's substrate gains a third implementation beside MemoryCacheService and DistributedCacheService. It is opt-in through…","i":"DistributedCacheService AddCommonHybridCache MemoryCacheService"},{"u":"/docs/adr/077-hybridcache-substrate.html#context","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Context","x":"ADR-026 settled Tier 1 as one abstraction (ICacheService) over two implementations chosen at startup: in-process memory when no real IDistributedCache is present, Redis…","i":"Microsoft.Extensions.Caching.Hybrid ICacheService.IncrementAsync StackExchangeRedisCache IDistributedCache ICacheService HybridCache WRONGTYPE Result INCR"},{"u":"/docs/adr/077-hybridcache-substrate.html#decision","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Decision","x":"Ship HybridCacheService as a third ICacheService implementation, opt-in per host, under a disjoint keyspace. This is the structural rule the design is built around, and…","i":"HybridCacheEntryFlags.DisableUnderlyingData Microsoft.Extensions.Caching.Hybrid CacheOptions.DefaultDuration HybridCache.GetOrCreateAsync HybridCache.RemoveByTagAsync MMCA.Common.Infrastructure Directory.Packages.props DistributedCacheService DisableLocalCacheWrite CachingQueryDecorator DisableLocalCacheRead AddCommonHybridCache"},{"u":"/docs/adr/077-hybridcache-substrate.html#rationale","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Rationale","x":"- The disjoint keyspace is the decision; everything else is implementation. Rather than trusting a second implementation to write a shape compatible with the first, this record…","i":"DisableUnderlyingData LocalCacheExpiration IncrementAsync GetAsync"},{"u":"/docs/adr/077-hybridcache-substrate.html#trade-offs","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Invalidation does not reach other replicas' L1 immediately. A remove evicts the L2 entry and the calling replica's L1; every other replica keeps its copy for up to…","i":"AddCommonHybridCache LocalCacheExpiration GetOrCreateAsync IncrementAsync ICacheService RemoveAll"},{"u":"/docs/adr/077-hybridcache-substrate.html#related","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Related","x":"ADR-026 (amended by this record: its Tier 1 substrate gains a third implementation, its 30-second default TTL becomes the local-cache bound as well, its prefix-invalidation model…","i":"IncrementAsync GetAsync"},{"u":"/docs/adr/078-csv-export-endpoint.html","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records"},{"u":"/docs/adr/078-csv-export-endpoint.html#status","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). The implementation lands in the MMCA.Common \"enterprise capability wave\" release. Unlike the wave's other features this one is NOT opt-in: every controller…","i":"EntityControllerBase"},{"u":"/docs/adr/078-csv-export-endpoint.html#context","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Context","x":"The request is \"export what you filtered\". The generic entity surface of ADR-034 already accepts a full query vocabulary on the paged route…","i":"EntityQueryPipeline.MaxUnboundedResultLimit context.CacheVaryByRules.QueryKeys options.ReturnHttpNotAcceptable PublicEndpointOutputCachePolicy ReturnHttpNotAcceptable QueryFilterModelBinder IAsyncEnumerable OutputFormatter sortDirection sortColumn Accept AddAPI"},{"u":"/docs/adr/078-csv-export-endpoint.html#decision","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Decision","x":"EntityControllerBase gains a virtual [HttpGet(\"export\")] ExportAsync(...) action (Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs). It accepts the same…","i":"QueryFieldService.ShapeCollectionData IPhysicalDbContextFactory.Create ApplicationSettings.MaxPageSize IEntityQueryService.GetAllAsync UnhandledResultFailureFilter JsonNamingPolicy.CamelCase OpenApiContractTestsBase MaxUnboundedResultLimit QueryFilterModelBinder IEntityControllerBase EntityControllerBase ApplicationSettings"},{"u":"/docs/adr/078-csv-export-endpoint.html#rationale","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Rationale","x":"- A route is an unambiguous request; an Accept header is a preference. Given a cache policy that ignores Accept and a pipeline configured to never return 406, a client that…","i":"OutputFormatter Accept"},{"u":"/docs/adr/078-csv-export-endpoint.html#trade-offs","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every derivative gains a bulk read whether its owner wanted one or not. The only gate is the controller's existing authorization posture. A resource that was safe to page 20…","i":"GetExportSpecification MaxExportRows ExportAsync MaxPageSize Accept Skip Take"},{"u":"/docs/adr/078-csv-export-endpoint.html#related","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Related","x":"ADR-034 (the generic entity surface and query contract this extends, and the MaxUnboundedResultLimit ceiling that forced the page loop), ADR-040 (the output-cache policy whose…","i":"MaxUnboundedResultLimit Accept Result"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#status","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#context","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Context","x":"In ASP.NET Core, middleware order is behavior, not style: a rate limiter placed before authentication partitions every request as anonymous, an HTTPS redirect placed in front of…","i":"TenantResolutionMiddleware SoftDeletedUserMiddleware UseAuthentication HttpContext.User"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#decision","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Decision","x":"Ship the edge as one ordered pipeline in the framework, UseCommonMiddlewarePipeline (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationExtensions.cs:45), and…","i":"UseCommonRequestLocalization UseCommonMiddlewarePipeline TenantResolutionMiddleware SoftDeletedUserMiddleware MapOidcDiscoveryEndpoint app.UseAuthentication UseForwardedHeaders app.UseRateLimiter HttpContext.User UseAuthorization KnownIPNetworks MapJwksEndpoint"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#rationale","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Rationale","x":"- Order is behavior, so it belongs to the framework, not to each host. Four of the adjacencies above fail silently when reversed: the limiter stops limiting, the tenant resolver…","i":"Program.cs"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#trade-offs","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing freezes the order. A workspace-wide search finds no test referencing UseCommonMiddlewarePipeline: the only non-host references are the method itself, a cross-reference…","i":"UseCommonMiddlewarePipeline MapOidcDiscoveryEndpoint UseCommonSecurityHeaders HttpContext.Items KnownIPNetworks MapControllers KnownProxies PreForwarded jwks_uri"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#related","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the in-process sibling: one fixed decorator order for commands and queries), ADR-019 (depends on forwarded headers before the limiter and on the limiter after…"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#status","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#context","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Context","x":"Both production apps deploy to Azure Container Apps from a single deploy.yml job on push to main, and every gate runs before anything rolls out: the deploy job waits on…","i":"deploy.yml foundation deploy main"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#decision","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Decision","x":"Roll out one revision at a time, verify it from outside, and auto-revert the image only when the verification fails. - Single-revision rollout. Every container app runs…","i":"activeRevisionsMode rollback_failed containerapp createdTime Provisioned pipefail revision rollback failure sqlcmd probe Smoke"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#rationale","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Rationale","x":"- ARM success is the wrong success signal. The smoke gate converts \"the control plane accepted the template\" into \"the fleet answers requests\", which is the only claim a deploy…","i":"deploy"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#trade-offs","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Trade-offs","x":"- Schema is never rolled back, so a bad migration is fix-forward only. The image reverts and the database does not, so the previous release resumes against the new schema. This…","i":"rollback_failed Provisioned revision APPS copy"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#related","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Related","x":"ADR-057 (built on this model: revision-only rollback is why every migration must be backward compatible one release back), ADR-030 (startup migration as sole migrator, the reason…"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#status","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#context","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Context","x":"Both deployed apps run a deliberately small production footprint: every Container App is declared with maxReplicas: 2 and every SQL database with the Basic tier…","i":"maxReplicas Basic"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#decision","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Decision","x":"The cost baseline is asserted by a read-only reusable workflow that both runs weekly and sits in deploy.needs, so an un-reverted scale-up blocks the next production deploy. - One…","i":"properties.template.scale.maxReplicas BASELINE_MAX_REPLICAS AZURE_RESOURCE_GROUP github.event_name workflow_dispatch workflow_call deploy.needs environment release.yml main.bicep production MMCAStore"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#rationale","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Configuration drift is the leading indicator; spend is the lagging one. The budget notification fires at 80% of actual spend, after the money is gone, and names a number rather…","i":"workflow_call deploy.needs maxReplicas deploy.yml sku.tier"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#trade-offs","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- A legitimate scale-up blocks deploys until the baseline is edited. Standing up extra capacity for a real event and then shipping a fix during it requires a pull request against…","i":"BASELINE_MAX_REPLICAS skip_freshness_gates skip_justification workflow_dispatch deploy.needs maxReplicas deploy.yml sku.tier Standard deploy Basic write"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#related","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-064 (the sibling deploy-precondition record, which decides the three proof-of-recency gates and enumerates this one only in passing; its break-glass input does not apply…","i":"deploy.needs"},{"u":"/docs/adr/082-two-tier-cors-posture.html","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records"},{"u":"/docs/adr/082-two-tier-cors-posture.html#status","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/082-two-tier-cors-posture.html#context","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Context","x":"Both deployed applications put a YARP gateway in front of per-module service hosts (ADR-008), and the browser and MAUI clients talk to the gateway origin while the services…"},{"u":"/docs/adr/082-two-tier-cors-posture.html#decision","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Decision","x":"Ship two cross-origin policies from the framework: an allow-listed one for service hosts and a deliberately broader one for gateways. - Service hosts register two named policies…","i":"CorsPolicyAllowSpecificOrigins app.Environment.IsDevelopment UseCommonMiddlewarePipeline Cors__AllowedOrigins__0 _allowSpecificOrigins AddCommonGatewayCors CorsPolicyAllowAll UseAuthentication AddDefaultPolicy AllowCredentials IHostEnvironment AllowAnyHeader"},{"u":"/docs/adr/082-two-tier-cors-posture.html#rationale","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Rationale","x":"- A proxy cannot allow-list what it does not own. The gateway has no controllers and no knowledge of which headers the fronted services accept, so a header allow-list there would…","i":"UseCommonMiddlewarePipeline AllowCredentials IHostEnvironment AllowAnyOrigin AddCommonCors UseCors"},{"u":"/docs/adr/082-two-tier-cors-posture.html#trade-offs","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Trade-offs","x":"- The gateway policy is broad on two of three axes. Any header and any method are accepted for an allow-listed origin. The origin list is the only lever there, so a mistake in…","i":"ProductionHostApplicationFactory IHostEnvironment.IsDevelopment configuration.GetSection ValidateOnStart UseEnvironment AddCommonCors UseCors string Get"},{"u":"/docs/adr/082-two-tier-cors-posture.html#related","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Related","x":"ADR-079 (the shared middleware pipeline whose fixed order places the environment-selected CORS policy between routing and authentication), ADR-008 (the gateway plus per-module…"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#status","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#context","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Context","x":"ADR-003 decides how a domain event moves: captured into the outbox inside SaveChangesAsync, dispatched in-process after commit, or published to the broker when it is an…","i":"SaveChangesAsync SessionChanged SessionCreated SessionDeleted Changed Created Deleted Session Entity"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#decision","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Decision","x":"Every generic CRUD lifecycle transition of an entity raises one event type for that entity, carrying a DomainEntityState discriminator; handlers filter on State. - One base…","i":"ProductVariantPriceChanged TicketChangedAuditHandler ProductVariantSkuChanged ShoppingCartItemChanged SessionQuestionChanged ShoppingCartCheckedOut ProductVariantRemoved SessionCreatedHandler BaseIntegrationEvent ProductVariantAdded EntityChangedEvent DomainEntityState"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#rationale","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Rationale","x":"- One type per entity is one subscription surface. A subscriber declares interest in the entity, then decides which transitions matter, instead of the container deciding for it…","i":"SessionChanged SessionCreated SessionDeleted OrderPaid"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#trade-offs","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every selective handler pays a filter. A handler that cares about one transition has to open with a State guard and return (SessionCreatedHandler.cs:17-18 is the shape to…","i":"EntityChangedEvent PointsEntryChanged BaseDomainEvent LivePollChanged LivePollStatus Unchanged Added State TId"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#related","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (how these events are captured and dispatched; this ADR decides only their shape), ADR-010 (schema versioning for the discriminator once it crosses a service boundary),…","i":"MessageId"},{"u":"/docs/adr/084-stripe-webhook-ingress.html","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records"},{"u":"/docs/adr/084-stripe-webhook-ingress.html#status","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/084-stripe-webhook-ingress.html#context","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Context","x":"Four ADRs already cover how a message crosses a boundary in this workspace. ADR-003 decides how an event leaves a service (outbox, at-least-once). ADR-021 decides how a…","i":"Warning"},{"u":"/docs/adr/084-stripe-webhook-ingress.html#decision","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Decision","x":"Treat third-party webhook ingress as its own contract with two halves: an acceptance-coded endpoint and a self-registering, self-provisioning endpoint registration at startup. -…","i":"StripeWebhookRegistrationService EventUtility.ValidateSignature payment_intent.payment_failed AddModuleSalesInfrastructure SignatureVerificationFailed StripeWebhookSecretProvider checkout.session.completed throwOnApiVersionMismatch checkout.session.expired HttpContext.Request.Body EventUtility.ParseEvent Stripe__WebhookBaseUrl"},{"u":"/docs/adr/084-stripe-webhook-ingress.html#rationale","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Rationale","x":"- The caller's protocol decides the response vocabulary. Stripe reads a status code as \"keep retrying\" or \"stop\", not as \"this succeeded\" or \"this failed\". Mapping every…","i":"Critical Warning"},{"u":"/docs/adr/084-stripe-webhook-ingress.html#trade-offs","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A startup service that writes to a live third-party account. Booting a Sales replica creates and deletes webhook endpoints in the real Stripe account…","i":"StripeWebhookRegistrationService PaymentReconciliationService PaymentsController WebhookBaseUrl SecretKey Critical Warning"},{"u":"/docs/adr/084-stripe-webhook-ingress.html#related","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (outbound at-least-once delivery, the other end of the same family), ADR-021 (broker-side inbound dedup, which never sees a webhook), ADR-017 (client-supplied idempotency…"},{"u":"/docs/onboarding/index.html","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","x":"A teaching guide for an experienced .NET engineer who is new to this codebase. It walks every first-party type, explaining not just what each type is but how it works and why it…","i":"CLAUDE.md dotnet new"},{"u":"/docs/onboarding/index.html#how-the-guide-is-organized-two-axes","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"How the guide is organized, two axes","x":"The guide has two organizing axes that work together. 1. Primary axis, functional grouping. Every type lives in exactly one functional group: the capability or cross-cutting…","i":"SelfHttpWarmupTask GateTestContext MMCA.Common MMCA.ADC Priority"},{"u":"/docs/onboarding/index.html#chapters","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Chapters","x":"---","i":"AuthenticationServiceBase HttpResilienceDefaults AuthenticationService ConferencePermissions ApplicationDbContext IdentityPermissions SQLServerDbContext HealthCheckTags OutboxFinalizer HasPermission ThemeService Contracts"},{"u":"/docs/onboarding/index.html#legend-how-to-read-a-type-section","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Legend, how to read a type section","x":"Every type gets one section using this template: {TypeName} {Assembly} · {namespace} · {file:line} · Level {n} · {kind} - What it is: one or two plain-language sentences. -…","i":"namespace Result Rubric Name"},{"u":"/docs/onboarding/index.html#suggested-reading-paths","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Suggested reading paths","x":"- Framework-first (recommended). Primer → group-01 → upward. You meet the MMCA.Common foundations before the MMCA.ADC features that build on them; this matches dependency order…","i":"MMCA.Common MMCA.ADC Rubric"},{"u":"/docs/onboarding/index.html#the-companion-projects-context","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"The companion projects (context)","x":"This guide covers MMCA.Common (the framework) and MMCA.ADC (one consumer). MMCA.Store is out of scope. The dependency arrow is why the Common framework groups (1–16) come before…","i":"MMCA.Store"},{"u":"/docs/onboarding/00-dependency-manifest.html","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","x":"Each distinct type node is assigned a Level by longest-path layering over its first-party dependencies (base/interface, generic constraints, field/property/param/return types,…","i":"System.Guid global static using Using int"},{"u":"/docs/onboarding/00-dependency-manifest.html#manifest-by-level-then-assembly","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","t":"Manifest (by level, then assembly)","i":"DefaultEntityConfigurationAssemblyProviderTests GetPublicSessionCategoryItemFilterHandlerTests GetPublicSpeakerCategoryItemFilterHandlerTests AddSessionQuestionAnswerCommandValidatorTests ConferenceCategoryCreateRequestValidatorTests ConferenceCategoryUpdateRequestValidatorTests UserNotificationExportServiceGrpcAdapterTests MarkAllNotificationsReadHandlerTrackingTests SignalRPushNotificationSenderAdditionalTests AddEventQuestionAnswerCommandValidatorTests AddSessionCategoryItemCommandValidatorTests AddSpeakerCategoryItemCommandValidatorTests"},{"u":"/docs/onboarding/00-group-taxonomy.html","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","x":"This is the primary axis of the guide. Every one of the 3,264 distinct first-party type nodes from 00-inventory.md is assigned to exactly one functional group - its primary home:…","i":"MMCA.Common MMCA.ADC Result"},{"u":"/docs/onboarding/00-group-taxonomy.html#design-notes-boundary-decisions-worth-stating-up-front","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"Design notes (boundary decisions worth stating up front)","x":"- Cycles are kept whole. The 13 dependency cycles (SCCs) from the manifest are never split across groups. Notably the ApplicationDbContext AuditSaveChangesInterceptor…","i":"DomainEventSaveChangesInterceptor DataSourceModelCacheKeyFactory AuditSaveChangesInterceptor MMCA.ADC.Notification ApplicationDbContext MMCA.Common.Testing IAnonymizable PiiAttribute Gallery Rubric Fact S30"},{"u":"/docs/onboarding/00-group-taxonomy.html#the-groups-ordered","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"The groups (ordered)","x":"Reconciliation: 1636 production types across 26 groups + 1628 test/testing types in G25 = 3264 (matches the inventory's distinct-node count). No type appears twice; none dropped.…"},{"u":"/docs/onboarding/00-group-taxonomy.html#group-membership","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"Group membership","x":"group-01-result-error-handling.md 11 types The Result/Error railway that every operation returns instead of throwing; pagination result shapes. group-02-domain-building-blocks.md…","i":"MMCA.ADC.ServiceBusEmulator.IntegrationTests SessionBookmarkValidationServiceGrpcAdapter DefaultEntityConfigurationAssemblyProvider GetPublicSessionCategoryItemFilterHandler GetPublicSpeakerCategoryItemFilterHandler SessionQuestionPendingCountChangedPayload AddSessionQuestionAnswerCommandValidator ConferenceCategoryCreateRequestValidator ConferenceCategoryUpdateRequestValidator CookieSessionRefreshMiddlewareExtensions DisabledSessionBookmarkValidationService MMCA.ADC.Conference.Infrastructure.Tests"},{"u":"/docs/onboarding/00-inventory.html","d":"Phase 0: Type Inventory","k":"Onboarding Guide","x":"Generated mechanically by a Roslyn syntactic parse of every in-scope .cs file under MMCA.Common/Source, MMCA.Common/Tests, MMCA.ADC/Source, MMCA.ADC/Tests. - Files scanned: 2699…","i":"extension"},{"u":"/docs/onboarding/00-inventory.html#full-inventory","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Full inventory","i":"MMCA.ADC.Conference.Application.Events.Sessionize MMCA.ADC.Conference.Application.Events.Validation MMCA.ADC.Conference.Application.Tests.Events.DTOs MMCA.ADC.Conference.Domain.Questions.DomainEvents MMCA.ADC.Conference.Infrastructure.Tests.Services MMCA.ADC.Conference.IntegrationTests.CrossService MMCA.ADC.Engagement.Application.CheckIns.Services MMCA.ADC.Engagement.Domain.LivePolls.DomainEvents MMCA.ADC.Engagement.Domain.Tests.SessionQuestions MMCA.ADC.Identity.IntegrationTests.Infrastructure MMCA.Common.Application.Interfaces.Infrastructure MMCA.Common.Application.Users.UseCases.DeleteUser"},{"u":"/docs/onboarding/00-inventory.html#extensiont-blocks","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"extension(T) blocks","i":"IDistributedApplicationBuilder IBusRegistrationConfigurator AuthenticationBuilder IEndpointRouteBuilder WebApplicationBuilder IApplicationBuilder ICurrentUserService IReadOnlyCollection currentUserService IServiceCollection OutputCacheOptions IResourceBuilder"},{"u":"/docs/onboarding/00-inventory.html#generated--excluded-artifacts-no-type-sections-written","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Generated / excluded artifacts (no type sections written)","x":"118 files excluded as generated (EF migrations, snapshots, .g.cs, AssemblyInfo)."},{"u":"/docs/onboarding/00-primer.html","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","x":"This chapter teaches the cross-cutting things once, so the per-type chapters can stay focused. Read it before the group chapters (start with group-01). Everything here is either…"},{"u":"/docs/onboarding/00-primer.html#1-the-big-picture","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"1. The big picture","x":"Two codebases are in scope: - MMCA.Common: a framework, published as fifteen NuGet packages to nuget.org (the documented install path) and mirrored to GitHub Packages (ADR-053)…","i":"Testing.Architecture Aspire.Hosting Infrastructure Application MMCA.Common Testing.E2E references Testing.UI MMCA.ADC Testing UI.Maui Aspire"},{"u":"/docs/onboarding/00-primer.html#2-architectural-styles-this-codebase-commits-to","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"2. Architectural styles this codebase commits to","x":"These are the recurring ideas. Each is taught fully at its first concrete appearance in a group chapter; here is the orientation so the vocabulary is familiar. - Domain-Driven…","i":"EntityTypeConfigurationSQLServer PublicEndpointOutputCachePolicy JwtForwardingClientInterceptor JwtSettings.SigningAlgorithm EntityTypeConfigurationBase UseCommonMiddlewarePipeline TenantResolutionMiddleware ExportUserDataHandlerBase ISoftDeletedUserValidator ServiceInfoControllerBase SoftDeletedUserMiddleware AddApplicationDecorators"},{"u":"/docs/onboarding/00-primer.html#3-the-external-stack-bcl--nuget-external-level-0","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"3. The external stack (BCL / NuGet, \"external Level 0\")","x":"These are not first-party and get no per-type sections. Versions are from MMCA.Common/Directory.Packages.props and MMCA.ADC/Directory.Packages.props (Central Package Management,…","i":"Microsoft.Extensions.ServiceDiscovery.Yarp Microsoft.Extensions.Http.Resilience Notification.PushNotifications IEntityTypeConfiguration MMCA.Common.UI global.json IMessageBus SaveChanges TryDecorate DbContext OrderBy vX.Y.Z"},{"u":"/docs/onboarding/00-primer.html#4-c-build-and-code-style-conventions","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"4. C#, build, and code-style conventions","x":"- .NET 10.0, LangVersion: preview: required because the codebase uses C extension types (extension(T) syntax, see below). - Central Package Management (CPM). All NuGet versions…","i":"csharp_style_namespace_declarations MMCA.Common.Testing.Architecture ManagePackageVersionsCentrally Directory.Packages.props DependencyInjection.cs DependencyVersionTests TreatWarningsAsErrors csharp_prefer_braces EntityTypeExtensions packageSourceMapping IServiceCollection IArchitectureMap"},{"u":"/docs/onboarding/00-primer.html#5-the-solution--test-layout","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"5. The solution / test layout","x":"- .slnx: the human solution (XML format). .slnf, a solution filter used in CI to build a subset fast (MMCA.Store.CI.slnf, MMCA.ADC.CI.slnf). - Microsoft Testing Platform, not…","i":"MMCA.Common.UI.E2E.Tests MMCA.Common.UI.Gallery MMCA.Store.CI.slnf MMCA.ADC.CI.slnf csproj slnx"},{"u":"/docs/onboarding/00-primer.html#6-the-34-category-architecture-evaluation-lens","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"6. The 34-category architecture-evaluation lens","x":"This codebase is also scored against a 34-category rubric (Website/docs-src/governance/ArchitectureEvaluationCriteria.md, published at ). This guide weaves the rubric in so you…","i":"Rubric Name"},{"u":"/docs/onboarding/group-01-result-error-handling.html","d":"1. Result & Error Handling","k":"Onboarding Guide","x":"This is the first capability chapter, and it is deliberately first because the pattern it teaches underpins almost every other one in the guide. Before you read a command…","i":"ArgumentOutOfRangeException.ThrowIfNegative ResultJsonConverterFactory.CreateConverter ArgumentNullException.ThrowIfNull DomainInvariantViolationException MMCA.Common.Shared.Serialization ApiControllerBase.HandleFailure MMCA.Common.Shared.Abstractions System.Text.Json.Utf8JsonReader MMCA.Common.Shared.Exceptions ValidationFailureExtensions ResultJsonConverterFactory System.Collections.Generic"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","x":"What this group covers. This is the DDD heart of the framework, the small, dependency-light primitives every business model in MMCA.Common and MMCA.ADC is built from. There are…","i":"EnumerationJsonConverterFactory AuditableAggregateRootEntity IdValueGeneratedAttribute CurrencyJsonConverter PhoneNumberInvariants EntityTypeExtensions EnumerationConverter AuditableBaseEntity MMCA.Common.Domain MMCA.Common.Shared RedactableProperty AddressInvariants"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#the-entity-chain-one-capability-per-rung","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"The entity chain, one capability per rung","x":"Read the chain bottom-up. BaseEntity (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/BaseEntity.cs:14) is almost nothing: a single required init identifier of the per-entity…","i":"AuditableAggregateRootEntity AuditSaveChangesInterceptor ChangeTracker.Entries AuditableBaseEntity GetChildOrNotFound RemoveDomainEvents ClearDomainEvents IAuditableEntity ValidateSetItems TIdentifierType AddDomainEvent entry.Property"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#two-opt-in-markers-beside-the-chain","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Two opt-in markers beside the chain","x":"Not every cross-cutting capability belongs on the inheritance chain, because not every entity should pay for it. ITenantEntity…","i":"AuditTrailSaveChangesInterceptor TenantSaveChangesInterceptor entity.HasQueryFilter ApplyTenantFilters IAuditableEntity TenantFilterName AddMultiTenancy AuditTrailEntry IAuditedEntity AddAuditTrail configuration ITenantEntity"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#how-a-domain-event-leaves-an-aggregate","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"How a domain event leaves an aggregate","x":"The runtime flow ties this group to the events/outbox group. A command handler loads an aggregate, calls a business method, and that method calls AddDomainEvent(...); the event…","i":"DomainEventSaveChangesInterceptor context.ChangeTracker.Entries RemoveDomainEvents DomainEntityState IIntegrationEvent DeferredDispatch OutboxProcessor AddDomainEvent IAggregateRoot OutboxMessage IDomainEvent Unchanged"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#value-objects-invalid-instances-cannot-exist","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Value objects, invalid instances cannot exist","x":"The second family models concepts with no identity: two Money(10, USD) are equal because their values match, not because they are the same row. ValueObject is the cheapest…","i":"EnsurePreferredCultureIsValid EnsurePreferredThemeIsValid EnsureCollectionIsNotEmpty InvalidOperationException PhoneNumberValueConverter EnsureMoneyIsNotNegative EnsureBytesAreNotEmpty EnsureStringIsNotEmpty CurrencyJsonConverter EnsureStringMaxLength PhoneNumberInvariants EnsureIdIsNotDefault"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#smart-enumerations-a-closed-set-that-can-carry-behavior","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Smart enumerations, a closed set that can carry behavior","x":"Enumeration (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:71) is the answer to a recurring shape a CLR enum handles badly: a closed set of named members…","i":"ValueObjectsAreImmutableSealedInShared JsonSerializerOptions.Converters EnumerationJsonConverterFactory Enumeration.UnknownValue Enumeration.UnknownName CurrencyJsonConverter EnumerationConverter ReadOnlyCollection FrozenDictionary JsonConverter JsonException TEnumeration"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#governance-markers-metadata-that-other-layers-act-on","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Governance markers, metadata that other layers act on","x":"The last family is tiny attributes and helpers that carry intent the rest of the stack reads reflectively. PiiAttribute…","i":"AuditTrailSaveChangesInterceptor CultureInfo.InvariantCulture IdValueGeneratedAttribute PiiRedactor.RedactedToken EncryptedStringConverter PiiConventionTestsBase ConcurrentDictionary EntityTypeExtensions GetCustomAttribute IsIdValueGenerated MMCA.Common.Domain PiiConventionTests"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#where-this-group-sits","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Where this group sits","x":"Everything above is consumed by the layers that follow: every module entity (for example the Conference domain, Engagement, and Identity modules) derives from one of the three…","i":"EnumerationJsonConverterFactory.CreateConverter PhoneNumberInvariants.EnsurePhoneNumberIsValid AddressInvariants.EnsureAddressLine1IsValid MMCA.Common.Domain.Interfaces.IAnonymizable AddressInvariants.AddressLine1MaxLength EntityTypeExtensions.IsIdValueGenerated AddressInvariants.EnsureAddressIsValid EventInvariants.EnsureDateRangeIsValid ValueObjectsAreImmutableSealedInShared EntityTypeBuilderExtensions.OwnsMoney EntitiesWithPiiImplementAnonymizable EmailInvariants.EnsureEmailIsValid"},{"u":"/docs/onboarding/group-03-querying-specifications.html","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","x":"What this group covers. Every read in MMCA.Common and ADC (\"list the published events\", \"get session 42\", \"the speakers in Atlanta, page 3, sorted by name, with only the name and…","i":"Expression IQueryable TEntity OFFSET SELECT ORDER WHERE bool Func name bio"},{"u":"/docs/onboarding/group-03-querying-specifications.html#the-specification-pattern-the-trusted-predicate","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The Specification pattern, the trusted predicate","x":"ISpecification (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/ISpecification.cs:12) exposes two faces of one rule: a Criteria expression tree that EF Core translates to…","i":"SpecificationConventionTestsBase PublishedEventSpecification CrossSourceSpecification OwnedByUserSpecification dependent.ForeignKey Enumerable.Contains InlineSpecification ParameterExpression s.Event.IsPublished Expression.AndAlso Expression.Invoke Expression.Lambda"},{"u":"/docs/onboarding/group-03-querying-specifications.html#dynamic-filtering-one-strategy-per-clr-type","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Dynamic filtering, one Strategy per CLR type","x":"User filters arrive as a Dictionary , property name to operator key plus raw string value, parsed from the query string by QueryFilterModelBinder at the API edge, which caps a…","i":"Filter.Operator.NotSupported QueryParameterizationTests Filter.Property.NotFound Filter.Type.NotSupported datetimefilterstrategy QueryFilterModelBinder ResolveFilterValueType decimalfilterstrategy Filter.Value.Invalid StringFilterStrategy ResolvePropertyInfo boolfilterstrategy"},{"u":"/docs/onboarding/group-03-querying-specifications.html#sorting-sparse-fieldsets-and-paging-arithmetic","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Sorting, sparse fieldsets, and paging arithmetic","x":"QueryFieldService (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:16) owns the rest of read shaping. ApplySorting (QueryFieldService.cs:135)…","i":"PropertyInfo.GetValue ValidateSortDirection ApplyFieldSelection ShapeCollectionData GetShapedAccessors Expression.Lambda QueryFieldService PagingMath.Clamp PropertyAccessor MaxCacheEntries ExpandoObject ApplySorting"},{"u":"/docs/onboarding/group-03-querying-specifications.html#the-pipeline-two-paths-and-one-contract","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The pipeline, two paths and one contract","x":"IEntityQueryPipeline (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/IEntityQueryPipeline.cs:10) is the execution contract, implemented by the sealed…","i":"inavigationmetadataprovider NavigationMetadataProvider MaxUnboundedResultLimit NavigationPropertyInfo CountUnpaginatedAsync EntityQueryParameters IEntityQueryPipeline INavigationPopulator entityquerypipeline navigationPopulator NavigationMetadata FrozenDictionary"},{"u":"/docs/onboarding/group-03-querying-specifications.html#the-query-service-the-public-face","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The query service, the public face","x":"IEntityQueryService (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/IEntityQueryService.cs:19) and its concrete EntityQueryService…","i":"IEntityQueryPipeline.ExecuteAsync SpeakerEntityQueryService BuildPaginationMetadata MaxUnboundedResultLimit TryGetByIdFastPathAsync DTOToEntityPropertyMap TryGetFastPathIncludes EntityQueryParameters PagedCollectionResult GetAllForLookupAsync INavigationPopulator DTOMapper.MapToDTOs"},{"u":"/docs/onboarding/group-03-querying-specifications.html#end-to-end-one-list-request","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"End to end, one list request","x":"The request reaches a read controller, EntityControllerBase (Group 12), which resolves MaxPageSize per request from IApplicationSettings, falling back to 500 when unset…","i":"PublicSessionStatusSpecification.StatusCriteria EntityQueryPipeline.MaxUnboundedResultLimit Filtering.DynamicQueryConfig.Parameterized MMCA.Common.Application.Services.Filtering QueryFilterService.ResolveFilterValueType System.Linq.Expressions.ExpressionVisitor NavigationMetadataProvider.BuildIncludes CrossSourceSpecification.BuildCriteria MMCA.Common.Application.Services.Query MMCA.Common.Application.Specifications QueryFieldService.ApplyFieldSelection QueryFieldService.ShapeCollectionData"},{"u":"/docs/onboarding/group-04-events-outbox.html","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","x":"What this chapter covers. This group is the codebase's event spine: how an aggregate says \"something happened\", how that fact is persisted so it cannot be lost, and how it…"},{"u":"/docs/onboarding/group-04-events-outbox.html#the-two-kinds-of-event","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The two kinds of event","x":"Everything starts with two marker interfaces in the Domain layer. IDomainEvent is the base contract: a DateOccurred timestamp (when the business action happened, not when it was…","i":"BaseIntegrationEvent EntityChangedEvent DomainEntityState IIntegrationEvent BaseDomainEvent TIdentifierType Infrastructure UserRegistered SchemaVersion Architecture DateOccurred IDomainEvent"},{"u":"/docs/onboarding/group-04-events-outbox.html#raising-and-capturing-where-the-outbox-is-written","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Raising and capturing: where the outbox is written","x":"Aggregates raise events by calling AddDomainEvent() (see AuditableAggregateRootEntity in G02), which simply buffers them on the entity. Nothing is dispatched yet; the events ride…","i":"DomainEventSaveChangesInterceptor OutboxMessage.FromDomainEvent AuditableAggregateRootEntity TIdentifierType AddDomainEvent OutboxMessages OutboxMessage SavingChanges Architecture DbContext Rubric Data"},{"u":"/docs/onboarding/group-04-events-outbox.html#the-routing-split-local-events-dispatch-in-process-integration-events-wait-for-the-bus","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The routing split: local events dispatch in-process, integration events wait for the bus","x":"Here is the detail that most people get wrong, and it is the heart of the design. After the transaction commits (SavedChanges), the interceptor does not treat all captured events…","i":"IIntegrationEventHandler IDomainEventDispatcher SafeDomainEventHandler DomainEventDispatcher someIntegrationEvent IDomainEventHandler TIntegrationEvent DbContextFactory OutboxFinalizer OutboxProcessor AddDomainEvent ExecuteUpdate"},{"u":"/docs/onboarding/group-04-events-outbox.html#the-safety-net-how-the-processor-schedules-itself","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The safety net: how the processor schedules itself","x":"The OutboxProcessor is a BackgroundService and the most intricate type in the group; most of its complexity is about not wasting work. It exists because the steps between commit…","i":"PollingIntervalSeconds ProcessingDelaySeconds BackgroundService OutboxCycleResult ComputeWaitTime OutboxProcessor OutboxSettings ExecuteUpdate IOutboxSignal SemaphoreSlim LeaseSeconds OutboxSignal"},{"u":"/docs/onboarding/group-04-events-outbox.html#failures-dead-letters-and-keeping-the-table-and-telemetry-bounded","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Failures, dead-letters, and keeping the table (and telemetry) bounded","x":"Delivery failures split into two very different outcomes, worth keeping straight. A transient failure (a handler or broker publish throwing) increments the row's RetryCount,…","i":"OutboxPollFilterProcessor outbox.dead_letter.count DeadLetterRetentionDays RetryBackoffBaseSeconds CleanupIntervalHours OutboxCleanupService MMCA.Common.Outbox Observability OutboxMetrics RetentionDays TimeProvider Operability"},{"u":"/docs/onboarding/group-04-events-outbox.html#the-pluggable-transport-in-process-versus-broker","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The pluggable transport: in-process versus broker","x":"Here is the boundary that makes a module extractable without rewriting its handlers. Application code that wants to publish an integration event depends on IEventBus (or on the…","i":"InProcessMessageBus AddBrokerMessaging IIntegrationEvent InProcessEventBus BrokerMessageBus OutboxFinalizer OutboxProcessor BrokerEventBus Microservices Application IMessageBus IEventBus"},{"u":"/docs/onboarding/group-04-events-outbox.html#consuming-from-the-broker-the-inbox-and-the-generic-consumer","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Consuming from the broker: the inbox and the generic consumer","x":"On the receiving side of a broker hop, application code keeps writing plain IIntegrationEventHandler implementations; there is no MassTransit-specific consumer class to author…","i":"IntegrationEventConsumerExtensions RegisterIntegrationEventConsumer IBusRegistrationConfigurator IIntegrationEventHandler IntegrationEventConsumer AlreadyProcessedAsync MarkProcessedAsync DbUpdateException NoOpInboxStore EfInboxStore InboxMessage AddConsumer"},{"u":"/docs/onboarding/group-04-events-outbox.html#putting-it-together-one-events-life","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Putting it together, one event's life","x":"To see the whole spine at once, follow a single integration event from a producer service to a consumer service in broker mode. (1) A command mutates an aggregate, which raises…","i":"MMCA.Common.Infrastructure.Persistence.Outbox MMCA.Common.Infrastructure.Persistence.Inbox OutboxProcessor.ProcessPendingMessagesAsync config.RegisterIntegrationEventConsumer ApplicationDbContext.SaveChangesAsync Microsoft.Extensions.Logging.ILogger MMCA.Common.Application.DomainEvents ApplicationDbContext.ConfigureInbox domainEventDispatcher.DispatchAsync MMCA.Common.Infrastructure.Services TryPersistStampsOnCancellationAsync IntegrationEventConsumerExtensions"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","x":"What this group covers. Every write and every read in an MMCA application is a use case: a small, single-purpose object (a command or a query) handed to a handler that does…","i":"TransactionalCommandDecorator FeatureGateCommandDecorator ValidatingCommandDecorator FeatureGateQueryDecorator ProfilingCommandDecorator CachingCommandDecorator LoggingCommandDecorator ProfilingQueryDecorator CachingQueryDecorator LoggingQueryDecorator ResultFailureFactory ICommandWithRequest"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#the-shape-thin-handlers-fat-pipeline","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"The shape: thin handlers, fat pipeline","x":"A handler is deliberately tiny. ICommandHandler (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/ICommandHandler.cs:9) and IQueryHandler…","i":"cancellationToken CancellationToken ICommandHandler IQueryHandler HandleAsync Patterns TCommand default TResult Design Result Rubric"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#how-the-pipeline-is-assembled-scrutor-registration-versus-execution-order","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"How the pipeline is assembled (Scrutor, registration versus execution order)","x":"The wiring lives in DependencyInjection.cs (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:21), exposed as extension(IServiceCollection services) members…","i":"ScanModuleApplicationServices ProfilingCommandDecorator AddApplicationDecorators DependencyInjectionTests AddApplicationProfiling ProfilingQueryDecorator DependencyInjection.cs EntityQueryPipeline IServiceCollection ICommandHandler TAssemblyMarker AddApplication"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#why-this-exact-order-and-what-each-layer-guards","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Why this exact order, and what each layer guards","x":"The nesting order is a deliberate cost-and-correctness argument, spelled out in the registration XML-doc (DependencyInjection.cs:72-86): - Feature-gating is outermost so a…","i":"TransactionCommitAmbiguousException ICacheService.RemoveByPrefixAsync IFeatureManager.IsEnabledAsync TransactionalCommandDecorator FeatureGateCommandDecorator OperationCanceledException ValidatingCommandDecorator CqrsMetrics.QueryDuration ExecuteInTransactionAsync FeatureGateQueryDecorator Stopwatch.GetElapsedTime CachingCommandDecorator"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#opt-in-by-marker-interface-pay-only-for-what-you-use","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Opt-in by marker interface, pay only for what you use","x":"The pipeline is registered for every handler, but most decorators are dormant unless the use case asks for them. The switch is a set of tiny marker / role interfaces in…","i":"MMCA.Common.Application.UseCases GetTicketByIdQuery ICacheInvalidating FeatureManagement GetNowNextQuery IQueryCacheable ITransactional CacheDuration IFeatureGated CachePrefix FeatureName OutputCache"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#tenant-scoping-and-the-two-lock-tables","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Tenant scoping and the two lock tables","x":"Both caching decorators are multi-tenant aware, and the reason is a nice illustration of where a cross-cutting concern has to live. ICacheService is a singleton and therefore…","i":"ICacheService.GetOrCreateAsync CachingQueryDecorator KeyedSemaphoreStripe QueryCacheKeyLocks ITenantContext TenantCacheKey CacheKeyLocks ICacheService tenantId TResult TQuery null"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#two-supporting-pieces-and-a-worked-example","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Two supporting pieces, and a worked example","x":"Two small helpers make the short-circuit decorators possible. ResultFailureFactory…","i":"AuditableAggregateRootEntity TypeInitializationException InvalidOperationException DeleteSessionCommand DeleteSpeakerCommand ResultFailureFactory DeleteEntityCommand DeleteEntityHandler ICacheInvalidating MMCA.Common.Cqrs TIdentifierType CachePrefix"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#the-other-application-layer-contracts-in-this-group","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"The other Application-layer contracts in this group","x":"Four contracts sit beside the pipeline rather than inside it. They are declared here, in the Application layer, and implemented in Infrastructure or the composition root, which…","i":"AuditTrailSaveChangesInterceptor InProcessDistributedLock IEntityRequestMapper RedisDistributedLock ICommandWithRequest ScheduledJobRunner cancellationToken IAuditTrailReader AuditTrailReader IAsyncDisposable IDistributedLock TIdentifierType"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#where-this-fits-and-the-failure-mode-contract","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Where this fits, and the failure-mode contract","x":"These contracts sit in the Application layer of Clean Architecture (primer §1), above Domain and below Infrastructure and the API. The API layer (G12) resolves a closed handler…","i":"Microsoft.FeatureManagement.IFeatureManager MMCA.Common.Application.UseCases.Decorators QueryCacheKeyLocks.Locks.AcquireAsync MMCA.Common.Application.Extensions MMCA.Common.Application.Interfaces ICacheService.RemoveByPrefixAsync correlationContext.CorrelationId MMCA.Common.Application.UseCases System.Diagnostics.Metrics.Meter ConferenceCategoryCreateRequest MMCA.Common.Shared.Abstractions ICacheService.GetOrCreateAsync"},{"u":"/docs/onboarding/group-06-validation.html","d":"6. Validation","k":"Onboarding Guide","x":"This chapter covers the small, framework-level validation kit that MMCA.Common.Application ships so that every consuming module validates command input the same way: a set of…","i":"AddressInvariants.AddressLine1MaxLength AddressInvariants.AddressLine2MaxLength ValidationFailureExtensions.ToErrors AddValidatorsFromAssemblyContaining AddressInvariants.CountryMaxLength AddressInvariants.ZipCodeMaxLength MMCA.Common.Application.Extensions MMCA.Common.Application.Validation System.Linq.Expressions.Expression AddressInvariants.StateMaxLength AddressInvariants.CityMaxLength MMCA.Common.Shared.ValueObjects"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html","d":"7. Persistence & EF Core","k":"Onboarding Guide","x":"What this group covers. This is the framework's data-access engine: everything between a domain aggregate and a row in a database. It is the single largest group in the guide…","i":"ApplicationDbContext SQLServerDbContext IWriteRepository SaveChangesAsync CosmosDbContext IReadRepository SqliteDbContext TIdentifierType IRepository UnitOfWork TEntity"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#one-base-context-one-class-per-engine-one-instance-per-database","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"One base context, one class per engine, one instance per database","x":"ApplicationDbContext (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:39) is an abstract primary-constructor class over EF's…","i":"Database.CreateExecutionStrategy DataSourceModelCacheKeyFactory IX_AuditTrailEntries_Entity IX_OutboxMessages_Processed IX_InboxMessages_MessageId IX_ScheduledJobs_NextRunOn PendingModelChangesWarning IX_OutboxMessages_Pending BeginTransactionAsync ChangeTracker.Entries ApplicationDbContext EnableRetryOnFailure"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#savechanges-as-an-interceptor-pipeline","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"SaveChanges as an interceptor pipeline","x":"The base context resolves its interceptors from DI in OnConfiguring (ApplicationDbContext.cs:236-261), and registration order is execution order. The audit interceptor runs…","i":"DomainEventSaveChangesInterceptor AuditSaveChangesInterceptor DiscardAbandonedCapture IDomainEventDispatcher BeginCaptureExclusion ConditionalWeakTable EndCaptureExclusion FlushDeferredAsync GetRequiredService RemoveDomainEvents CurrentSaveUserId IIntegrationEvent"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#the-tenant-boundary-read-filter-plus-write-guard","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"The tenant boundary, read filter plus write guard","x":"Multi-tenancy (ADR-073) is two independent halves that meet in this group. The read half is the named Tenant query filter the base context applies to every non-owned…","i":"TenantSaveChangesInterceptor CrossTenantWriteException InvalidOperationException TenantDataSourceTargets TenantDataSourceTarget ApplicationDbContext IgnoreQueryFilters CurrentTenantId ITenantEntity TenantContext e.TenantId SoftDelete"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#recording-what-changed-the-audit-trail","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Recording what changed, the audit trail","x":"AuditTrailSaveChangesInterceptor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:62) is the fourth interceptor and…","i":"AuditTrailSaveChangesInterceptor AuditTrailCleanupJob AuditTrailReader AuditTrailEntry IAuditedEntity AddAuditTrail ExecuteDelete RedactedToken RetentionDays PiiAttribute PropertyName PiiRedactor"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#repositories-and-the-unit-of-work","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Repositories and the unit of work","x":"Handlers do not touch a DbContext directly. They ask a UnitOfWork (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:13) for a repository. The…","i":"TransactionCommitAmbiguousException DefaultSqlServerDbContextFactory ApplicationDbContextEFFactory DefaultCosmosDbContextFactory DefaultSqliteDbContextFactory UpdatePropertySetterBuilder EFReadRepositoryDecorator ExecuteInTransactionAsync HasPendingMigrationsAsync IPhysicalDbContextFactory ChangeTracker.HasChanges PhysicalDbContextFactory"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#routing-an-entity-to-its-database","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Routing an entity to its database","x":"The heart of ADR-006 is that every entity resolves to a DataSourceKey (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/DataSourceKey.cs:15), a (Engine,…","i":"IEntityDataSourceRegistry EntityDataSourceRegistry UseDataSourceAttribute NamespaceConventions UseDatabaseAttribute IDataSourceResolver DataSourceResolver DataSourceService DataSourceKey GetModuleName DataSources DataSource"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#two-model-finalizing-conventions","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Two model-finalizing conventions","x":"The base context adds both of its conventions in ConfigureConventions (ApplicationDbContext.cs:282-297), and each exists because a cross-cutting policy above would otherwise…","i":"CrossDataSourceDegradeConvention SoftDeleteUniqueIndexConvention IndexBuilderExtensions ConfigureConventions INavigationPopulator HasSoftDeleteFilter SoftDeleteFilterSql IndexBuilder extension IsDeleted TEntity Build"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#entity-configuration-and-engine-portability","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Entity configuration and engine portability","x":"Concrete entity configurations derive from the engine-aware EntityTypeConfiguration…","i":"DefaultEntityConfigurationAssemblyProvider IEntityConfigurationAssemblyProvider IEntityTypeConfigurationSQLServer NullableEnumerationValueConverter NullablePhoneNumberValueConverter EntityTypeConfigurationSQLServer IEntityTypeConfigurationCosmos IEntityTypeConfigurationSqlite EntityTypeConfigurationCosmos EntityTypeConfigurationSqlite PushNotificationConfiguration UserNotificationConfiguration"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#encryption-seeding-design-time-and-the-shared-helpers","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Encryption, seeding, design time, and the shared helpers","x":"A handful of supporting pieces round out the EF side. EncryptedStringConverter…","i":"PaymentReconciliationService IDesignTimeDbContextFactory DesignTimeDbContextOptions IdentityModuleDbSeederBase DesignTimeDbContextHelper NullDomainEventDispatcher PeriodicBackgroundService EncryptedStringConverter EntityDataSourceRegistry ExplicitAssemblyProvider EFQueryableExecutor DataSourceResolver"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#blobs-images-and-native-push","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Blobs, images, and native push","x":"The group also carries the storage-adjacent infrastructure services that are not EF at all, each behind an Application-layer interface with a null default so a host that has not…","i":"AzureNotificationHubNativePushSender AzureNotificationHubDeviceRegistrar AzureBlobFileStorageService ImageSharpImageProcessor NullPushDeviceRegistrar NullFileStorageService IPushDeviceRegistrar NullNativePushSender IFileStorageService ImageContentSniffer NativePushPayloads INativePushSender"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#where-this-group-sits","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Where this group sits","x":"Persistence is the concrete floor the abstract domain stands on. The entity bases and audit contracts from Group 02 are what the interceptors stamp and the query filters hide;…","i":"MMCA.Common.Application.Interfaces.Infrastructure MMCA.Common.Infrastructure.Persistence.AuditTrail MMCA.Common.Infrastructure.Persistence.DbContexts MMCA.Common.Infrastructure.Persistence.Encryption DomainEventSaveChangesInterceptor.DropDeferred EntityTypeConfiguration.ApplyEngineConventions Microsoft.Extensions.Hosting.BackgroundService AddInfrastructure_RegistersIRepositoryFactory CrossTenantWriteException.ForUnresolvedTenant ModelBuilderExtensions.ApplyAllConfigurations DangerousAcceptAnyServerCertificateValidator RelationalEventId.PendingModelChangesWarning"},{"u":"/docs/onboarding/group-08-auth.html","d":"8. Authentication & Authorization","k":"Onboarding Guide","x":"What this group covers. This is the security spine of the framework: how a caller proves who they are (authentication), how the system decides what they may do (authorization),…","i":"SessionCookieAuthenticationHandler PermissionAuthorizationHandler AuthenticationServiceBase AuthenticationValidators ClaimBasedUserIdProvider AuthorizationExtensions ILoginProtectionService IPasswordChangeableUser LoginProtectionSettings CookieSessionRefresher IAuthenticationService LoginProtectionService"},{"u":"/docs/onboarding/group-08-auth.html#tokens-one-signing-switch-two-validation-worlds","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Tokens: one signing switch, two validation worlds","x":"The framework mints two credentials on every successful login: a short-lived access token (a JWT, 15 minutes by default,…","i":"OidcDiscoveryEndpointExtensions OpenIdConnectMetadataWarmupTask GetPrincipalFromExpiredToken ExecutionAndPublication JwksEndpointExtensions RandomNumberGenerator JwtSigningAlgorithm IValidatableObject additionalClaims SigningAlgorithm PublicationOnly RsaJwksProvider"},{"u":"/docs/onboarding/group-08-auth.html#the-shared-authentication-workflow","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"The shared authentication workflow","x":"Login, registration, refresh, and revocation are not re-implemented per app. They live once in AuthenticationServiceBase…","i":"RefreshTokenRequestValidator AuthenticationServiceBase FindUntrackedByEmailAsync AuthenticationValidators OAuthCodeExchangeRequest AuthenticationResponse CancellationToken.None IAuthenticationService AuthenticationRequest AuthenticationService ChangePasswordRequest LoginRequestValidator"},{"u":"/docs/onboarding/group-08-auth.html#what-the-apps-user-aggregate-must-expose","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"What the app's User aggregate must expose","x":"The shared workflows never see an app's User class. They see four small Domain-layer contracts, each sized to one workflow, which is the [Rubric §1, SOLID] interface-segregation…","i":"GetUserPreferencesHandlerBase ChangePreferencesHandlerBase ChangePasswordHandlerBase ChangePreferencesRequest IPasswordChangeableUser UserPreferencesResponse DeleteUserHandlerBase AuditableBaseEntity RevokeRefreshToken UpdateRefreshToken UpdatePreferences IUserPreferences"},{"u":"/docs/onboarding/group-08-auth.html#passwords-and-brute-force-protection","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Passwords and brute-force protection","x":"Password material is handled by PasswordHasher (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Services/PasswordHasher.cs:12), which hashes with PBKDF2-HMAC-SHA512 at 600,000…","i":"CryptographicOperations.FixedTimeEquals ILoginProtectionService LoginProtectionSettings LoginProtectionService IDistributedCache MaxFailedAttempts MaxLockoutSeconds IPasswordHasher PasswordHasher ICacheService Email Range"},{"u":"/docs/onboarding/group-08-auth.html#reading-identity-from-claims","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Reading identity from claims","x":"Once a request is authenticated, downstream code needs the caller's identity without re-parsing the JWT. CurrentUserService…","i":"CultureInfo.InvariantCulture ClaimBasedUserIdProvider IHttpContextAccessor ICurrentUserService CurrentUserService ClaimsPrincipal IUserIdProvider AuthClaimTypes GetClaimValue Clients.User TokenService IsInRole"},{"u":"/docs/onboarding/group-08-auth.html#authorization-roles-permissions-ownership","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Authorization: roles, permissions, ownership","x":"The framework supports three overlapping authorization styles, wired together by the single AddAuthorizationPolicies() extension in AuthorizationExtensions…","i":"PermissionAuthorizationHandler AllowMissingOwnerAttribute OwnerOrAdminFilterOptions PermissionRegistryBuilder AddAuthorizationPolicies PermissionPolicyProvider AuthorizationExtensions HasPermissionAttribute AuthorizationPolicies PermissionRequirement RequireAuthenticated IPermissionRegistry"},{"u":"/docs/onboarding/group-08-auth.html#session-cookies-keeping-ssr-authenticated","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Session cookies: keeping SSR authenticated","x":"The final cluster solves a Blazor-specific problem: an interactive Blazor app keeps its access token in browser memory, but a cold server-side render (a new tab, an F5, an…","i":"CookieSessionRefreshMiddlewareExtensions SessionCookieAuthenticationExtensions SessionCookieAuthenticationHandler CookieSessionRefreshMiddleware ICookieSessionRefresher CookieSessionRefresher SessionCookieEndpoints KeyedSemaphoreStripe SessionCookieRequest SessionTokenResponse SessionTokenResult CookieTokenReader"},{"u":"/docs/onboarding/group-08-auth.html#privacy-the-data-subject-export-package","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Privacy: the data-subject export package","x":"Three members of this group belong to the privacy surface that sits beside erasure. UserDataExportDTO (MMCA.Common/Source/Core/MMCA.Common.Shared/Privacy/UserDataExportDTO.cs:15)…","i":"ExportUserDataHandlerBase DataExportControllerBase UserDataExportSectionDTO IUserDataExportSection Privacy.DataExport UserDataExportDTO PrivacyFeatures FormatVersion FeatureGate Authorize Available Subject"},{"u":"/docs/onboarding/group-08-auth.html#shared-primitives-and-adjacent-members","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Shared primitives and adjacent members","x":"Four group members are general-purpose primitives that landed in this chapter because of how the dependency grouping fell, though one of them is now load-bearing for auth.…","i":"MMCA.Common.Application.Interfaces.Infrastructure AuthorizationExtensions.AddAuthorizationPolicies context.ActionDescriptor.EndpointMetadata.OfType Microsoft.AspNetCore.Http.IHttpContextAccessor JsonWebKeyConverter.ConvertFromRSASecurityKey SessionCookieAuthenticationHandler.SchemeName Microsoft.AspNetCore.SignalR.IUserIdProvider Microsoft.IdentityModel.Tokens.JsonWebKeySet services.AddValidatorsFromAssemblyContaining ArgumentException.ThrowIfNullOrWhiteSpace CookieTokenReader.FreshAccessTokenItemKey ICookieSessionRefresher.GetOrRefreshAsync"},{"u":"/docs/onboarding/group-09-caching.html","d":"9. Caching","k":"Onboarding Guide","x":"What this group covers. Caching in this codebase is small, deliberate, and woven into the CQRS pipeline rather than scattered across handlers. The group is eight types: one port…","i":"Microsoft.Extensions.Caching.Hybrid.HybridCache HybridCacheEntryFlags.DisableUnderlyingData StackExchange.Redis.IConnectionMultiplexer Microsoft.Extensions.Options.IOptions MMCA.Common.Application.Interfaces MMCA.Common.Infrastructure.Caching DistributedCacheServiceRedisTests connectionMultiplexer.GetServers AbsoluteExpirationRelativeToNow PublicEndpointOutputCachePolicy System.Text.Json.JsonSerializer LogPrefixEvictionNoMultiplexer"},{"u":"/docs/onboarding/group-10-notifications.html","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","x":"What this group covers. This is the notification subsystem, the machinery that turns \"an organizer wants to tell every attendee something\" into messages that actually reach…","i":"INotificationRecipientProvider NullPushNotificationSender NullLiveChannelPublisher IPushNotificationSender ILiveChannelPublisher NotificationModule DevicesController INativePushSender UserNotification NotificationHub SmtpEmailSender IEmailSender"},{"u":"/docs/onboarding/group-10-notifications.html#the-layering-and-why-the-pieces-sit-where-they-do","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The layering, and why the pieces sit where they do","x":"The dependency flow of the group mirrors the framework's Clean Architecture story ([Rubric §3, Clean Architecture]). The Domain layer holds the two aggregates, PushNotification…","i":"NullNotificationRecipientProvider Notification.PushNotifications SignalRPushNotificationSender SendPushNotificationRequest SignalRLiveChannelPublisher NullPushNotificationSender PushNotificationInvariants DeviceInstallationRequest NullLiveChannelPublisher NotificationsController PushNotificationCreated PushNotificationStatus"},{"u":"/docs/onboarding/group-10-notifications.html#the-broadcast-send-flow-end-to-end","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The broadcast send flow, end to end","x":"Sending a notification is a command-side vertical slice ([Rubric §5, Vertical Slice], [Rubric §6, CQRS & Event-Driven]). An organizer POSTs to NotificationsController, which is…","i":"NotificationFeatures.PushNotifications AttendeeNotificationRecipientProvider AddNotificationApplicationServices NullNotificationRecipientProvider INotificationRecipientProvider PushNotification.NoRecipients unitOfWork.GetReadRepository SendPushNotificationCommand SendPushNotificationHandler PushNotificationDTOMapper IPushNotificationSender NotificationsController"},{"u":"/docs/onboarding/group-10-notifications.html#the-inbox-side","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The inbox side","x":"Reading and acknowledging notifications is the query/command counterpart, served by InboxController under the same feature gate and [Authorize(RequireAuthenticated)], so any user…","i":"GetUnreadNotificationCountQuery MarkAllNotificationsReadCommand MarkNotificationReadCommand MarkNotificationReadHandler ICurrentUserService.UserId GetMyNotificationsHandler UserNotification.NotFound GetMyNotificationsQuery RequireAuthenticated PushNotification UserNotification InboxController"},{"u":"/docs/onboarding/group-10-notifications.html#the-signalr-transport-and-how-it-survives-extraction","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The SignalR transport, and how it survives extraction","x":"NotificationHub is intentionally thin (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Hubs/NotificationHub.cs:16-17): it is [Authorize]d, and beyond ASP.NET's built-in…","i":"LiveChannelPublisherGrpcAdapter services__notification__grpc__0 SignalRPushNotificationSender SignalRLiveChannelPublisher NullLiveChannelPublisher LiveChannelGrpcService ILiveChannelPublisher AddPushNotifications RequireAuthorization _grpc.notification MapNotificationHub NotificationHub"},{"u":"/docs/onboarding/group-10-notifications.html#the-module-host-native-device-registration-and-the-privacy-export","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The module host, native-device registration, and the privacy export","x":"On the ADC side the whole capability is packaged by NotificationModule (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/NotificationModule.cs:15), an IModule that…","i":"UserNotificationExportServiceGrpcAdapter DisabledUserNotificationExportService UserNotificationExportGrpcService IUserNotificationExportService UserNotificationExportItemDTO UserNotificationExportService currentUserService.UserId DeviceInstallationRequest AddNotificationModule IPushDeviceRegistrar RequiresDependencies DependencyInjection"},{"u":"/docs/onboarding/group-10-notifications.html#where-this-group-sits","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"Where this group sits","x":"Upstream, this group depends on the domain building blocks of Group 02 (both aggregates derive from AuditableAggregateRootEntity ), the Result pattern of Group 01, the CQRS…","i":"LiveChannelPushService.LiveChannelPushServiceBase MMCA.Common.Application.Interfaces.Infrastructure MMCA.ADC.Notification.Shared.UserNotifications attendeeQueryService.GetAttendeeUserIdsAsync services.AddNotificationApplicationServices MMCA.Common.API.Controllers.Notifications NotificationHub.ReceiveNotificationMethod PushNotificationInvariants.TitleMaxLength Microsoft.Extensions.DependencyInjection PushNotificationInvariants.BodyMaxLength CommonInvariants.EnsureStringIsNotEmpty pushNotificationSender.SendToUsersAsync"},{"u":"/docs/onboarding/group-11-navigation-populators.html","d":"11. Navigation Metadata & Populators (EF-decoupled eager loading)","k":"Onboarding Guide","x":"EF Core gives you .Include() for eager loading, and for a single SQL Server database that is the right tool. But this codebase is a database-per-service modular monolith…","i":"navigationMetadata.UnsupportedIncludes.Count MMCA.Common.Application.Services.Navigation NavigationLoader.LoadChildrenPropertyAsync NavigationMetadataProvider.BuildIncludes NavigationMetadata.UnsupportedIncludes IDataSourceService.HaveIncludeSupport NavigationLoader.LoadFKPropertyAsync INavigationPopulator.PopulateAsync MMCA.Common.Application.Interfaces DeclarativeNavigationPopulator.cs CrossDataSourceDegradeConvention EntityQueryPipeline.ExecuteAsync"},{"u":"/docs/onboarding/group-12-api-hosting-mapping.html","d":"12. API Hosting, Middleware, Idempotency & DTO/Contract Mapping","k":"Onboarding Guide","x":"What this group covers. This is the ASP.NET Core edge of the framework: the layer that turns an HTTP request into a domain call and turns a Result back into an HTTP response.…","i":"Microsoft.Extensions.Localization.LocalizedString Microsoft.AspNetCore.Http.IProblemDetailsService Microsoft.EntityFrameworkCore.DbUpdateException Microsoft.AspNetCore.Http.IHttpContextAccessor Microsoft.IdentityModel.Tokens.JsonWebKeySet IDbContextFactory.HasPendingMigrationsAsync AuthorizationPolicies.RequireAuthenticated Microsoft.AspNetCore.Authentication.Google ArgumentException.ThrowIfNullOrWhiteSpace ApplicationSettings.DatabaseInitStrategy StatusCodes.Status499ClientClosedRequest StatusCodes.Status500InternalServerError"},{"u":"/docs/onboarding/group-13-grpc-contracts.html","d":"13. gRPC & Inter-Service Contracts","k":"Onboarding Guide","x":"What this chapter is about. Once the ADC modules stopped sharing a process and became four separate service hosts (Identity, Conference, Engagement, Notification), the in-process…","i":"Microsoft.AspNetCore.Http.IHttpContextAccessor ArgumentException.ThrowIfNullOrWhiteSpace Microsoft.Extensions.DependencyInjection ErrorHttpMapping.ErrorTypeToStatusCode AddConferenceSessionValidationClient Microsoft.Extensions.Http.Resilience Microsoft.Extensions.Logging.ILogger ResultGrpcExtensions.ThrowIfFailure ResultGrpcExtensions.ToRpcException Grpc.Core.Interceptors.Interceptor ArgumentNullException.ThrowIfNull ISessionBookmarkValidationService"},{"u":"/docs/onboarding/group-14-module-system-composition.html","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","x":"What this chapter covers. This is the wiring layer, the code that turns a pile of layered assemblies into a running host. It answers three questions a new host author asks: how…","i":"ConnectionStringSettings InProcessDistributedLock PushNotificationSettings UseDataSourceAttribute RedisDistributedLock UseDatabaseAttribute ApplicationSettings DataSourcesSettings DependencyInjection FileStorageSettings PersistenceSettings AuditTrailSettings"},{"u":"/docs/onboarding/group-14-module-system-composition.html#the-module-contract-and-the-boundary-it-creates","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The module contract and the boundary it creates","x":"A module is the unit of cohesion above a feature slice: Conference, Engagement, Identity, Notification. Each one implements IModule…","i":"DisabledSessionBookmarkValidationService DisabledEventLiveValidationService GetSessionBookmarkCountHandler IBookmarkCountService RegisterDisabledStubs RequiresDependencies AddConferenceModule applicationSettings ConferenceModule moduleEnabled Dependencies Register"},{"u":"/docs/onboarding/group-14-module-system-composition.html#discovery-and-kahn-ordered-registration","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Discovery and Kahn-ordered registration","x":"ModuleLoader (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:15) is the engine. Its DiscoverAndRegister comes in two overloads: the short one…","i":"AppDomain.CurrentDomain.GetAssemblies ModulesSettings.IsModuleEnabled ValidateModuleDependencies ValidateRemoteDependencies Activator.CreateInstance IModuleSeeder.SeedAsync RegisterDisabledStubs RegisterEnabledModule RequiresDependencies DisabledModuleNames DiscoverAndRegister RemoteDependencies"},{"u":"/docs/onboarding/group-14-module-system-composition.html#the-two-composition-roots-and-the-ordering-they-enforce","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The two composition roots and the ordering they enforce","x":"Service registration itself lives in two static DependencyInjection classes, each using a C extension(IServiceCollection services) block (see primer §4 for the extension(T)…","i":"ScanModuleApplicationServices FeatureGateCommandDecorator INavigationMetadataProvider AddNativePushNotifications AddApplicationDecorators InProcessDistributedLock AddApplicationProfiling AddAzureBlobFileStorage CommandRequestValidator LoggingCommandDecorator IConnectionMultiplexer IDomainEventDispatcher"},{"u":"/docs/onboarding/group-14-module-system-composition.html#opt-in-platform-features-are-composed-the-same-way","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Opt-in platform features are composed the same way","x":"Four newer capabilities are registered beside the roots rather than inside them, and they share one discipline: registering a feature is not the same as turning it on.…","i":"AuditTrailSaveChangesInterceptor TenantSaveChangesInterceptor AddUserDataExportSection TenancySettingsValidator IUserDataExportSection MMCA.Common.Scheduler AuditTrailEntryDTO AuditTrailSettings ScheduledJobRunner AddInfrastructure BackgroundService ScheduledJobEntry"},{"u":"/docs/onboarding/group-14-module-system-composition.html#assembly-anchors","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Assembly anchors","x":"Several pieces of machinery need a Type whose Assembly identifies a layer: Scrutor's FromAssemblyOf () scans, FluentValidation's AddValidatorsFromAssemblyContaining (), and…","i":"AddValidatorsFromAssemblyContaining AddInfrastructure AssemblyReference AddApplication ClassReference FromAssemblyOf AssemblyName Assembly static class Type"},{"u":"/docs/onboarding/group-14-module-system-composition.html#configuration-binding-the-settings-family","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Configuration binding, the Settings family","x":"Everything a host operator tunes arrives as a strongly-typed settings object bound from an appsettings.json section, each carrying a static readonly string SectionName so the…","i":"TenantDataSourceOverrideSettings EffectiveExcludedPathPrefixes ScheduledJobOverrideSettings IValidatableObject.Validate IConnectionStringSettings IPushNotificationSettings SQLServerConnectionString ConnectionStringSettings EffectiveResolutionOrder PushNotificationSettings TenancySettingsValidator TenantResolutionStrategy"},{"u":"/docs/onboarding/group-14-module-system-composition.html#the-two-routing-attributes","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The two routing attributes","x":"Two attributes, both in MMCA.Common.Infrastructure, both Inherited = true so they ride down a configuration class hierarchy, encode where an entity is stored declaratively: the…","i":"MMCA.Common.Infrastructure EntityDataSourceRegistry UseDataSourceAttribute UseDatabaseAttribute DataSourceResolver DbContextFactory DataSource Inherited Domain true"},{"u":"/docs/onboarding/group-14-module-system-composition.html#shared-user-use-case-bases-composition-in-the-other-direction","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Shared user use-case bases: composition in the other direction","x":"The chapter's last family is composition at the handler level rather than the container level. ADC and Store each own an Identity module, and five of their account use cases had…","i":"UserOwnershipRule.CheckOwnership GetUserPreferencesHandlerBase UserDataExportSectionDefaults ChangePreferencesHandlerBase UserDataExportSectionResult ChangePasswordHandlerBase ExportUserDataHandlerBase ISoftDeletedUserValidator SoftDeletedUserValidator GetUserPreferencesQuery IUserDataExportSection DeleteUserHandlerBase"},{"u":"/docs/onboarding/group-14-module-system-composition.html#end-to-end-one-hosts-boot","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"End-to-end: one host's boot","x":"Reading MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs top to bottom shows the whole chapter cooperating. The host binds and validates ApplicationSettings and…","i":"MMCA.Common.Application.Interfaces.Infrastructure MMCA.Common.Application.Users.UseCases.DeleteUser UserDataExportSectionDefaults.UnavailableReason TenancySettingsValidator.ConnectionStringFor DefaultEntityConfigurationAssemblyProvider ArgumentException.ThrowIfNullOrWhiteSpace InProcessDistributedLock.TryAcquireAsync Microsoft.Extensions.DependencyInjection ScheduledJobRunner.ResolveCronExpression UserDataExportSectionResult.Unavailable UserUseCaseLog.ExportSectionUnavailable DbContextFactory.ResolveTenantOverride"},{"u":"/docs/onboarding/group-15-common-ui-framework.html","d":"15. Common UI Framework (MudBlazor components, theme, base pages)","k":"Onboarding Guide","x":"What this chapter covers. MMCA.Common.UI is the Blazor presentation package, and it is one of the two layers (with Grpc) allowed to reference Shared only (see primer §1). It…","i":"Microsoft.AspNetCore.Components.NavigationManager Microsoft.Extensions.Configuration.IConfiguration Microsoft.AspNetCore.Builder.IApplicationBuilder Microsoft.AspNetCore.Components.DynamicComponent MauiBackNavigationBridge.HandleBackPressedAsync Microsoft.AspNetCore.WebUtilities.QueryHelpers WasmTokenStorageService.GetAccessTokenAsync HttpResilienceDefaults.TotalRequestTimeout ArgumentException.ThrowIfNullOrWhiteSpace CultureInfo.DefaultThreadCurrentUICulture ITokenStorageService.GetAccessTokenAsync Microsoft.Extensions.DependencyInjection"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","x":"This chapter covers the hosting boundary: how the distributed MMCA system is composed and run, locally as a single dotnet run, and in Azure Container Apps (ACA) as a set of…","i":"MMCA.Common.Aspire.Hosting AddServiceDefaults MMCA.Common.Aspire MMCA.Common.Shared MMCA.ADC.AppHost Aspire.Hosting dotnet run"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-orchestrator-declaring-the-resource-graph","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The orchestrator: declaring the resource graph","x":"When you run dotnet run --project Source/Hosting/MMCA.ADC.AppHost, Aspire executes Program.cs top to bottom, building a resource model rather than starting anything immediately.…","i":"LoginProtection__MaxRegistrationsPerIpPerHour ConnectionStrings__SQLServerConnectionString Authentication__JwtBearer__Authority WithE2eRegistrationThrottleLift E2E_LIFT_REGISTRATION_THROTTLE E2eRegistrationsPerIpPerHour __SQLServerConnectionString MMCA.Common.Aspire.Hosting DefaultBrokerResourceName E2E_JWT_PRIVATE_KEY_PEM WithSQLServerDataSource E2E_JWT_PUBLIC_KEY_PEM"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#startup-ordering-and-the-grpc-deadlock-avoidance-trick","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Startup ordering and the gRPC deadlock-avoidance trick","x":"Aspire's WaitFor builds a startup dependency graph from the resource model: a service does not start until the resources it waits on report healthy (/health/ready for projects,…","i":"ISessionBookmarkValidationService IBookmarkCountService AddTypedGrpcClient WaitFor"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-service-baseline-addservicedefaults","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The service baseline: AddServiceDefaults()","x":"Every running host calls one method first in Program.cs: AddServiceDefaults() from the single framework-grade Extensions in MMCA.Common.Aspire (Level 2,…","i":"EnableMultipleHttp2Connections ConfigureHttpClientDefaults AddDefaultHealthChecks ConfigureOpenTelemetry AddServiceDiscovery AddServiceDefaults AddWarmupReadiness MMCA.Common.Aspire SocketsHttpHandler HttpClient Program.cs TBuilder"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#one-source-of-truth-for-outbound-http-httpresiliencedefaults","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"One source of truth for outbound HTTP: HttpResilienceDefaults","x":"The numbers that pipeline uses are not literals in the Aspire package. They live in HttpResilienceDefaults (Level 0,…","i":"MMCA.Common.Grpc Continuity properties Resilience including Business Concerns lifetime sampling attempt initial request"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#listeners-and-probes-one-kestrel-profile-per-host-shape","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Listeners and probes: one Kestrel profile per host shape","x":"Before a service can answer a health probe it has to be listening on a protocol the prober speaks, and that is not free when the same port serves inbound gRPC.…","i":"redeclareCleartextEndpoint ASPNETCORE_HTTP_PORTS HttpProtocols.Http2 MapDefaultEndpoints BuildListenerPlan HTTP_1_1_REQUIRED Http1AndHttp2 Deployment Protocols deployed profiles httpGet"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#health-checks-liveness-readiness-and-the-optional-tag","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Health checks: liveness, readiness, and the \"optional\" tag","x":"MapDefaultEndpoints() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:329) exposes the three-probe surface the platform reads: /health (every check, for humans and…","i":"AddInfrastructureHealthChecks AddDefaultHealthChecks MapDefaultEndpoints requireSqlServer Observability Operability Deployment optional Optional DevOps Rubric Ready"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#telemetry-what-gets-exported-and-what-it-costs","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Telemetry: what gets exported, and what it costs","x":"ConfigureOpenTelemetry() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:121) wires logging with formatted messages and scopes (:123-127), metrics, and tracing. It…","i":"APPLICATIONINSIGHTS_CONNECTION_STRING ActivityTraceFlags.Recorded OTEL_EXPORTER_OTLP_ENDPOINT AddOpenTelemetryExporters IsInstrumentationDisabled TraceIdRatioBasedSampler MMCA.Common.Idempotency ConfigureOpenTelemetry TryGetTraceSampleRatio MMCA.Common.Scheduler MMCA.Common.Outbox ParentBasedSampler"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#warm-up-defeating-aca-cold-start","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Warm-up: defeating ACA cold-start","x":"The warm-up subsystem exists for one concrete failure mode: the \"first request fails, second succeeds\" pattern on a CPU-throttled idle ACA replica, where lazy initialization…","i":"RequireSuccessStatusCode HealthCheckTags.Ready WebApplicationFactory Interlocked.Exchange RequestVersionPolicy AddServiceDefaults AddWarmupReadiness ApplicationStarted IHttpClientFactory BackgroundService ResolveWarmupPort WithJwksDiscovery"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#configuration-secrets-the-vault-as-one-more-configuration-source","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Configuration secrets: the vault as one more configuration source","x":"Connection strings, RSA signing keys and OAuth client secrets have to reach the process somehow, and KeyVaultConfigurationExtensions (Level 0,…","i":"AddCommonDataProtection DefaultAzureCredential builder.Configuration ConfigurationManager AddServiceDefaults IConfiguration Deployment Security answer DevOps Rubric the"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#security-headers-cors-and-the-shared-key-ring-at-the-host-edge","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Security headers, CORS, and the shared key ring at the host edge","x":"The last boundary in this group hardens the request and response edge. The shared response-header middleware is defined entirely in…","i":"AddCommonSecurityHeaders UseCommonSecurityHeaders AddCommonDataProtection DefaultAzureCredential AddCommonGatewayCors AddCommonBlazorCsp PermissionsPolicy MMCA.Common.API TryAddSingleton ReferrerPolicy AddCommonCors FrameOptions"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#how-it-all-fits-at-runtime","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"How it all fits at runtime","x":"Putting the pieces in sequence: the AppHost declares the graph and injects per-service env vars (WithSQLServerDataSource, WithBroker, WithJwksDiscovery, the two E2E helpers, and…","i":"Azure.Extensions.AspNetCore.Configuration.Secrets identityService.WithE2eRegistrationThrottleLift LoginProtection__MaxRegistrationsPerIpPerHour ConnectionStrings__SQLServerConnectionString database.Resource.ConnectionStringExpression ResilienceCircuitBreakerFaultInjectionTests WarmupReadinessHealthCheck.CheckHealthAsync HttpKeepAlivePingPolicy.WithActiveRequests cancellationToken.IsCancellationRequested ConnectionStrings__CosmosConnectionString ConnectionStrings__SqliteConnectionString Microsoft.AspNetCore.Server.Kestrel.Core"},{"u":"/docs/onboarding/group-17-conference-domain.html","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","x":"What this chapter covers. This is the heart of the Atlanta Developers Conference application, the Conference bounded context, the largest and richest domain in MMCA.ADC. It…","i":"AuditableAggregateRootEntity MMCA.ADC.Conference.Shared IdValueGeneratedAttribute INavigationPopulator EntityChangedEvent DomainEntityState TIdentifierType IAuditedEntity IModule TEntity Design Result"},{"u":"/docs/onboarding/group-17-conference-domain.html#two-packages-one-bounded-context","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Two packages, one bounded context","x":"The Conference context spans two of the module's projects, and the split is deliberate Clean Architecture ([Rubric §3, Clean Architecture]). MMCA.ADC.Conference.Domain holds the…","i":"ISessionBookmarkValidationService IEventLiveValidationService MMCA.ADC.Conference.Domain MMCA.ADC.Conference.Shared SessionFeedbackSubmitted SpeakerUnlinkedFromUser EventFeedbackSubmitted SpeakerLinkedToUser MMCA.Common.Domain AssemblyReference ClassReference Architecture"},{"u":"/docs/onboarding/group-17-conference-domain.html#seven-aggregates-and-their-ownership-boundaries","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Seven aggregates and their ownership boundaries","x":"An aggregate is a root entity plus the children it exclusively owns; invariants are enforced inside the boundary, and references across aggregates are by ID, never by object…","i":"AuditableAggregateRootEntity RecordSessionizeRefresh SessionQuestionAnswer SpeakerQuestionAnswer EventQuestionAnswer SessionCategoryItem SpeakerCategoryItem IDENTITY_INSERT TIdentifierType IAuditedEntity QuestionEntity QuestionSource"},{"u":"/docs/onboarding/group-17-conference-domain.html#the-aggregate-shape-taught-once","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"The aggregate shape, taught once","x":"Open any of the roots and you will see the same skeleton; this repetition is the point, and it is what makes the per-type sections that follow read quickly. The shape, using…","i":"Session.SessionQuestionAnswers Event.EventQuestionAnswers IReadOnlyCollection RestoreEventSpeaker isIdValueGenerated _rooms.AsReadOnly Result.Combine Architecture IsCollection base.Delete Performance RestoreRoom"},{"u":"/docs/onboarding/group-17-conference-domain.html#invariants-business-rules-as-testable-units","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Invariants, business rules as testable units","x":"Each aggregate has a co-located static invariant class, EventInvariants (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:10),…","i":"System.Net.Mail.MailAddress CategoryInvariants QuestionInvariants SessionInvariants SpeakerInvariants SponsorInvariants CommonInvariants EventInvariants SessionStatuses Result.Combine Decline_Queue Accept_Queue"},{"u":"/docs/onboarding/group-17-conference-domain.html#domain-events-and-the-outbox-spine","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Domain events and the outbox spine","x":"Every state-changing method raises a domain event through the inherited AddDomainEvent(...). The events come in two shapes. The aggregate-level ones, EventChanged,…","i":"SessionCategoryItemChanged SpeakerCategoryItemChanged SessionSpeakerChanged PreviousLinkedUserId CategoryItemChanged EventSpeakerChanged EntityChangedEvent DomainEntityState SaveChangesAsync CategoryChanged QuestionChanged TIdentifierType"},{"u":"/docs/onboarding/group-17-conference-domain.html#the-cross-aggregate-cascade-a-pure-domain-service","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"The cross-aggregate cascade: a pure domain service","x":"One business rule cannot live inside a single aggregate: deleting an Event must also delete every Session belonging to it (BR-127) and every Sponsor sold against it, but sessions…","i":"IEventCascadeDeletionDomainService EventCascadeDeletionDomainService EventId Session Sponsor Design Rubric Event List"},{"u":"/docs/onboarding/group-17-conference-domain.html#read-models-and-the-ai-decision-support-feature","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Read models and the AI decision-support feature","x":"The largest cluster in Conference.Shared is the DTO layer, the wire contracts that decouple the API from the domain entities ([Rubric §9, API & Contract Design]; ADR-001 chose…","i":"RefreshFromSessionizeResultDTO RefreshFromSessionizeCommand SessionSelectionDashboardDTO ScoreEventSessionsResultDTO CategoryGroupDistribution Conference.Infrastructure CategoryItemDistribution SessionQuestionAnswerDTO SpeakerQuestionAnswerDTO SpeakerSessionOverlapDTO CategoryDistributionDTO ConcurrencyTokenRequest"},{"u":"/docs/onboarding/group-17-conference-domain.html#authorization-vocabulary-and-current-event-selection","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Authorization vocabulary and current-event selection","x":"Two more Shared helpers deserve a mention because they encode policy the whole module relies on. ConferencePermissions…","i":"TimeZoneInfo.ConvertTimeToUtc ConferenceReadAudience ConferencePermissions CurrentEventDefaults CurrentEventSelector ContentManagement ContentEditor HasPermission Organizer RoleNames StartDate EventDTO"},{"u":"/docs/onboarding/group-17-conference-domain.html#crossing-the-module-boundary-contracts-stubs-and-integration-events","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Crossing the module boundary: contracts, stubs, and integration events","x":"Conference does not live alone. Three kinds of connection point join it to other modules, and all live in Conference.Shared so neither side reaches into the other's domain…","i":"DisabledSessionBookmarkValidationService DisabledEventLiveValidationService ISessionBookmarkValidationService IEventLiveValidationService QuestionModerationDefault SessionFeedbackSubmitted SpeakerUnlinkedFromUser Conference.Application EventFeedbackSubmitted BaseIntegrationEvent User.LinkedSpeakerId SpeakerLinkedToUser"},{"u":"/docs/onboarding/group-17-conference-domain.html#end-to-end-one-organizer-action","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"End-to-end: one organizer action","x":"To see the chapter cooperate, follow an organizer renaming a room on an event. The application handler loads the Event aggregate (with its Rooms hydrated by the navigation…","i":"CategoryInvariants.EnsureCategoryItemNameIsUnique IEventLiveValidationService.GetEventLiveInfoAsync MMCA.ADC.Conference.Domain.Questions.DomainEvents MMCA.ADC.Conference.Domain.Sessions.DomainEvents MMCA.ADC.Conference.Domain.Speakers.DomainEvents MMCA.ADC.Conference.Domain.Sponsors.DomainEvents IUserSpeakerLinkService.ClearLinkedSpeakerAsync MMCA.ADC.Conference.Domain.Events.DomainEvents SessionInvariants.EnsureAnswerValueIsValid SpeakerInvariants.EnsureAnswerValueIsValid CurrentEventSelector.SelectCurrentOrNext DisabledSessionBookmarkValidationService"},{"u":"/docs/onboarding/group-18-conference-application.html","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","x":"What this chapter covers. This is the application layer of the Conference module, the largest single application assembly in the codebase (this group covers 251 distinct types).…","i":"MMCA.Common.Application ics"},{"u":"/docs/onboarding/group-18-conference-application.html#the-vertical-slice-anatomy-of-a-use-case","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"The vertical-slice anatomy of a use case","x":"Open any feature folder under Sessions/UseCases/, Events/UseCases/, Speakers/UseCases/, Sponsors/UseCases/, Categories/UseCases/, or Questions/UseCases/ and you will find the…","i":"ValidateRoomAssignmentAsync BuildOverlapPredicate EventQuestionAnswers UnprocessableEntity EventSpeakers int.MinValue HandleAsync s.StartsAt DbContext s.EndsAt startsAt Session"},{"u":"/docs/onboarding/group-18-conference-application.html#manual-mapping-validation-rule-fragments-and-authorization-specifications","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Manual mapping, validation rule fragments, and authorization specifications","x":"Three sibling families recur across every aggregate. DTO mappers (SessionDTOMapper, EventDTOMapper, SpeakerDTOMapper, SponsorDTOMapper, RoomDTOMapper, CategoryItemDTOMapper, and…","i":"TimeZoneInfo.FindSystemTimeZoneById s.Event.IsPublished AbstractValidator GetProjectedAsync GetReadRepository Session.EventId SessionSpeaker e.IsPublished EventSpeaker Expression IsEligible StartDate"},{"u":"/docs/onboarding/group-18-conference-application.html#query-services-navigation-populators-and-the-composition-root","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Query services, navigation populators, and the composition root","x":"Read paths do not get bespoke handlers for the common cases; they go through the framework's generic IEntityQueryService , which supplies filtering, sorting, paging, and field…","i":"ScanModuleApplicationServices IServiceCollection ClassReference extension FirstName FullName LastName Question Sponsor"},{"u":"/docs/onboarding/group-18-conference-application.html#event-driven-reactions-domain-and-integration-handlers","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Event-driven reactions: domain and integration handlers","x":"The application layer is also where the module reacts to events. Domain event handlers implement IDomainEventHandler and run in-process after the aggregate's SaveChangesAsync.…","i":"EnsureNotServiceSession SpeakerUnlinkedFromUser EnsureStatusIsEligible User.LinkedSpeakerId SpeakerLinkedToUser GetLiveWindowUtc SaveChangesAsync SessionChanged UserRegistered LogAndRethrow IEventBus Deleted"},{"u":"/docs/onboarding/group-18-conference-application.html#attendee-facing-read-models-calendar-export-and-nownext","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Attendee-facing read models: calendar export and Now/Next","x":"A small cluster of queries serves the public schedule surfaces without going through the generic query service, because their output is not a DTO list. ExportEventCalendarHandler…","i":"CalendarExportMapper.IsExportable DateTimeOffset.UtcNow GetNowNextHandler GetLiveWindowUtc Error.NotFound IsExportable TimeProvider DTSTAMP Result string ics"},{"u":"/docs/onboarding/group-18-conference-application.html#the-sessionize-import-strategy-pattern-orchestration","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"The Sessionize import: Strategy-pattern orchestration","x":"The single most involved use case is importing a conference agenda from Sessionize.com. Sessionize returns one JSON payload covering five interdependent entity families…","i":"ThrowIfCancellationRequested TimeoutRejectedException BrokenCircuitException NotSupportedException RequestIdentityInsert HttpRequestException SaveChangesAsync JsonException TimeProvider Create Update catch"},{"u":"/docs/onboarding/group-18-conference-application.html#decision-support-ai-scoring-and-content-analytics","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Decision support: AI scoring and content analytics","x":"The last cluster is session-selection decision support, analytics that help organizers triage proposals. GetSessionSelectionDashboardHandler is a composite query: it validates…","i":"MMCA.ADC.Conference.Application.Events.Sessionize MMCA.ADC.Conference.Application.Events.Validation SessionRoomScheduling.ValidateRoomAssignmentAsync currentUserService.IsPrivilegedConferenceReader eventCascadeDeletionDomainService.CascadeDelete IUserSpeakerLinkService.ClearLinkedSpeakerAsync MMCA.ADC.Conference.Application.Categories.DTOs MMCA.ADC.Engagement.Shared.UserSessionBookmarks PublicSessionStatusSpecification.StatusCriteria SessionSimilarityCalculator.CalculateSimilarity cancellationToken.ThrowIfCancellationRequested EventInvariants.OrganizerContactEmailMaxLength"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","x":"What this chapter covers. This is the adapter layer of the Conference module, the place where the engine-agnostic domain meets concrete technology. Three concerns live here: (1)…","i":"SessionScoringQueue ISessionizeService IAiScoringService Architecture DbContext Rubric Clean DbSet"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#engine-agnostic-entities-engine-chosen-by-the-config-base-class","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Engine-agnostic entities, engine chosen by the config base class","x":"The most important idea in this chapter is one the entities themselves never express: what storage engine each entity uses is decided here, not in the domain. A Conference domain…","i":"EntityTypeConfigurationSQLServer EntityDataSourceRegistry EntityTypeConfiguration DataSource.SQLServer SessionConfiguration SpeakerConfiguration SponsorConfiguration EventConfiguration TIdentifierType UseDataSource Session Speaker"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#each-config-inherits-the-cross-cutting-behavior-then-adds-entity-specifics","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Each config inherits the cross-cutting behavior, then adds entity specifics","x":"Every configuration's Configure method begins with base.Configure(builder) (for example SessionConfiguration.cs:18) and then adds its own mappings. That one base call is where…","i":"SessionQuestionAnswerConfiguration SpeakerQuestionAnswerConfiguration EventQuestionAnswerConfiguration SessionCategoryItemConfiguration SessionInvariants.TitleMaxLength SpeakerCategoryItemConfiguration ConferenceCategoryConfiguration SoftDeleteUniqueIndexConvention SponsorInvariants.NameMaxLength EventInvariants.NameMaxLength EventQuestionAnswer.EventId NullableEmailValueConverter"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#dbsets-the-context-shape-and-how-the-configurations-are-actually-found","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"DbSets, the context shape, and how the configurations are actually found","x":"ModuleApplicationDbContext (MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:19) is the Conference module's abstract DbContext. It does one…","i":"IEntityTypeConfigurationSQLServer MMCA.ADC.Conference.Service ModuleApplicationDbContext SessionQuestionAnswers SpeakerQuestionAnswer ApplicationDbContext EventQuestionAnswers SessionCategoryItems SpeakerCategoryItems dbo.OutboxMessages SQLServerDbContext SaveChangesAsync"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#seeding-two-real-events-always-sample-data-only-in-dev-and-ci","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Seeding: two real events always, sample data only in dev and CI","x":"ConferenceModuleDbSeeder (MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:24) derives from the framework's DbSeeder and runs after…","i":"ConferenceModuleDbSeeder ConferenceModuleSeeder ManualIdRangeStart QuestionInvariants includeSampleData SessionInvariants ExistsAsync DbSeeder sf1nopko z1ecmzux Migrate"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#the-sessionize-adapter","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"The Sessionize adapter","x":"SessionizeService (MMCA.ADC.Conference.Infrastructure/Services/SessionizeService.cs:10) is a deliberately thin HTTP client: the whole class is one method. Given a Sessionize…","i":"EnsureSuccessStatusCode DependencyInjection SessionizeResponse SessionizeService HttpClient GetAsync code"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#the-anthropic-ai-scoring-adapter","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"The Anthropic AI scoring adapter","x":"AnthropicScoringService (MMCA.ADC.Conference.Infrastructure/Services/AnthropicScoringService.cs:16) is the richer of the two adapters: it scores one session proposal against a…","i":"CultureInfo.InvariantCulture OperationCanceledException AnthropicScoringService AnthropicContentBlock SessionScoringResult AnthropicResponse IAiScoringService AnthropicMessage AnthropicRequest JsonPropertyName AiScoreResponse LoggerMessage"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#scoring-runs-on-a-hosted-drain-guarded-across-replicas","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Scoring runs on a hosted drain, guarded across replicas","x":"SessionScoringProcessor (MMCA.ADC.Conference.Infrastructure/Services/SessionScoringProcessor.cs:49) is the piece that makes a multi-minute paid AI pass safe to trigger from an…","i":"MMCA.ADC.Conference.Scoring scoring.run.failed.terminal ScoreEventSessionsCommand SessionScoringProcessor queue.MarkCompleted SessionScoringQueue BackgroundService CreateAsyncScope IDistributedLock TryAcquireAsync conferenceApp MarkCompleted"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#di-wiring-and-a-deliberate-resilience-override","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"DI wiring and a deliberate resilience override","x":"DependencyInjection (MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:11) is a single extension(IServiceCollection) block (the codebase's standard DI-registration idiom,…","i":"AddModuleConferenceInfrastructure RemoveAllResilienceHandlers StandardResilienceHandler DependencyInjection HttpClient.Timeout IServiceCollection extension"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#how-it-fits-together-at-runtime","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"How it fits together at runtime","x":"Three flows tie the chapter together. Persistence flow: a Conference command handler mutates an aggregate and the unit of work saves; that resolves the concrete…","i":"Microsoft.EntityFrameworkCore.Metadata.Builders System.Text.Json.Serialization.JsonPropertyName CategoryInvariants.CategoryItemNameMaxLength MMCA.ADC.Conference.Infrastructure.Services AnthropicScoringService.ScoreSessionAsync AnthropicScoringService.ParseSingleScore Microsoft.Extensions.DependencyInjection MMCA.ADC.Migrations.SqlServer.Conference ApplyConfigurationsForEntitiesInContext SessionInvariants.AnswerValueMaxLength SpeakerInvariants.AnswerValueMaxLength QuestionInvariants.ManualIdRangeStart"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","x":"This chapter is the edge of the Conference bounded context, the layer that turns the rich Conference domain (G17) and its CQRS slices (G18) into a running HTTP + gRPC surface,…","i":"MMCA.ADC.Conference.Contracts MMCA.ADC.Conference.Service MMCA.ADC.Conference.API ConferenceModuleSeeder ConferenceModule Microservices Readiness Contract Vertical IModule Design Rubric"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-controller-hierarchy-almost-everything-is-inherited","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The controller hierarchy, almost everything is inherited","x":"The Conference API exposes sixteen controllers, and the striking thing about them is how little code each carries. They split into three structural families, all built on the…","i":"sessionquestionanswerscontroller conferencecategoriescontroller ConferenceCategoriesController eventquestionanswerscontroller sessioncategoryitemscontroller speakercategoryitemscontroller SessionSelectionController sessionspeakerscontroller categoryitemscontroller eventspeakerscontroller PagedCollectionResult ServiceInfoController"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#authorization-at-the-edge-three-shapes-not-one","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Authorization at the edge, three shapes not one","x":"Authorization is capability-based by default but not uniform, and the differences are the interesting part. Most write-bearing controllers carry a class-level…","i":"AuthorizationPolicies.RequireAuthenticated ConferencePermissions.SpeakersManage SessionQuestionAnswersController EventQuestionAnswersController CurrentUserServiceExtensions IsPrivilegedConferenceReader AddModuleConferenceAPI ConferenceReadAudience HasPermissionAttribute SessionSelectionManage ConferencePermissions ICurrentUserService"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-request-records-the-inbound-write-shapes","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The request records, the inbound write shapes","x":"Several controllers declare small record class request types alongside themselves, co-located in the same file: AddRoomRequest/UpdateRoomRequest…","i":"updatesessionquestionanswerrequest updateeventquestionanswerrequest addsessionquestionanswerrequest addeventquestionanswerrequest addsessioncategoryitemrequest addspeakercategoryitemrequest updatecategoryitemrequest addsessionspeakerrequest addcategoryitemrequest addeventspeakerrequest SessionCreateRequest SessionsController"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#where-the-generic-shape-gives-way-filtering-warnings-and-calendars","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Where the generic shape gives way: filtering, warnings, and calendars","x":"SessionsController is the best illustration of how a controller earns its overrides. Every read action is [AllowAnonymous] and [OutputCache(PolicyName = \"SessionsCache\")]…","i":"BuildPublicSessionSpecificationAsync BuildPagedSessionSpecificationAsync GetSessionsBySpeakerFilterQuery GetPublicSessionFilterQuery GetPublicSponsorFilterQuery PublishedEventSpecification ExportSessionCalendarQuery EvictSessionsCacheAsync UpdateSponsorCommand HasDateRangeWarning IdempotentAttribute IOutputCacheFeature"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#two-more-deviations-versioning-and-decision-support","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Two more deviations, versioning and decision support","x":"ServiceInfoController exists to prove the API-versioning machinery works beyond a single version ([Rubric §9, API & Contract Design]). It is a one-member shell over Common's…","i":"ConferencePermissions.SessionSelectionManage SessionScoringEnqueueResult SessionSelectionController ServiceInfoControllerBase SessionScoringProcessor ServiceInfoController ISessionScoringQueue minimumSimilarity ConferenceCache AllowAnonymous AlreadyPending HandleFailure"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-module-entry-point-and-seeder-how-conference-plugs-in","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The module entry point and seeder, how Conference plugs in","x":"ConferenceModule is the Conference implementation of IModule. It is tiny by design: Register(...) calls the DependencyInjection extension's AddConferenceModule(...)…","i":"DisabledSessionBookmarkValidationService DisabledEventLiveValidationService ISessionBookmarkValidationService IEventLiveValidationService ConferenceErrorResources ConferenceModuleDbSeeder ConferenceModuleSeeder AuthorizationPolicies RegisterDisabledStubs RequireAuthenticated AddConferenceModule DependencyInjection"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-grpc-edge-conference-as-both-server-and-client","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The gRPC edge, Conference as both server and client","x":"When Conference is extracted into its own process, two of its in-process collaborations must cross a network boundary, and both are handled by the G13 transport boundary (Result…","i":"SessionBookmarkValidationServiceGrpcAdapter AddConferenceEventLiveValidationClient eventlivevalidationservicegrpcadapter AddConferenceSessionValidationClient ISessionBookmarkValidationService AddEngagementBookmarkCountClient ModuleLoader.DiscoverAndRegister eventlivevalidationgrpcservice GrpcResultExceptionInterceptor MMCA.ADC.Conference.Contracts MMCA.ADC.Conference.Service SessionBookmarksGrpcService"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-service-host-kestrel-first-and-why","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The service host: Kestrel first, and why","x":"The MMCA.ADC.Conference.Service Program.cs boots only the Conference module (Modules:Conference:Enabled=true). Kestrel is configured before anything else, and the whole of it is…","i":"builder.ConfigureEndpointsWithHealthProbe MMCA.ADC.Conference.Scoring MMCA.ADC.Conference.Service KestrelEndpointExtensions HttpProtocols.Http2 MapDefaultEndpoints HTTP_1_1_REQUIRED Http1AndHttp2 Program.cs UseSerilog httpGet GOAWAY"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#output-caching-and-warm-up-the-two-performance-extension-points","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Output caching and warm-up, the two performance extension points","x":"Output caching is where this host carries the most bespoke configuration (Program.cs:191-255). The base policy is deny-by-default NoCache (Program.cs:198), so only explicitly…","i":"ConferenceReadAudience.PrivilegedRoles PublicEndpointOutputCachePolicy SelfHttpOutputCacheWarmupTask DbUpdateConcurrencyException SessionSelectionController ConferenceErrorResources AddPublicEndpointPolicy SelfHttpWarmupTaskBase ConferencePublicCache BookmarkCountsCache AddErrorResources Event.Name.Empty"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-runtime-picture-one-host-two-transports","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The runtime picture, one host, two transports","x":"After module discovery (Program.cs:313-319) the host wires the Engagement gRPC client (Program.cs:329), the broker (AddBrokerMessaging registering the UserRegistered…","i":"MMCA.Common.Application.Interfaces.Infrastructure currentUserService.IsPrivilegedConferenceReader ConferencePermissions.SessionSelectionManage AuthorizationPolicies.RequireAuthenticated ConferenceReadAudience.PrivilegedRoles.Any builder.ConfigureEndpointsWithHealthProbe DisabledSessionBookmarkValidationService MMCA.ADC.Conference.Shared.Authorization ConferencePermissions.ContentManagement GetPublicSessionCategoryItemFilterQuery GetPublicSpeakerCategoryItemFilterQuery AddConferenceEventLiveValidationClient"},{"u":"/docs/onboarding/group-21-conference-ui.html","d":"21. ADC Conference - UI","k":"Onboarding Guide","x":"What this chapter covers. This is the consumer half of the \"write-once UI, render everywhere\" story (primer §2): the Blazor pages and per-page HTTP services that turn the…","i":"MMCA.ADC.Conference.UI Architecture Responsive Component Design Rubric"},{"u":"/docs/onboarding/group-21-conference-ui.html#the-layering-inside-the-ui-a-page-never-touches-httpclient","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The layering inside the UI: a page never touches HttpClient","x":"Each page is a .razor + .razor.cs code-behind pair that depends only on a UI service interface, never on HttpClient and never on the API's internals. The eight CRUD-shaped…","i":"IConferenceCategoryUIService RefreshFromSessionizeAsync ConferenceCategoryService EnsureSuccessStatusCode ICategoryItemUIService ServiceExceptionHelper PagedCollectionResult SponsorIdentifierType CategoryItemService IQuestionUIService EntityServiceBase ISessionUIService"},{"u":"/docs/onboarding/group-21-conference-ui.html#the-list-pages-derive-from-datagridlistpagebasetdto-get-everything-for-free","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The list pages: derive from DataGridListPageBase, get everything for free","x":"Ten list screens, the organizer EventList, SessionList, SpeakerList, ConferenceCategoryList, QuestionList, RoomList, SponsorList, and the public PublicEventList,…","i":"MobileInfiniteScrollList ConferenceCategoryList DataGridListPageBase PublicSessionList PublicSpeakerList PublicSponsorList FetchMobilePage ListPageActions PublicEventList LoadServerData RestoreFilters GetPagedAsync"},{"u":"/docs/onboarding/group-21-conference-ui.html#container-and-presentational-split","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Container and presentational split","x":"The behaviour-heavy screens do not keep everything in one code-behind: the page stays the container (data fetching, filter and paging state, service calls) and hands rendering to…","i":"SessionSelectionSpeakerOverlap PublicSessionListFilterBar SpeakerCategoryItemsPanel SessionSelectionAiScores SessionSelectionDisplay PublicSessionListView PublicSessionList Architecture ReloadAsync Changed Testing Rubric"},{"u":"/docs/onboarding/group-21-conference-ui.html#child-and-join-entities-a-thin-postdelete-base","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Child-and-join entities: a thin POST/DELETE base","x":"Sessions, speakers, and events own join relationships (a speaker added to a session, a category item to a speaker) that the generic CRUD base cannot model, because the write…","i":"ISessionCategoryItemUIService ISpeakerCategoryItemUIService SessionCategoryItemService SpeakerCategoryItemService ISessionSpeakerUIService ChildEntityServiceBase IEventSpeakerUIService SessionSpeakerService EventSpeakerService MMCA.Common.UI DeleteAsync Validation"},{"u":"/docs/onboarding/group-21-conference-ui.html#display-enrichment-lookups-the-getall-vs-getbyid-populator-gap-worked-around-in-the-ui","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Display-enrichment lookups: the GetAll-vs-GetById populator gap, worked around in the UI","x":"Because the API's list endpoints do not always populate every cross-entity navigation, several pages need a cheap id-to-name map to render speaker names beside a session or an…","i":"ICategoryItemLookupService CategoryItemLookupService ISpeakerLookupService SpeakerLookupService SponsorshipPacketUrl IEventLookupService EventLookupService PublicSessionList CategoryItemInfo SessionSpeakers SpeakerInfo Dictionary"},{"u":"/docs/onboarding/group-21-conference-ui.html#three-feature-areas-that-go-beyond-crud","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Three feature areas that go beyond CRUD","x":"First, the speaker self-service dashboard: SpeakerDashboard is gated on the speakerid JWT claim (read from the cascaded authentication state and parsed as a Guid,…","i":"IOrganizerSessionFeedbackUIService IOrganizerEventFeedbackUIService OrganizerSessionFeedbackService OrganizerEventFeedbackService ISpeakerDashboardUIService AuthenticatedServiceBase OrganizerSessionFeedback SpeakerDashboardService OrganizerEventFeedback ServiceExceptionHelper IPublicLinkBuilder SpeakerDashboard"},{"u":"/docs/onboarding/group-21-conference-ui.html#session-selection-decision-support-the-asynchronous-edge","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Session-selection decision support, the asynchronous edge","x":"The most behaviour-rich page is the organizer-only SessionSelectionDashboard, which renders category distribution, speaker overlap, locality breakdown, and AI content-similarity…","i":"SessionSelectionFilterOptions ScoreEventSessionsResultDTO ISessionSelectionUIService ScoreEventSessionsCommand SessionSelectionDashboard SessionSelectionService CurrentEventSelector ScorePollTracker ScorePollSignal SessionsScored Resilience inherited"},{"u":"/docs/onboarding/group-21-conference-ui.html#public-versus-authenticated-rendering-and-the-device-capability-path","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Public versus authenticated rendering, and the device-capability path","x":"A recurring [Rubric §11, Security] pattern: the same conference entity is exposed through two page families. The public family (PublicEventList/PublicEventDetail,…","i":"IServiceProvider.GetService IConnectivityStatusService ISessionBookmarkUIService ConferenceReadAudience IHapticFeedbackService CurrentEventDefaults PublicSessionDetail PublicSpeakerDetail IScreenshotService CachedSessionPage PublicEventDetail PublicSessionList"},{"u":"/docs/onboarding/group-21-conference-ui.html#sponsors-a-feature-area-in-miniature","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Sponsors, a feature area in miniature","x":"The sponsor surface is worth reading as a compact tour of every pattern above, because it is the newest and touches all of them. Organizers manage the roster through SponsorList…","i":"ADCSponsorCollectionResult DataGridListPageBase EventLookupService PublicSponsorList ADCSponsorInfo Enum.GetValues SponsorCreate SponsorDetail SponsorList SponsorTier SponsorDTO ADCHome"},{"u":"/docs/onboarding/group-21-conference-ui.html#the-landing-page","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The landing page","x":"ADCHome is the conference front door, shared by the web and MAUI heads; both serve the editorial images from their own site root today, so neither overrides the ImageBasePath…","i":"CurrentEventSelector ADCCollectionResult ConferenceTrackInfo KeynoteSpeakerInfo ImageBasePath ADCEventInfo Performance EventPhase Rendering ADCHome Rubric Timer"},{"u":"/docs/onboarding/group-21-conference-ui.html#routes-and-navigation","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Routes and navigation","x":"All paths are centralized in ConferenceRoutePaths, a static catalogue of literal routes and id-parameterized builder methods (EventDetails(id), PublicSessionDetails(id),…","i":"ConferenceRoutePaths.EventDetails NavigationManager.NavigateTo NavigationPublicLinkBuilder EventFeedbackOrganizer ConferenceRoutePaths Internationalization PublicSessionDetails IPublicLinkBuilder IStringLocalizer SponsorVisitLink RoomCheckInLink SponsorDetails"},{"u":"/docs/onboarding/group-21-conference-ui.html#how-it-all-plugs-into-the-shell","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"How it all plugs into the shell","x":"Two registration types wire the area in. ConferenceUIModule implements Common's IUIModule (the front-end counterpart of the IModule back-end contract): it declares the module's…","i":"MMCA.ADC.Conference.UI.Pages.ConferenceCategory ConferenceRoutePaths.SessionSelectionDashboard MMCA.ADC.Conference.UI.Pages.SessionSelection ListPageActions.DeleteWithConfirmationAsync ArgumentException.ThrowIfNullOrWhiteSpace CurrentEventDefaults.SelectCurrentOrNext CurrentEventSelector.SelectCurrentOrNext DashboardService.GetSpeakerSessionsAsync ListPageActions.ReloadActiveLayoutAsync MMCA.ADC.Conference.UI.Pages.Feedback MMCA.ADC.Conference.UI.Pages.Question MMCA.ADC.Conference.UI.Pages.Session"},{"u":"/docs/onboarding/group-22-engagement-module.html","d":"22. ADC Engagement Module (Session Bookmarks)","k":"Onboarding Guide","x":"What this group covers. Engagement is the ADC bounded context that holds everything an attendee does at the conference beyond browsing the catalog. Four capability families live…","i":"MMCA.ADC.Engagement.Application.CheckIns.Services BookmarkCountService.BookmarkCountServiceClient MMCA.ADC.Engagement.Application.Points.Services MMCA.ADC.Engagement.Domain.UserSessionBookmarks MMCA.ADC.Engagement.Shared.UserSessionBookmarks MMCA.ADC.Engagement.Domain.Points.DomainEvents BookmarkCountService.BookmarkCountServiceBase MMCA.ADC.Engagement.Application.CheckIns.DTOs assemblyProvider.GetConfigurationAssemblies AuthorizationPolicies.RequireAuthenticated CheckInsController.GetAttendanceStatsAsync SessionFeedbackSubmittedPointsHandlerTests"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","x":"What this chapter covers. This is the conference-day layer of the Engagement bounded context: the features that only matter while an event is actually happening in the room.…","i":"SessionQuestion PresenterView HappeningNow SessionLive LivePoll"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-two-aggregates-and-their-invariants","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The two aggregates and their invariants","x":"Both aggregates are sealed AuditableAggregateRootEntity subclasses that follow the framework's factory-plus-Result discipline (primer §2). LivePoll…","i":"AuditableAggregateRootEntity SessionQuestion.Create SessionQuestionChanged SessionQuestionUpvote ToggleUpvoteHandler LivePollInvariants DomainEntityState LiveWindowEndUtc BaseDomainEvent CanAcceptUpvote CastVoteHandler LivePollChanged"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-write-path-and-where-the-realtime-broadcast-actually-happens","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The write path, and where the realtime broadcast actually happens","x":"Each operation is a vertical slice under Application/{LivePollsSessionQuestions}/UseCases/{Op}/, and every command handler shares the first two beats: mutate the aggregate…","i":"SessionQuestionUpvoteChangedHandler ILiveChannelPublishQueue.Enqueue SessionQuestionUpvoteChanged LiveChannelPublishWorkItem LivePollVoteChangedHandler ILiveChannelPublishQueue ModerateQuestionHandler SessionQuestionChannel CreateLivePollHandler LivePollClosedPayload SubmitQuestionHandler CloseLivePollHandler"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html#one-websocket-one-publisher-port-and-a-cross-service-ingress","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"One WebSocket, one publisher port, and a cross-service ingress","x":"The transport itself is framework-owned (ADR-039, Group 10). The single NotificationHub carries both durable notifications and channel events on one connection, and the…","i":"LiveChannelPublisherGrpcAdapter LiveChannelPublishProcessor SignalRLiveChannelPublisher RendererInfo.IsInteractive NullLiveChannelPublisher IPushNotificationSender LiveChannelGrpcService NotificationHubService ILiveChannelPublisher OnAfterRenderAsync OnInitializedAsync LeaveChannelAsync"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-read-path-and-how-the-ui-reacts","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The read path and how the UI reacts","x":"Reads do not go through the generic entity-query machinery; the live views need shaped projections. LivePollResultsBuilder…","i":"LivePollNavigationPopulator SessionLiveModerationPanel SessionQuestionViewBuilder SessionLiveQuestionPanel LivePollResultsBuilder ISessionLiveUIService LivePollConfiguration CurrentEventSelector SessionLivePollPanel SessionLiveUIService GetOpenPollsHandler LivePollDTOMapper"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html#authorization-feature-gating-and-the-cross-service-dependency-on-conference","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"Authorization, feature gating, and the cross-service dependency on Conference","x":"Both controllers, LivePollsController (MMCA.ADC.Engagement.API/Controllers/LivePollsController.cs:42) and SessionQuestionsController…","i":"MMCA.ADC.Engagement.Domain.LivePolls.DomainEvents MMCA.ADC.Engagement.Application.LivePolls.DTOs SessionQuestionChannel.QuestionUpvoteChanged MMCA.ADC.Engagement.Domain.SessionQuestions MMCA.ADC.Engagement.Shared.SessionQuestions AuthorizationPolicies.RequireAuthenticated LivePollInvariants.EnsureOptionTextIsValid PushNotificationSettings.ChannelKeyPattern MMCA.ADC.Engagement.UI.Pages.HappeningNow SessionQuestionPendingCountChangedPayload SessionQuestionUpvote.QuestionId.Required CurrentEventSelector.SelectCurrentOrNext"},{"u":"/docs/onboarding/group-24-identity-module.html","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","x":"What this chapter covers. This is the Identity bounded context of MMCA.ADC, the module that owns who a person is across every ADC surface: web, WebAssembly, and MAUI. It is a…","i":"GetUserPreferencesHandlerBase AuditableAggregateRootEntity AuthenticationServiceBase ChangePasswordHandlerBase ExportUserDataHandlerBase HasPermissionAttribute DeleteUserHandlerBase BaseIntegrationEvent SoftDeletedUserCache TIdentifierType IAnonymizable PiiAttribute"},{"u":"/docs/onboarding/group-24-identity-module.html#projects-one-bounded-context","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Projects, one bounded context","x":"The module is split along the standard Clean Architecture layering ([Rubric §3, Clean Architecture]), each project pinned by a trivial AssemblyReference / ClassReference anchor…","i":"MMCA.ADC.Identity.Infrastructure MMCA.ADC.Identity.Application ScanModuleApplicationServices MaxRegistrationsPerIpPerHour MMCA.ADC.Identity.Contracts ModuleApplicationDbContext MMCA.ADC.Identity.Service MMCA.ADC.Identity.Domain MMCA.ADC.Identity.Shared SoftDeletedUserValidator IdentityErrorResources IdentityModuleDbSeeder"},{"u":"/docs/onboarding/group-24-identity-module.html#the-user-aggregate-credentials-profile-and-cross-context-links-in-one-root","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The User aggregate: credentials, profile, and cross-context links in one root","x":"User (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:33) is the only aggregate root in the module, and it carries more responsibility than most: it is…","i":"RegisterRequestValidator IPasswordChangeableUser DeviceFieldMaxLength UserPasswordChanged FirstNameMaxLength RefreshTokenExpiry RevokeRefreshToken UpdateRefreshToken LastNameMaxLength UpdatePreferences UserConfiguration CommonInvariants"},{"u":"/docs/onboarding/group-24-identity-module.html#authentication-a-thin-subclass-over-the-shared-engine","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Authentication: a thin subclass over the shared engine","x":"The login / registration / refresh / revocation workflow is not re-implemented here. It lives in AuthenticationServiceBase (G08), which owns the validate-first flow, the lockout…","i":"HttpContextExternalLoginEmailVerifier UnitOfWork.ExecuteInTransactionAsync CreateChangePreferencesCommand Auth.ExternalEmailNotVerified IdentityPermissions.UsersRead UserAccountAuthControllerBase CreateChangePasswordCommand IExternalLoginEmailVerifier AuthenticationServiceBase GetUserPreferencesHandler TChangePreferencesCommand ChangePreferencesCommand"},{"u":"/docs/onboarding/group-24-identity-module.html#the-privacy-pair-export-and-erasure","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The privacy pair: export and erasure","x":"Two use cases make this module the codebase's clearest [Rubric §30, Compliance / Privacy / Data Governance] story, and both are now thin ADC specializations of a G14 base. The…","i":"UserDataExportEngagementSectionDTO NotificationUserDataExportSection EngagementUserDataExportSection IUserNotificationExportService IUserEngagementExportService BuildSubjectSnapshotAsync ExportUserDataHandlerBase UserDataExportSectionDTO UserDataExportSubjectDTO IUserDataExportSection OnAfterSoftDeleteAsync DeleteUserHandlerBase"},{"u":"/docs/onboarding/group-24-identity-module.html#avatars-the-third-mutating-slice","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Avatars: the third mutating slice","x":"The avatar trio is a small but complete example of a file-handling slice ([Rubric §11, Security] at the content boundary, ADR-045). UsersController caps the multipart upload at 2…","i":"RemoveUserAvatarHandler Avatar.InvalidUpload GetUserAvatarHandler SetUserAvatarHandler IFileStorageService ImageContentSniffer RequestSizeLimit IImageProcessor UsersController MaxAvatarBytes"},{"u":"/docs/onboarding/group-24-identity-module.html#persistence-seeding-and-the-disabled-stub","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Persistence, seeding, and the disabled stub","x":"ModuleApplicationDbContext (ModuleApplicationDbContext.cs:15) is the abstract, engine-agnostic context declaring the single Users set (:22); the concrete per-engine class…","i":"EntityTypeConfigurationSQLServer DisabledAttendeeQueryService IdentityModuleDbSeederBase ModuleApplicationDbContext IdentityModuleDbSeeder RegisterDisabledStubs ApplicationDbContext IdentityModuleSeeder EmailValueConverter dbo.OutboxMessages SQLServerDbContext UserConfiguration"},{"u":"/docs/onboarding/group-24-identity-module.html#crossing-the-service-boundary-grpc-and-integration-events","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Crossing the service boundary: gRPC and integration events","x":"Identity talks to its peers two ways, and both live in Shared and Contracts so neither side reaches into the other's domain ([Rubric §7, Microservices Readiness]). Synchronously,…","i":"ConfigureEndpointsWithHealthProbe ModuleLoader.DiscoverAndRegister AttendeeQueryServiceGrpcAdapter SpeakerUnlinkedFromUserHandler SpeakerLinkedToUserHandler AddIdentityAttendeeClient KestrelEndpointExtensions RequireSuccessStatusCode SpeakerUnlinkedFromUser SelfHttpWarmupTaskBase AuthenticationService IAttendeeQueryService"},{"u":"/docs/onboarding/group-24-identity-module.html#the-ui-edge","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The UI edge","x":"The Blazor surface is registered as an IdentityUIModule (MMCA.ADC.Identity.UI/IdentityUIModule.cs:13), an IUIModule descriptor that contributes two NavItems as resource keys, \"My…","i":"AuthenticatedServiceBase MobileInfiniteScrollList RetryPolicy.ExecuteAsync MMCA.Common.Testing.E2E DataGridListPageBase DependencyInjection IMediaPickerService IdentityRoutePaths IdentityUIModule ListPageActions IUserUIService UserListDTO"},{"u":"/docs/onboarding/group-24-identity-module.html#end-to-end-one-registration","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"End-to-end: one registration","x":"To see the chapter cooperate, follow a new attendee signing up. AuthController receives the register POST, captures the client IP for BR-213 rate limiting (AuthController.cs:57),…","i":"MMCA.ADC.Identity.Shared.Users.IntegrationEvents AttendeeQueryService.AttendeeQueryServiceClient System.Diagnostics.CodeAnalysis.SuppressMessage MMCA.ADC.Identity.Application.Users.Validation AttendeeQueryService.AttendeeQueryServiceBase AuthStateProvider.GetAuthenticationStateAsync LoginProtection__MaxRegistrationsPerIpPerHour ServiceCollectionDescriptorExtensions.Replace ListPageActions.DeleteWithConfirmationAsync MMCA.ADC.Identity.Domain.Users.DomainEvents ExternalAuthExtensions.ExternalLoginScheme System.Collections.Frozen.FrozenDictionary"},{"u":"/docs/onboarding/group-25-adc-host-composition.html","d":"25. ADC Application Host, UI Shell & Cross-Module Composition","k":"Onboarding Guide","x":"What this chapter covers. Every ADC module described so far, Conference, Engagement, Identity, Notification, is consumed somewhere. This chapter is that somewhere: the client…","i":"Microsoft.Extensions.Configuration.IConfiguration ArgumentException.ThrowIfNullOrWhiteSpace NowNextWidgetProvider.FetchSnapshotAsync MMCA.Common.UI.Components.Capabilities IPlatformApplication.Current.Services UIModuleConfiguration.IsModuleEnabled RemoteCertificateValidationCallback SessionCookieAuthenticationHandler EngagementRoutePaths.HappeningNow NowNextWidgetProvider.BuildViews System.Resources.ResourceManager WebAuthenticatorCallbackActivity"},{"u":"/docs/onboarding/group-26-device-capability-layer.html","d":"26. Device Capability Abstraction Layer (Native Contracts, MAUI, Browser & Fallback Adapters)","k":"Onboarding Guide","x":"What this group covers. A single Blazor UI codebase in MMCA.Common.UI renders on three very different heads: Blazor Server (server-side render plus interactive Server circuits),…","i":"MauiBackNavigationBridge.HandleBackPressedAsync MMCA.Common.UI.Services.Capabilities.Fallbacks MMCA.Common.UI.Services.Capabilities.Browser builder.Services.AddMauiDeviceCapabilities WebAuthenticator.Default.AuthenticateAsync ArgumentException.ThrowIfNullOrWhiteSpace Battery.Default.EnergySaverStatusChanged CommunityToolkit.Maui.Media.SpeechToText Connectivity.Current.ConnectivityChanged CultureInfo.DefaultThreadCurrentCulture ILocalNotificationService.ScheduleAsync IPushDeviceTokenProvider.GetTokenAsync"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","x":"What this group covers. Everything the codebase uses to prove itself: the four reusable test-support packages that ship out of MMCA.Common/Source/Hosting (MMCA.Common.Testing,…","i":"ServiceInfoVersioningContractTestsBase SqlServerIntegrationTestFixtureBase MMCA.Common.Testing.Architecture ProductionHostApplicationFactory AccessibilityViolationException DecoratorPipelineOrderTestsBase FeatureManagementTestExtensions ProblemDetailsContractTestsBase CapturingHttpMessageHandler CrossEntityNavigationFinder RouteAuthorizationTestsBase BunitInteractionExtensions"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#integration-tests-a-real-host-a-throwaway-database-a-per-test-reset","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Integration tests: a real host, a throwaway database, a per-test reset","x":"The integration tier boots the actual application, not a mock of it. The abstraction at its center is IIntegrationTestFixture (MMCA.Common.Testing/IIntegrationTestFixture.cs:8):…","i":"SqlServerIntegrationTestFixtureBase ProductionHostApplicationFactory FeatureManagementTestExtensions appsettings.Development.json SqlBaseEnvironmentVariable ConfigureTestFeatureFlags IEntityDataSourceRegistry CrossServiceFixtureBase IIntegrationTestFixture CrossServiceDataSource __EFMigrationsHistory WebApplicationFactory"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#architecture-fitness-functions-rules-that-gate-the-build","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Architecture fitness functions: rules that gate the build","x":"The layering and DDD conventions this codebase commits to are not left to code review, they are executed as tests. The reusable rule library lives in…","i":"ConstructorDependencyCountTestsBase IntegrationEventContractTestsBase MMCA.Common.Testing.Architecture ObservabilityConventionTestsBase AggregateRootsHaveResultFactory MicroserviceExtractionTestsBase RawQueryableConventionTestsBase ArchitectureRules.Entities.cs AggregateConventionTestsBase CrossEntityNavigationFinder DomainExposesAggregateRoots DomainFactoriesReturnResult"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#component-tests-real-mudblazor-faked-edges","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Component tests: real MudBlazor, faked edges","x":"The bUnit tier renders a single Blazor component in-process with its real dependencies but stubbed network and auth. BunitComponentTestBase…","i":"IsAuthenticatedAuthorizationService AuthenticationStateProvider CapturingHttpMessageHandler BunitInteractionExtensions StubTokenStorageService BunitComponentTestBase FreshApiClientFactory MarkupSnapshotResult UiHttpServiceHarness AuthenticationState HttpMessageHandler IRenderedComponent"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#end-to-end-tests-a-real-browser-accessibility-and-performance-as-gates","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"End-to-end tests: a real browser, accessibility and performance as gates","x":"The E2E tier drives a real browser through Playwright. PlaywrightFixture (MMCA.Common.Testing.E2E/Infrastructure/PlaywrightFixture.cs:6) is an xUnit collection fixture (its…","i":"AssertNoAccessibilityViolationsAsync AccessibilityViolationException Wcag21AaExceptMudPagerCombobox ProfileManagementTestsBase GotoAndWaitForBlazorAsync UserRegistrationTestsBase UserPreferencesTestsBase ClickAndWaitForUrlAsync window.Blazor._internal AuthorizationTestsBase WaitForAuthResultAsync AuthenticatedUserPath"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#the-gallery-harness","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"The Gallery harness","x":"Component and E2E coverage of MMCA.Common's own UI needs a page to render, but the framework is not a runnable app. MMCA.Common.UI.Gallery is a deliberately backend-less Blazor…","i":"GalleryAuthenticationStateProvider GalleryFakeAuthenticationHandler StubNotificationInboxUIService StubPushNotificationUIService MMCA.Common.UI.E2E.Tests NullTokenStorageService MMCA.Common.UI.Gallery MapRazorComponents NullTokenRefresher NoOpAuthUIService MMCA.Common.slnx GalleryUIModule"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#contract-pipeline-and-benchmark-bases","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Contract, pipeline, and benchmark bases","x":"The last family pins guarantees that live in the composition of the stack rather than in any one type, and it is the subject of ADR-058: these suites ship in MMCA.Common.Testing…","i":"Application_ShouldNotDependOn_EntityFrameworkCore Controllers_ShouldNotDependOn_EntityFrameworkCore DataSubject_DeclaresPii_SoTheContractIsNotVacuous Module_ShouldDeclare_ExpectedRequiresDependencies PiiRedactor_LeaksNoClearTextPii_ToLogsOrTelemetry CultureSwitch_ToSpanish_ShouldLocalizeAndPersist EveryRunbookAlertSection_MapsToAProvisionedAlert MobileViewport_CultureAndTheme_ShouldBeReachable ModuleShared_ShouldNotDependOn_OwnInternalLayers OpenApiDocument_DescribesEveryCorePublicResource Register_WithMismatchedPasswords_ShouldShowError RegisterPage_ShouldHaveNoAccessibilityViolations"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#per-project-test-rollup","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Per-project test rollup","x":"This guide treats tests as grouped, not sectioned per [Fact] (the logged exception in the charter): the reusable test bases, the shared architecture-fitness library and its…","i":"MMCA.ADC.ServiceBusEmulator.IntegrationTests CachingDecoratorConstructorSelectionTests MMCA.ADC.Conference.Infrastructure.Tests MMCA.ADC.Engagement.Infrastructure.Tests MMCA.ADC.Notification.Application.Tests EntityServiceBaseIdempotencyRetryTests MMCA.ADC.CrossService.IntegrationTests MMCA.ADC.Identity.Infrastructure.Tests MMCA.ADC.Notification.IntegrationTests MMCA.Common.Infrastructure.Redis.Tests UserNotificationExportGrpcServiceTests ApplicationDbContextTenantFilterTests"},{"u":"/docs/onboarding/devops-aspire.html","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","x":"This chapter teaches how the MMCA.ADC system goes from a single dotnet run on your workstation to a running stack of six .NET processes plus four containers: databases, a broker,…","i":"MMCA.Common.Aspire ServiceDefaults WithReference dotnet run"},{"u":"/docs/onboarding/devops-aspire.html#the-one-command-local-run","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The one-command local run","x":"That command brings up everything the application needs locally: four SQL Server databases, Redis, RabbitMQ with management UI, a MailDev SMTP interceptor, four extracted…"},{"u":"/docs/onboarding/devops-aspire.html#mmcaadcapphost-the-orchestration-project","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.ADC.AppHost, the orchestration project","x":"Source file: MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs Extension helpers: MMCA.Common.Aspire.Hosting/Extensions.cs (AddMessageBroker, WithBroker, WithJwksDiscovery,…","i":"identityService.WithE2eRegistrationThrottleLift LoginProtection__MaxRegistrationsPerIpPerHour ConnectionStrings__SQLServerConnectionString ConnectionStrings__CosmosConnectionString ConnectionStrings__SqliteConnectionString Authentication__JwtBearer__Authority identityService.WithEnvironment services__notification__grpc__0 WithE2eRegistrationThrottleLift E2E_LIFT_REGISTRATION_THROTTLE GrpcResultExceptionInterceptor JwtForwardingClientInterceptor"},{"u":"/docs/onboarding/devops-aspire.html#where-service-defaults-come-from-mmcacommonaspire-not-a-local-project","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Where service defaults come from, MMCA.Common.Aspire, not a local project","x":"There is no MMCA.ADC.ServiceDefaults project. The conventional Aspire \"ServiceDefaults\" shared project that scaffolding generates has been deleted; each service host (and the UI)…","i":"AddCommonKeyVaultConfiguration scoring.run.failed.terminal MMCA.ADC.ServiceDefaults AddCommonDataProtection DefaultAzureCredential builder.Configuration AuditTrailCleanupJob ConfigurationManager MapDefaultEndpoints AddServiceDefaults MMCA.Common.Aspire ScheduledJobRunner"},{"u":"/docs/onboarding/devops-aspire.html#mmcacommonaspire-the-framework-service-defaults-package","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.Common.Aspire, the framework service-defaults package","x":"Source: MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs Telemetry: MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Telemetry/OutboxPollFilterProcessor.cs Security:…","i":"APPLICATIONINSIGHTS_CONNECTION_STRING OpenIdConnectMetadataWarmupTask EnableMultipleHttp2Connections AddInfrastructureHealthChecks Services.AddServiceDiscovery Telemetry__TracesSampleRatio ActivityTraceFlags.Recorded ConfigureHttpClientDefaults OTEL_EXPORTER_OTLP_ENDPOINT PooledConnectionIdleTimeout MMCA.Common.Infrastructure WarmupReadinessHealthCheck"},{"u":"/docs/onboarding/devops-aspire.html#mmcacommonaspirehosting-the-apphost-extensions-package","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.Common.Aspire.Hosting, the AppHost extensions package","x":"Source: MMCA.Common/Source/Hosting/MMCA.Common.Aspire.Hosting/Extensions.cs This package lives in a separate assembly from MMCA.Common.Aspire so running services do not pull in…","i":"identityService.WithE2eRegistrationThrottleLift LoginProtection__MaxRegistrationsPerIpPerHour Authentication__JwtBearer__Authority WithE2eRegistrationThrottleLift E2E_LIFT_REGISTRATION_THROTTLE E2eRegistrationsPerIpPerHour builder.AddMessageBroker E2E_JWT_PRIVATE_KEY_PEM WithSQLServerDataSource E2E_JWT_PUBLIC_KEY_PEM Jwks__RsaPublicKeyPem Jwt__RsaPrivateKeyPem"},{"u":"/docs/onboarding/devops-aspire.html#the-six-dockerfiles","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The six Dockerfiles","x":"All six Dockerfiles share the same multi-stage structure (base → build → publish → final) and the same base images. None build the AppHost, it is a local-only orchestration…","i":"MMCA.ADC.Notification.Service.dll MMCA.ADC.Conference.Service.dll MMCA.ADC.Engagement.Service.dll GlobalUsings.IdentifierType.cs MMCA.ADC.Identity.Service.dll MMCA.ADC.UI.Web.Client Directory.Build.props TreatWarningsAsErrors MMCA.ADC.Gateway.dll MMCA.ADC.UI.Web.dll MMCA.Common.Aspire MMCA.ADC.UI.Web"},{"u":"/docs/onboarding/devops-aspire.html#local-to-cloud-parity","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Local-to-cloud parity","x":"The AppHost topology maps directly to the Azure infrastructure provisioned by infra/main.bicep. The table below cross-references the local resource with its Azure equivalent: The…","i":"ConnectionStrings__SQLServerMigrationsAssembly APPLICATIONINSIGHTS_CONNECTION_STRING __SQLServerMigrationsAssembly OTEL_EXPORTER_OTLP_ENDPOINT ConnectionStrings__redis WithSQLServerDataSource Outbox__DatabaseName AddBrokerMessaging MessageBusProvider ADC_Notification AzureServiceBus ADC_Conference"},{"u":"/docs/onboarding/devops-aspire.html#the-yarp-gateways-role","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The YARP Gateway's role","x":"The gateway (Source/Hosts/MMCA.ADC.Gateway) is a pure YARP reverse proxy. It has no DbContext, no ModuleLoader, no REST controllers, and no broker connection. Its Program.cs is…","i":"HttpResilienceDefaults.TotalRequestTimeout notificationRestConfig HttpVersion.Version20 RequestVersionOrLower RequestVersionExact restActivityTimeout ActivityTimeout Http1AndHttp2 VersionPolicy ForwardHttp2 MapForwarder ModuleLoader"},{"u":"/docs/onboarding/devops-aspire.html#startup-ordering-summary","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Startup ordering summary","x":"The health-based WaitFor chain imposes this ordering. Note that three of the four services wait on Identity without any explicit WaitFor in the AppHost: WithJwksDiscovery adds it…","i":"WithJwksDiscovery WithReference WaitFor"},{"u":"/docs/onboarding/devops-aspire.html#not-determinable-from-source","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Not determinable from source","x":"- The specific integration events that flow over the broker (e.g., UserRegistered, SpeakerLinkedToUser) are cited from AppHost inline comments (Program.cs:46-51, 130-136), not…","i":"SpeakerLinkedToUser UserRegistered CLAUDE.md"},{"u":"/docs/onboarding/devops-cicd.html","d":"CI/CD and Operations","k":"Onboarding Guide","x":"This chapter walks the GitHub Actions workflows that govern MMCA, from the framework's continuous integration and lockstep NuGet release in MMCA.Common, through the ADC…","i":"MMCA.Common"},{"u":"/docs/onboarding/devops-cicd.html#mmcacommon-ciyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.Common, ci.yml","x":"File: MMCA.Common/.github/workflows/ci.yml The continuous-integration workflow for the MMCA.Common framework. Because the fifteen packages are consumed by every downstream…","i":"MMCA.Common.Infrastructure.Redis.Tests RestorePackagesWithLockFile Deque.AxeCore.Playwright Directory.Packages.props PLAYWRIGHT_BROWSERS_PATH DistributedCacheService MMCA.Common.Testing.E2E MMCA.Common.UI.Gallery Directory.Build.props TreatWarningsAsErrors Infrastructure.Tests MMCA.Common.UI.Tests"},{"u":"/docs/onboarding/devops-cicd.html#mmcacommon-releaseyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.Common, release.yml","x":"File: MMCA.Common/.github/workflows/release.yml The lockstep NuGet release workflow. When a maintainer pushes a vX.Y.Z git tag, this workflow deterministically derives the…","i":"Directory.Packages.props github.repository_owner DependencyVersionTests Testing.Architecture MMCA.Common.UI.Maui MMCA.Common.slnx GITHUB_REF_NAME Aspire.Hosting Infrastructure GITHUB_TOKEN Application release.yml"},{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-deployyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, deploy.yml","x":"File: MMCA.ADC/.github/workflows/deploy.yml The primary CI/CD pipeline for the Atlanta Developers Conference application. It runs on every push to main, on every pull request…","i":"needs.foundation.outputs.acrLoginServer coverage.integration.cobertura.xml MMCA.ADC.Integration.slnf Directory.Packages.props USE_MANAGED_IDENTITY_SQL JWT_RSA_PRIVATE_KEY_PEM MMCA.ADC.Services.Tests __EFMigrationsHistory Directory.Build.props SQL_LOCATION_OVERRIDE WebApplicationFactory skip_freshness_gates"},{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-e2eyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, e2e.yml","x":"File: MMCA.ADC/.github/workflows/e2e.yml The full-stack Playwright E2E test workflow. It brings up the complete Aspire stack (SQL Server + Redis + RabbitMQ + four services +…","i":"PLAYWRIGHT_BROWSERS_PATH MMCA.Common.Testing.E2E github.event.schedule WEB_VITALS_OUTPUT_DIR PlaywrightFixture workflow_dispatch matrix.browser WebVitalsTests workflow_call E2E_BASE_URL GITHUB_TOKEN E2E_BROWSER"},{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-cost-guardyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, cost-guard.yml","x":"File: MMCA.ADC/.github/workflows/cost-guard.yml A read-only FinOps check that confirms the production Azure footprint is at its cost baseline. It detects a specific operational…","i":"project_adc_2026_actual_load.md BASELINE_MAX_REPLICAS workflow_dispatch workflow_call deploy.yml production"},{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-load-testyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, load-test.yml","x":"File: MMCA.ADC/.github/workflows/load-test.yml A k6 load test targeting the output-cached Conference read endpoints through the production Gateway. It establishes a repeatable…","i":"project_adc_2026_actual_load.md workflow_dispatch inputs.peak_vus production base_url BASE_URL peak_vus PEAK_VUS"},{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-cutover-per-service-dbsyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, cutover-per-service-dbs.yml","x":"File: MMCA.ADC/.github/workflows/cutover-per-service-dbs.yml A one-time, manually-triggered workflow that migrated the four empty per-service databases (ADCIdentity,…","i":"inputs.freeze_traffic ADC_Notification OutboxProcessor ADC_Conference ADC_Engagement freeze_traffic OutboxMessages ADC_Identity containerapp GITHUB_TOKEN SqlBulkCopy deploy.yml"},{"u":"/docs/onboarding/devops-cicd.html#cross-workflow-summary","d":"CI/CD and Operations","k":"Onboarding Guide","t":"Cross-workflow summary","x":"(dr-drill.yml is the ADR-009 §29 restore drill: it PITR-restores a copy of a chosen database, times the restore for the RTO record, verifies it comes back Online, then deletes…","i":"workflow_call deploy.needs deploy.yml federated because e2e.yml subject deploy scoped false slnx the"},{"u":"/docs/onboarding/devops-cicd.html#rubric-category-index-for-this-chapter","d":"CI/CD and Operations","k":"Onboarding Guide","t":"Rubric category index for this chapter","i":"WebVitalsTests deploy.needs environment release.yml deploy.yml foundation production coverage cutover e2e.yml ci.yml deploy"},{"u":"/docs/onboarding/devops-iac.html","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","x":"This chapter teaches the Azure Infrastructure-as-Code layer for the MMCA.ADC application: what resources are provisioned, why they are shaped the way they are, how secrets reach…","i":"azure.yaml deploy.yml"},{"u":"/docs/onboarding/devops-iac.html#how-the-pieces-fit-together","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"How the pieces fit together","x":"Before diving into individual files, here is the end-to-end picture: Phases 1 and 2 are their own jobs (deploy.yml:747, deploy.yml:795) rather than steps inside deploy, so they…","i":"AZURE_RESOURCE_GROUP resourceGroup foundation main.bicep AtlDevCon deploy"},{"u":"/docs/onboarding/devops-iac.html#azureyaml-the-azd-project-definition","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"azure.yaml, the azd project definition","x":"File: MMCA.ADC/azure.yaml azure.yaml is the Azure Developer CLI (azd) manifest for the project. It declares six deployable services and points azd at the Bicep infrastructure…","i":"Directory.Packages.props foundation.bicep containerapp notification azure.yaml conference engagement main.bicep identity language provider context"},{"u":"/docs/onboarding/devops-iac.html#infrafoundationbicep-long-lived-shared-infrastructure","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"infra/foundation.bicep, long-lived shared infrastructure","x":"File: MMCA.ADC/infra/foundation.bicep Foundation is deployed first (CI/CD chapter: deploy.yml:773-779) on every run. It provisions three resources: the Azure Container Registry,…","i":"reference_log_analytics_sku_limits.md needs.foundation.outputs.acrName workspaceCapping.dailyQuotaGb appLogsConfiguration adminUserEnabled logAnalyticsName environmentName acrLoginServer resourceGroup resourceToken timerTriggers acrPurgeTask"},{"u":"/docs/onboarding/devops-iac.html#deployment-parameters-assembled-at-deploy-time-not-committed","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Deployment parameters, assembled at deploy time, not committed","x":"There is no infra/main.parameters.json file in the repository, the infra/ directory holds only foundation.bicep, main.bicep, DISASTER-RECOVERY.md, OPERATIONS.md,…","i":"USE_MANAGED_IDENTITY_SQL useManagedIdentitySql deploymentParameters SQL_ADMIN_PASSWORD alertEmailAddress foundation.bicep logAnalyticsName sqlAdminPassword environmentName Microsoft.Sql OPERATIONS.md hasAnthropic"},{"u":"/docs/onboarding/devops-iac.html#inframainbicep-the-full-application-infrastructure","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"infra/main.bicep, the full application infrastructure","x":"File: MMCA.ADC/infra/main.bicep main.bicep declares every application-layer Azure resource: Application Insights, the SLO scheduled query rules and their action group, two…","i":"ApplicationSettings__DatabaseInitStrategy Logging__OpenTelemetry__LogLevel__Default APPLICATIONINSIGHTS_CONNECTION_STRING AddHttpForwarderWithServiceDiscovery Authentication__JwtBearer__Authority project_outbox_cost_optimization.md Telemetry__DisableHttpClientMetrics project_adc_no_broker_in_azure.md Scheduler__PollingIntervalSeconds ObservabilityConventionTestsBase Telemetry__DisableRuntimeMetrics DataProtection__ApplicationName"},{"u":"/docs/onboarding/devops-iac.html#deployment-model-summary","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Deployment model summary","x":"The complete credential chain: No static credential exists at any link in this chain. The GitHub secrets AZURECLIENTID, AZURETENANTID, AZURESUBSCRIPTIONID are the OIDC…","i":"AZURE_SUBSCRIPTION_ID SQL_ADMIN_PASSWORD AZURE_CLIENT_ID AZURE_TENANT_ID secure"},{"u":"/docs/onboarding/devops-iac.html#rubric-category-cross-reference","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Rubric category cross-reference","x":"---","i":"useManagedIdentitySql OTEL_SERVICE_NAME adminUserEnabled KeyVault__Uri dailyQuotaGb minReplicas commonTags secrets secure false grpc"},{"u":"/docs/onboarding/devops-iac.html#not-determinable-from-source","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Not determinable from source","x":"- The exact AcrPull and Key Vault Secrets User role-assignment commands used in the out-of- band bootstrap are referenced in comments (main.bicep:915-919, main.bicep:933-936) but…","i":"USE_MANAGED_IDENTITY_SQL AZURE_RESOURCE_GROUP SQL_AAD_ADMIN_LOGIN AZURE_SQL_LOCATION SQL_AAD_ADMIN_OID deploymentMode deploy.yml main.bicep AcrPull Secrets westus2 false"},{"u":"/docs/onboarding/devops-runbooks.html","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","x":"This chapter covers every operational script and runbook in MMCA.ADC: the one-time Azure bootstrap, the database-per-service cutover story (how the legacy AtlDevCon monolith DB…","i":"MMCA.Store AtlDevCon MMCAStore MMCA.ADC ib_rg"},{"u":"/docs/onboarding/devops-runbooks.html#azure-setupsh-one-time-azure-bootstrap","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"azure-setup.sh, One-time Azure bootstrap","x":"File: MMCA.ADC/scripts/azure-setup.sh What it is. A bash script that creates every Azure identity and OIDC credential the GitHub Actions deploy pipeline needs. It is idempotent:…","i":"feedback_azure_cli_role_bug.md JWT_RSA_PRIVATE_KEY_PEM JWT_RSA_PUBLIC_KEY_PEM AZURE_SUBSCRIPTION_ID create_or_replace_fic AZURE_RESOURCE_GROUP MissingSubscription SQL_ADMIN_PASSWORD AZURE_CLIENT_ID AZURE_TENANT_ID Technologies assign_role"},{"u":"/docs/onboarding/devops-runbooks.html#the-database-per-service-cutover-story","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"The database-per-service cutover story","x":"Before the cutover scripts make sense, the story behind them does. Before ADR-006. All four modules (Identity, Conference, Engagement, Notification) pointed at a single shared…","i":"DataSources__Identity__SQLServerConnectionString CrossDataSourceDegradeConvention project_outbox_race_shared_db.md AtlDevCon.dbo.OutboxMessages inputs.freeze_traffic dbo.OutboxMessages workflow_dispatch ADC_Notification OutboxProcessor ADC_Conference ADC_Engagement freeze_traffic"},{"u":"/docs/onboarding/devops-runbooks.html#copy-atldevcon-to-per-service-dbsazureps1-azure-data-copy","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"copy-atldevcon-to-per-service-dbs.azure.ps1, Azure data copy","x":"File: MMCA.ADC/scripts/copy-atldevcon-to-per-service-dbs.azure.ps1 What it is. A PowerShell script that streams rows from AtlDevCon into the four per-service Azure SQL databases…","i":"Microsoft.Data.SqlClient AtlDevCon.schema.Table QUOTED_IDENTIFIER OutboxMessages KeepIdentity is_computed SqlBulkCopy sys.columns CHECKIDENT rowversion RowVersion AtlDevCon"},{"u":"/docs/onboarding/devops-runbooks.html#migrate-atldevcon-to-per-service-dbsps1-local-data-copy-wrapper","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"migrate-atldevcon-to-per-service-dbs.ps1, local data copy wrapper","x":"File: MMCA.ADC/scripts/migrate-atldevcon-to-per-service-dbs.ps1 What it is. A thin PowerShell wrapper that invokes the companion SQL script via sqlcmd against the local Aspire…","i":"QUOTED_IDENTIFIER AtlDevCon localhost sqlcmd error exit sql"},{"u":"/docs/onboarding/devops-runbooks.html#migrate-atldevcon-to-per-service-dbssql-local-sql-copy-script","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"migrate-atldevcon-to-per-service-dbs.sql, local SQL copy script","x":"File: MMCA.ADC/scripts/migrate-atldevcon-to-per-service-dbs.sql What it is. The T-SQL script that performs the actual per-row copy from AtlDevCon into the four per-service…","i":"AtlDevCon.sys.columns sys.identity_columns IDENTITY_INSERT OutboxMessages CHECKIDENT SchemaName XACT_ABORT AtlDevCon TableName timestamp TargetDb EXISTS"},{"u":"/docs/onboarding/devops-runbooks.html#infradisaster-recoverymd-dr-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/DISASTER-RECOVERY.md, DR runbook","x":"File: MMCA.ADC/infra/DISASTER-RECOVERY.md (175 lines; not the Store file of the same name) What it is. The authoritative disaster-recovery runbook for the ADC production…","i":"publicNetworkAccess scheduledQueryRules serviceDatabaseLtr workflow_dispatch ADC_Notification ADC_Conference ADC_Engagement resourceToken sloAlertSpecs ADC_Identity containerapp keyVaultUrl"},{"u":"/docs/onboarding/devops-runbooks.html#dr-drillyml-and-dr-restore-drillps1-the-adr-009-restore-drill","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"dr-drill.yml and dr-restore-drill.ps1, the ADR-009 restore drill","x":"Files: MMCA.ADC/.github/workflows/dr-drill.yml, MMCA.ADC/scripts/dr-restore-drill.ps1 What it is. The automation behind the drill requirement above: the workflow picks a target…","i":"workflow_dispatch SourceDatabase ADC_Identity deploy.needs deploy.yml AtlDevCon finally restore Online status exit show"},{"u":"/docs/onboarding/devops-runbooks.html#infraoperationsmd-day-2-alert-triage-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/OPERATIONS.md, day-2 alert triage runbook","x":"File: MMCA.ADC/infra/OPERATIONS.md What it is. The alert-to-action companion to the provisioned observability: what to do when each SLO alert fires, how to read the SLO workbook,…","i":"MMCA.Common.Testing.Architecture ObservabilityConventionTestsBase legacySloMetricAlertSpecs infra.OPERATIONS.md MinimumAlertSpecs infra.main.bicep OPERATIONS.md sloAlertSpecs ALERT_EMAIL AppTraces sloAlerts resource"},{"u":"/docs/onboarding/devops-runbooks.html#infrasql-managed-identitymd-staged-passwordless-sql-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/SQL-MANAGED-IDENTITY.md, staged passwordless-SQL runbook","x":"File: MMCA.ADC/infra/SQL-MANAGED-IDENTITY.md What it is. The runbook for moving the four service apps from SQL-login (password) auth to Entra managed-identity auth against their…","i":"vars.USE_MANAGED_IDENTITY_SQL USE_MANAGED_IDENTITY_SQL SQL_AAD_ADMIN_LOGIN SQL_AAD_ADMIN_OID Directory db_owner EXTERNAL Identity PROVIDER Managed Active CREATE"},{"u":"/docs/onboarding/devops-runbooks.html#infrapost-cutover-atldevcon-downgrademd-archive-downgrade-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/POST-CUTOVER-atldevcon-downgrade.md, archive downgrade runbook","x":"File: MMCA.ADC/infra/POST-CUTOVER-atldevcon-downgrade.md What it is. A step-by-step runbook for the third and final commit of the database-per-service rollout: downgrading…","i":"maxSizeBytes ProcessedOn deploy.yml main.bicep AtlDevCon capacity against bacpac update query name NULL"},{"u":"/docs/onboarding/devops-runbooks.html#play-store-captureps1-android-screenshot-capture","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"play-store-capture.ps1, Android screenshot capture","x":"File: MMCA.ADC/scripts/play-store-capture.ps1 What it is. A PowerShell 7 script that captures a screenshot from an attached Android device or emulator via adb screencap and saves…","i":"screencap Files shell PATH slug adb png x86"},{"u":"/docs/onboarding/devops-runbooks.html#play-store-composeps1-play-store-screenshot-compositor","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"play-store-compose.ps1, Play Store screenshot compositor","x":"File: MMCA.ADC/scripts/play-store-compose.ps1 What it is. A PowerShell 7 script that reads raw captures from store-assets/play-store/raw/, wraps each into a 1080×1920 branded…","i":"System.Drawing.Common LinearGradientBrush brandTealDark brandCyan brandTeal imageMaxH imageMaxW slug png"},{"u":"/docs/onboarding/devops-runbooks.html#docsmobilereleaserunbookmd-store-submission-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Docs/MobileReleaseRunbook.md, store-submission runbook","x":"File: MMCA.ADC/Docs/MobileReleaseRunbook.md What it is. The manual, credential-holding steps around a store submission that code and CI cannot perform, each tagged with when it…","i":"ADC_ANDROID_SIGNING_PASSWORD FileStorage.UploadFailed sha256_cert_fingerprints AndroidSigningStorePass com.ivanball.atldevcon grantAvatarStorageRole AndroidSigningKeyPass deployNotificationHub TargetPlatformVersion InternalServerError Entitlements.plist ivanball.AtlDevCon"},{"u":"/docs/onboarding/devops-runbooks.html#the-database-per-service-cutover-in-full-context","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"The database-per-service cutover in full context","x":"The five database-related artifacts above form a single coherent story, and the resilience artifacts extend it past the cutover: The AtlDevCon database is the thread that runs…","i":"CrossDataSourceDegradeConvention OPERATIONS.md deploy.yml main.bicep AtlDevCon delete NEVER sql"},{"u":"/docs/onboarding/devops-runbooks.html#rubric-tag-summary","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Rubric tag summary","x":"---","i":"OPERATIONS.md"},{"u":"/docs/onboarding/devops-runbooks.html#not-determinable-from-source","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Not determinable from source","x":"- ALERTEMAIL variable: DISASTER-RECOVERY.md:55-57 and OPERATIONS.md:8-11 both route alert notifications through the alertEmailAddress action-group receiver fed by the ALERTEMAIL…","i":"alertEmailAddress ALERT_EMAIL"},{"u":"/docs/onboarding/devops-testing.html","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","x":"Chapter scope note. The tier chapters (tier-00 through the sweep) document every type in the production codebase one by one. Test types are the logged exception: this chapter…","i":"Fact"},{"u":"/docs/onboarding/devops-testing.html#1-solution-composition-and-the-test-runner","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"1. Solution composition and the test runner","x":"The two deployed apps use the same two-file pattern; MMCA.Common and MMCA.Helpdesk ship a .slnx only, because their solutions are already fast enough not to need a CI subset:…","i":"MMCA.ADC.ServiceBusEmulator.IntegrationTests MMCA.ADC.CrossService.IntegrationTests MMCA.ADC.Architecture.Tests MMCA.Store.Integration.slnf MMCA.ADC.Integration.slnf MMCA.ADC.IntegrationTests DistributedCacheService MMCA.ADC.Services.Tests MMCA.ADC.Gateway.Tests MMCA.ADC.WebAPI.Tests MMCA.Common.API.Tests WebApplicationFactory"},{"u":"/docs/onboarding/devops-testing.html#2-test-project-layout","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"2. Test project layout","x":"The inventory below is drawn from 00-inventory.md:23-117 (test-assembly counts) and the solution files above. Counts are distinct types per project as reported by the Roslyn…","i":"MMCA.ADC.ServiceBusEmulator.IntegrationTests CurrentEventNotificationScopeProviderTests MMCA.ADC.Conference.Infrastructure.Tests MMCA.ADC.Engagement.Infrastructure.Tests MMCA.ADC.Notification.Application.Tests MMCA.ADC.CrossService.IntegrationTests MMCA.ADC.Identity.Infrastructure.Tests MMCA.ADC.Notification.IntegrationTests MMCA.Common.Infrastructure.Redis.Tests NotificationUserDataExportSectionTests MMCA.ADC.Conference.Application.Tests MMCA.ADC.Engagement.Application.Tests"},{"u":"/docs/onboarding/devops-testing.html#3-shipped-testing-infrastructure-packages","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"3. Shipped testing-infrastructure packages","x":"MMCA.Common ships four of its fifteen packages as testing infrastructure that downstream apps consume as NuGet references rather than writing their own harness…","i":"AxeOptions.Wcag21AaExceptMudPagerCombobox WebApplicationFactory.ConfigureServices ServiceInfoVersioningContractTestsBase AssertNoAccessibilityViolationsAsync IsAuthenticatedAuthorizationService SqlServerIntegrationTestFixtureBase MutableAuthenticationStateProvider PageExtensions.FillAndVerifyAsync MMCA.Common.Testing.Architecture ProductionHostApplicationFactory AccessibilityViolationException DecoratorPipelineOrderTestsBase"},{"u":"/docs/onboarding/devops-testing.html#4-architecture-fitness-tests-executable-governance","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"4. Architecture fitness tests, executable governance","x":"[Rubric §34, Architecture Governance & Documentation]: §34 assesses whether architectural decisions are documented, enforced, and kept honest over time; fitness functions are the…","i":"AggregateRoots_ShouldHave_NoPublicConstructors SpecificationsDoNotNavigateToOtherEntities ArchitectureRules.PinnedPackageMajorBelow LayerMap_ModulesDeclareEveryExpectedLayer MassTransit_MustNotExceed_MajorVersion8 CoreLayers_ShouldNotDependOn_Transport ImageSharp_MustNotExceed_MajorVersion3 ObservabilityConventionTestsBaseTests Infrastructure_ShouldNotDependOn_Api ConstructorDependencyCountTestsBase DomainFactories_ShouldReturn_Result FakeDependentModuleConformanceTests"},{"u":"/docs/onboarding/devops-testing.html#5-integration-and-e2e-strategy","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"5. Integration and E2E strategy","x":"The four integration test projects (Identity, Conference, Engagement, Notification) each boot their service in-process with WebApplicationFactory . The lifecycle is not written…","i":"MMCA.Store.ServiceBusEmulator.IntegrationTests MMCA.ADC.ServiceBusEmulator.IntegrationTests MMCA.Store.CrossService.IntegrationTests MMCA.ADC.CrossService.IntegrationTests MMCA.Common.Infrastructure.Redis.Tests AssertNoAccessibilityViolationsAsync IntegrationTestBase.InitializeAsync SqlServerIntegrationTestFixtureBase MMCA.Common.Infrastructure.Tests IdentityIntegrationTestFixture appsettings.Development.json DatabaseInitStrategy.Migrate"},{"u":"/docs/onboarding/devops-testing.html#6-worked-examples","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"6. Worked examples","x":"Three examples tie the infrastructure above to real test code. The per-repo class is a bare subclass; the facts, the package lists and the parsing live once in the shared base:…","i":"IdentityIntegrationTestFixture.DisposeAsync ImageSharp_MustNotExceed_MajorVersion3 IntegrationTestBase.InitializeAsync MutableAuthenticationStateProvider IntegrationTestBase.DisposeAsync IdentityIntegrationTestFixture AuthenticationStateProvider GetAuthenticationStateAsync IdentityIntegrationTestBase Fixture.ResetDatabaseAsync Directory.Packages.props AuthenticateAsAttendee"},{"u":"/docs/onboarding/devops-testing.html#7-the-tiers-and-the-gates-that-run-them","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"7. The tiers and the gates that run them","x":"A test tier only means something once you know what it blocks. This is the map. MMCA.Common's ui-e2e job (MMCA.Common/.github/workflows/ci.yml:228) builds the out-of-slnx gallery…","i":"Integration.slnf MemoryDiagnoser E2E_BROWSER browsers chromium coverage CI.slnf e2e.yml firefox skipped success deploy"},{"u":"/docs/onboarding/devops-testing.html#quick-reference-rubric-categories-touched-in-this-chapter","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"Quick reference: rubric categories touched in this chapter","x":"---"},{"u":"/docs/onboarding/devops-testing.html#cross-links","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"Cross-links","x":"- Primer: 00-primer.md5-the-solution--test-layout , solution files, MTP runner, slnx-excluded UI projects - Primer:…","i":"MMCA.ADC.Integration.slnf IIntegrationTestFixture"},{"u":"/docs/onboarding/99-coverage-audit.html","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","x":"This audit reconciles the written guide against the mechanically-extracted inventory, logs every deliberate exception, verifies the grouping/ordering rules, proves all 34 rubric…","i":"classify.ps1 verify.ps1 plan.ps1"},{"u":"/docs/onboarding/99-coverage-audit.html#1-coverage-reconciliation","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"1. Coverage reconciliation","x":"Cross-check result: verify.ps1 confirms 0 of the 1,804 individually-sectioned types are missing from their group chapter, every one appears as a heading or in a sibling-family…","i":"SpecificationsDoNotNavigateToOtherEntities MMCA.Common.Infrastructure.Redis.Tests DatabaseInitializationExtensionsTests HttpContextExternalLoginEmailVerifier MMCA.ADC.Conference.Application.Tests MMCA.ADC.Engagement.Application.Tests ObservabilityConventionTestsBaseTests OwnSessionQuestionAnswerSpecification AnonymousAuthenticationStateProvider AzureNotificationHubNativePushSender EntitiesWithPiiImplementAnonymizable FrameworkVersionConsistencyTestsBase"},{"u":"/docs/onboarding/99-coverage-audit.html#2-exceptions-log-every-deliberate-omission-with-reason","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"2. Exceptions log (every deliberate omission, with reason)","x":"EF Core migrations (/Migrations/, .Migrations.SqlServer), ModelSnapshot, .Designer.cs, .g.cs, GlobalUsings.g.cs, and AssemblyInfo.cs are excluded by rule (Tools/invtool…","i":"ObservabilityConventionTestsBase ProductionHostApplicationFactory RouteAuthorizationTestsBase ModuleConformanceTestsBase DependencyInjectionAssert GracefulShutdownTestsBase MMCA.Common.Benchmarks Migrations.SqlServer Testing.Architecture MMCA.Common.Testing GlobalUsings.g.cs AssemblyInfo.cs"},{"u":"/docs/onboarding/99-coverage-audit.html#3-grouping--ordering-verification","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"3. Grouping & ordering verification","x":"- Every type in exactly one group. classify.ps1 assigns all 3,264 nodes via name-level overrides (for the grab-bag MMCA.Common.Interfaces/Services namespaces) + ordered…","i":"MidSaveContextCreatingDbContext OutboxRoutingTestDbContext ReentrantSaveInterceptor FailingSaveInterceptor INavigationPopulator ResultGrpcExtensions EntityQueryService SelfHttpWarmupTask ApiControllerBase DeferredDispatch ErrorHttpMapping _typemap.tsv"},{"u":"/docs/onboarding/99-coverage-audit.html#4-rubric-coverage-matrix","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"4. Rubric coverage matrix","x":"Every one of the 34 categories is explained at least once against real code. \"First explained in\" is the earliest group chapter (by order) that tags it; many recur and several…","i":"ThemeService verify.ps1 token"},{"u":"/docs/onboarding/99-coverage-audit.html#5-open-questions--not-determinable-from-source","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"5. Open questions / not determinable from source","x":"1. IDbSeeder host invocation (group-07). The seeding contract and implementations are in MMCA.Common, but the IHostedService/startup invoker that actually runs seeding at boot…","i":"MMCA.ADC.Identity.Contracts.DependencyInjection ModuleApplicationDbContext CrossSourceSpecification ReadRepositoryExtensions EntityTypeConfiguration DependencyInjection DbContexts.Factory ChangePassword ExportUserData IHostedService EnsureCreated IUnitOfWork"},{"u":"/docs/onboarding/99-coverage-audit.html#6-how-to-regenerate-this-audit","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"6. How to regenerate this audit","x":"Then copy the refreshed out/00-inventory.md and out/00-dependency-manifest.md into Docs/Onboarding/ (the 00-group-taxonomy.md is written there directly by classify.ps1).","i":"classify.ps1"},{"u":"/docs/onboarding/CONCEPT-MAPS.html","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","x":"Mermaid diagrams distilled from the Onboarding guide (primer, group taxonomy, dependency manifest, and the 27 group chapters). Each diagram captures a relationship between the…"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#1-system-context-two-codebases--the-15-packages","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"1. System context, two codebases + the 15 packages","x":"MMCA.Common is a framework published as fifteen NuGet packages in lockstep, to nuget.org and GitHub Packages from one tag (ADR-053); MMCA.ADC and MMCA.Store consume them. The…","i":"MMCA.Common.slnx MMCA.Common MMCA.Store MMCA.ADC UI.Maui"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#2-clean-architecture-the-layered-dependency-rule","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"2. Clean Architecture, the layered dependency rule","x":"Source dependencies point inward toward the Domain; each layer references only layers below it. Deliberate exceptions: UI and Grpc depend on Shared only (UI for Blazor WASM…","i":"ProjectReference UI.Maui Aspire Blazor bridge depend Shared above host only sits and"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#3-the-27-functional-groups-dependency--build-order","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"3. The 27 functional groups, dependency / build order","x":"The primary axis of the guide: every type lives in exactly one of 27 chapter groups, ordered roughly topologically. Foundational, widely-depended-on concerns first (Result →…"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#4-core-framework-patterns-how-the-building-blocks-compose","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"4. Core framework patterns, how the building blocks compose","x":"The pattern-level view of the same backbone: the ideas the primer commits to and how they feed each other. Result is the pervasive currency; DDD blocks produce domain events;…","i":"Result"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#5-request-lifecycle-the-cqrs-decorator-pipeline-adr-014","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"5. Request lifecycle, the CQRS decorator pipeline (ADR-014)","x":"Handlers are thin (one method); every cross-cutting concern is a decorator wrapping the next. Scrutor TryDecorate composes them in reverse registration order (last registered =…","i":"AddApplicationDecorators TryDecorate"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#6-event-driven-integration-outbox-dual-dispatch-adr-003--010--021","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"6. Event-driven integration, outbox dual-dispatch (ADR-003 / 010 / 021)","x":"Domain events are captured into an OutboxMessage row in the same transaction as the data (no dual-write bug). The two event kinds then part ways: local domain events are…","i":"IIntegrationEventPublisher IEventBus.PublishAsync OutboxProcessor OutboxMessage SchemaVersion IMessageBus MessageId"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#7-modular-monolith--extractable-services-adr-006--007--008--012","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"7. Modular monolith → extractable services (ADR-006 / 007 / 008 / 012)","x":"Modules implement IModule and are discovered + Kahn-ordered by ModuleLoader (ADR-059). The same module code runs as a single monolith host or as N service processes behind a YARP…","i":"MMCA.ADC.WebAPI ModuleLoader IMessageBus IModule"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#8-persistence-database-per-service--polyglot-engines-adr-006--018--030","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"8. Persistence, database-per-service + polyglot engines (ADR-006 / 018 / 030)","x":"One concrete SQLServerDbContext over the abstract ApplicationDbContext, one instance per database. Each entity is engine-agnostic; a single [UseDataSource(engine)] attribute on…","i":"ApplicationDbContext SQLServerDbContext UseDataSource engine"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#9-authentication--authorization-stack","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"9. Authentication & Authorization stack","x":"The auth concern (G08) spans token validation, session cookies, federated sign-in, password hashing, brute-force protection, refresh-token rotation and revocation, and a layered…"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#10-notifications-three-channels-behind-one-send-pipeline-adr-024--044","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"10. Notifications, three channels behind one send pipeline (ADR-024 / 044)","x":"One use case (SendPushNotificationHandler) writes a durable per-user inbox, fires a transient SignalR push, and then an OS-level native push that reaches a backgrounded or killed…","i":"SendPushNotificationHandler MMCA.ADC.Notification SmtpEmailSender IEmailSender"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#11-ui-write-once-render-everywhere--i18n--theming","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"11. UI, write-once render everywhere + i18n + theming","x":"A page is authored once as a Razor component in a per-module UI library; both the Blazor web host (Server + WASM) and the .NET MAUI host reference the same libraries, so it…","i":"IStringLocalizer InteractiveAuto MMCA.Common.UI ThemeService rendermode"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#12-adc-business-modules-bounded-contexts-end-to-end","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"12. ADC business modules, bounded contexts end-to-end","x":"Each ADC module is a vertical slice through all layers. Conference is large enough to split across five chapters (G17-G21); Engagement takes two (G22 session bookmarks, G23 the…"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#13-the-adrs-grouped-by-theme","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"13. The ADRs, grouped by theme","x":"Every accepted ADR in Website/docs-src/adr/, clustered by the concern it governs. That directory's README.md is the canonical index and owns the count and range; this map only…","i":"README.md"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#14-the-34-category-evaluation-rubric","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"14. The 34-category evaluation rubric","x":"The lens the guide tags code against ([Rubric §N]). Scored on two axes: Maturity (0-4, process) and Implementation (0-10, substance). Three parts. ---","i":"Rubric"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#15-how-the-axes-fit-together-reading-map","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"15. How the axes fit together (reading map)","x":"The guide is organized on two axes at once. This ties the diagrams above back to the guide's navigation. --- - Group-to-group arrows in §3 show the dominant \"builds on\" direction…","i":"ApplicationDbContext"},{"u":"/docs/governance/index.html","d":"Architecture Governance","k":"Architecture Governance","x":"The governance artifacts behind the MMCA platform: the shared 34-category evaluation rubric, and each repo's evidence-based scorecard plus its remediation backlog. Every score…"},{"u":"/docs/governance/index.html#the-rubric","d":"Architecture Governance","k":"Architecture Governance","t":"The rubric","x":"- Architecture Evaluation Criteria: the 34-category rubric (Maturity 0-4 and Implementation 0-10 per category) that all three application repos are scored against."},{"u":"/docs/governance/index.html#how-these-are-maintained","d":"Architecture Governance","k":"Architecture Governance","t":"How these are maintained","x":"Scores are re-verified from source on a cadence: each category is scored by reading the current code, config, and CI (never rolled forward), and any change lands with the…"},{"u":"/docs/governance/adc-ArchitectureScorecard.html","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.ADC's architecture scores (replaces the former single-axis snapshot; see git history).…","i":"dotnet_analyzer_diagnostic.severity SessionSelectionDashboard.razor.cs ArchitectureEvaluationCriteria.md MMCA.ADC.Notification.Application MMCA.Common.Testing.Architecture UIArchitectureConventionTests.cs StateManagementConventionTests SQLitePCLRaw.bundle_e_sqlite3 UIArchitectureConventionTests ObservabilityConventionTests PseudoLocalizationTests RemediationBacklog.md"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#executive-summary","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Executive summary","x":"MMCA.ADC is a mature, evidence-driven .NET 10.0 (LangVersion preview) DDD/Clean Architecture system for the Atlanta Developers Conference, deliberately extracted from a modular…","i":"MMCA.ADC.CrossService.IntegrationTests MMCA.ADC.Notification.IntegrationTests SessionIncludeChildrenRegressionTests UIArchitectureConventionTestsBase FrameworkVersionConsistencyTests LocalizedTextConventionTestsBase MMCA.Common.Testing.Architecture ErrorMessages.ValidationError LocalizedTextConventionTests ObservabilityConventionTests SpecificationConventionTests BlazorCspPolicyProvider.cs"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#scorecard","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted column = Maturity·weight / Implementation·weight per row. Axis-gap findings: the former §21 gap (excellent-but-not-enforced, impl 7 over maturity 2) closed on 2026-07-02…","i":"MmcaCultureBootstrap.SetBrowserCultureAsync ResilienceCircuitBreakerFaultInjectionTests MMCA.ADC.CrossService.IntegrationTests dotnet_analyzer_diagnostic.severity FrameworkVersionConsistencyTests.cs StateManagementConventionTestsBase MMCA.ADC.Notification.Application UIArchitectureConventionTestsBase MMCA.Common.Testing.Architecture ConstructorDependencyCountTests LocalizedTextConventionTests.cs ObservabilityConventionTests.cs"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#indices","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Indices","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 311 ÷ 320 = 97.2% (down from 97.8%, 313/320: the twenty-second-cycle full re-score (2026-07-21, pin v1.121.0) corrected §22…"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Architecture intent is executable, not prose: 29 architecture-test classes / 91 methods gate CI (§34 Governance, mat 4 / impl 9, and §3 Clean Architecture, mat 4 / impl 9):…","i":"SessionIncludeChildrenRegressionTests MMCA.Common.Testing.Architecture SpecificationConventionTests.cs AddSessionCookieAuthentication StateManagementConventionTests MicroserviceExtractionTests AddCommonSecurityHeaders ArchitecturalAnalysis.md LayerDependencyTests AddCommonBlazorCsp DataResidencyTests DomainPurityTests"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#top-5-risks","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"Expected-delta note (updated 2026-08-01): several entries below record an expected lift of \"impl 9→10\". Under the 2026-08-01 recalibration those are attainable, not aspirational:…","i":"publicNetworkAccess packages.lock.json MMCA.ADC.CI.slnf deploy.needs maxReplicas MMCA.ADC.UI CI.slnf"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How this repo's quality relates to the other consumers (where the framework's quality propagates, where a repo diverges or under-uses it, and where a consumer does better than…"},{"u":"/docs/governance/adc-RemediationBacklog.html","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md (single-axis 0-4, baseline 75%, 241/320, dated 2026-06-08). Current authoritative two-axis scores (twenty-sixth-cycle full re-score,…","i":"MMCA.ADC.CrossService.IntegrationTests SqlServerIntegrationTestFixtureBase SessionSelectionDashboard.razor.cs MMCA.ADC.Notification.Application FrameworkVersionConsistencyTests StateManagementConventionTests UIArchitectureConventionTests IntegrationTestReworkPlan.md LocalizedTextConventionTests ObservabilityConventionTests TranslationCompletenessTests MMCA.ADC.Integration.slnf"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-6-highest-leverage","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔴 Priority 6: highest leverage","x":"Token handling uses two rubric-named anti-patterns, with no CSP defense-in-depth. Status (2026-06-27): cookie-only refresh + in-memory access (auth-path BFF), OAuth…","i":"ResilienceCircuitBreakerFaultInjectionTests DisconnectedCircuitRetentionPeriod ManagementRouteAuthorizationTests GatewaySecurityHeadersMiddleware E2E_LIFT_REGISTRATION_THROTTLE OAuthController.CompleteAsync OAuthController.ExchangeAsync SameOriginProxyTokenRefresher MMCA.ADC.Conference.UI.Tests AuthenticationStateProvider EventDetailPage.StatusChip InvalidOperationException"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-4","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟠 Priority 4","x":"The (4−score)×weight formula puts this at 4, but the High flag is a contractual/regulatory exposure that contradicts a shipped, publicly-served policy: treat it as do-soon. -…","i":"user_notification_export.proto LocalizedTextConventionTests TranslationCompletenessTests user_engagement_export.proto ExportUserDataHandlerTests ErasureAndPiiLoggingTests DeleteUserHandlerTests ErrorMessages.Success SessionQuestionAnswer User.PreferredCulture UserRegisteredHandler EventQuestionAnswer"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-3-score-3-weight-3-one-rung-from-a-4","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟡 Priority 3: score 3, weight 3 (one rung from a 4)","x":"- ~~(High) The 258-test Testcontainers integration tier references the deleted MMCA.ADC.WebAPI host, won't build, and is excluded.~~ RESOLVED: reworked as per-service…","i":"Update_WithStaleRowVersion_ReturnsConflict MMCA.ADC.CrossService.IntegrationTests SessionSelectionDashboard.razor.cs StateManagementConventionTestsBase ManagementRouteAuthorizationTests UIArchitectureConventionTestsBase InProcessEventBus.PublishAsync SessionSelectionSpeakerOverlap StateManagementConventionTests UIArchitectureConventionTests DbUpdateConcurrencyException PublicSessionList.razor.cs"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-2-score-3-weight-2-polish--hardening","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Priority 2: score 3, weight 2 (polish / hardening)","x":"- ~~(Medium) No OpenAPI served by any running service, yet CLAUDE.md still advertises 4 doc UIs (/swagger, /nswag-swagger, /api-docs, /scalar/v1).~~ - [x] Serve OpenAPI per…","i":"Microsoft.AspNetCore.Authorization.Authorize AuthorizationPolicies.RequireOrganizer MMCA.ADC.Conference.IntegrationTests ManagementRouteAuthorizationTests FrameworkVersionConsistencyTests IdentityRouteAuthorizationTests IntegrationEventContractTests MMCA.ADC.Migrations.SqlServer Microsoft.AspNetCore.OpenApi ObservabilityConventionTests MicroserviceExtractionTests Validation.CorrectFollowing"},{"u":"/docs/governance/adc-RemediationBacklog.html#implementation-band-implementation--8-ranked-by-implpriority","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔵 Implementation band (implementation <= 8, ranked by implPriority)","x":"Added 2026-07-28 when the ledger gained its second ranked axis. Until then implementation gaps appeared only as unranked sub-bullets inside maturity items, so they were never…","i":"SessionSelectionDashboard.razor.cs MMCA.ADC.Notification.Application AdcArchitectureMap.DefineLayers ConferenceCategoryDetail.razor HappeningNow.razor.cs TreatWarningsAsErrors DeviceSettings.razor SponsorCreate.razor SponsorDetail.razor System.Private.Uri workflow_dispatch UI.Web.Client"},{"u":"/docs/governance/adc-RemediationBacklog.html#resolved-2026-07-25-performance-program-2","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Resolved 2026-07-25 (performance program 2)","x":"Second evidence-led performance pass over Common/ADC/Store. ADC's share shipped as three PRs plus the v1.127.0 framework sweep. - [x] Output cache shared across replicas.…","i":"Session.SessionQuestionAnswers Event.EventQuestionAnswers SessionQuestionViewBuilder CategoryItemLookupService SessionScoringProcessor SpeakerDashboardService SessionQuestionAnswers EventQuestionAnswers SessionCategoryItems SpeakerCategoryItems GetOpenPollsHandler PublicSessionDetail"},{"u":"/docs/governance/adc-RemediationBacklog.html#deliberate--accepted-recorded-decisions-not-scheduled-work","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate / accepted (recorded decisions, not scheduled work)","x":"Conscious, recorded choices, not pending work (the former TECHDEBT.md accepted-risk section): - Single-region deployment (no multi-region failover): accepted in…","i":"SessionRoomScheduling.ValidateRoomAssignmentAsync MMCA.ADC.Notification.Application ConstructorDependencyCountTests LocalizedTextConventionTests TranslationCompletenessTests ArchitecturalAnalysis.md PseudoLocalizationTests AuthenticationService BrandColorTokenTests DeviceSettings.razor skip_freshness_gates SliceCohesionTests"},{"u":"/docs/governance/adc-RemediationBacklog.html#already-at-level-4-protect-dont-regress","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"✅ Already at level 4: protect, don't regress","x":"1 SOLID · 2 Design Patterns · 3 Clean Architecture · 4 Domain-Driven Design · 5 Vertical Slice Architecture · 6 CQRS & Event-Driven · 7 Microservices Readiness · 8 Data…","i":"AnthropicScoringService.ScoreSessionAsync MMCA.ADC.CrossService.IntegrationTests GetSessionSelectionDashboardHandler SessionSelectionDashboard.razor.cs GetSpeakerSessionOverlapHandler GetCategoryDistributionHandler Session.AddSessionCategoryItem Session.CategoryItem.Duplicate Speaker.AddSpeakerCategoryItem Speaker.CategoryItem.Duplicate ObservabilityConventionTests OperationCanceledException"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html","d":"Architecture Evaluation Criteria","k":"Architecture Governance","x":"A structured rubric for evaluating the architecture of an enterprise application. Each category defines what is being assessed, concrete criteria to check, red flags that signal…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#how-to-use-this-rubric","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"How to Use This Rubric","x":"Score each category 0–4. Use the same scale everywhere so totals are comparable. Alongside the maturity level, rate how well each category is actually implemented on a finer 0–10…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#1-solid-principles","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"1. SOLID Principles","x":"Intent: Object/module-level design discipline that keeps code flexible and decoupled. Criteria - SRP: each class/handler has one reason to change; no \"god\" services orchestrating…","i":"NotSupportedException switch new"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#2-design-patterns","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"2. Design Patterns","x":"Intent: Appropriate, idiomatic use of patterns, solving real problems, not pattern theater. Criteria - Creational (Factory methods on entities, Builder, Options) used where…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#3-clean-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"3. Clean Architecture","x":"Intent: Dependencies point inward; business rules are independent of frameworks, UI, and data stores. Criteria - Dependency rule enforced: Domain → (nothing); Application →…","i":"JsonProperty DbContext Table"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#4-domain-driven-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"4. Domain-Driven Design","x":"Intent: The model reflects the business; boundaries follow capability boundaries, not technical layers. Criteria - Ubiquitous language: type/method names match business terms…","i":"decimal Result string Guid"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#5-vertical-slice-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"5. Vertical Slice Architecture","x":"Intent: Code is organized by feature/capability, so a change touches one cohesive slice. Criteria - Features grouped by use case (command/query + handler + validator + DTO…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#6-cqrs--event-driven-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"6. CQRS & Event-Driven Design","x":"Intent: Reads and writes are separated where it pays off; integration via events is reliable. Criteria - Commands (mutate, return Result) and queries (read, side-effect-free) are…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#7-microservices-readiness","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"7. Microservices Readiness","x":"Intent: Whether services (or future-extractable modules) are independently deployable and own their data. Criteria - Service boundaries align with bounded contexts; one team can…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#8-data-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"8. Data Architecture","x":"Intent: Persistence, consistency, and migrations are deliberate and safe. Criteria - Transaction boundaries match aggregate boundaries; unit-of-work scope is clear. - Migrations…","i":"Include"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#9-api--contract-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"9. API & Contract Design","x":"Intent: External and inter-service contracts are clear, stable, and evolvable. Criteria - Consistent resource/endpoint design (REST/minimal APIs/gRPC) with predictable shapes. -…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#10-cross-cutting-concerns","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"10. Cross-Cutting Concerns","x":"Intent: Validation, caching, resilience, configuration, and mapping are centralized and consistent. Criteria - Validation, logging, transactions handled by pipeline…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#11-security","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"11. Security","x":"Intent: AuthN/AuthZ, secrets, and data protection are correct by construction. Criteria - Authentication centralized; tokens validated; identity flows documented (e.g.,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#12-performance--scalability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"12. Performance & Scalability","x":"Intent: The system meets latency/throughput goals and scales horizontally. Criteria - Async I/O throughout; no sync-over-async; no blocking the request thread. - Hot-path query…","i":"Result Wait"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#13-observability--operability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"13. Observability & Operability","x":"Intent: You can understand and operate the system in production. Criteria - Structured logging with correlation/trace IDs flowing across module/service boundaries. - Distributed…","i":"Console.WriteLine"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#14-testability--test-strategy","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"14. Testability & Test Strategy","x":"Intent: The design supports fast, reliable, meaningful tests at the right levels. Criteria - Healthy test pyramid: many fast unit tests on domain/application, fewer integration,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#15-best-practices--code-quality","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"15. Best Practices & Code Quality","x":"Intent: Day-to-day craftsmanship that keeps the codebase healthy. Criteria - Analyzers at error severity (style, security, threading, maintainability) enforced in CI;…","i":"disable warning pragma"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#16-maintainability--evolvability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"16. Maintainability & Evolvability","x":"Intent: The system absorbs change cheaply and ages well. (The governance/documentation depth behind this (ADRs, fitness functions, diagrams) is scored separately in §34.)…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#17-devops--deployment","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"17. DevOps & Deployment","x":"Intent: Building, releasing, and provisioning are automated, repeatable, and safe. (The local developer experience / inner loop behind this (local orchestration, cross-repo dev,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#18-ui-architecture--component-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"18. UI Architecture & Component Design","x":"Intent: Components are cohesive, reusable, and composed cleanly, the UI has a deliberate structure, not page-sized blobs. Criteria - Container/presentational split: smart…","i":"EventCallback ShouldRender razor key"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#19-state-management--data-flow","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"19. State Management & Data Flow","x":"Intent: Client state has a clear owner and predictable flow; server state is cached and invalidated deliberately. Criteria - Single source of truth per piece of state; ownership…","i":"StateHasChanged IsDirty"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#20-design-system-theming--ui-consistency","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"20. Design System, Theming & UI Consistency","x":"Intent: A coherent visual language enforced by a component library, not re-implemented per screen. Criteria - Component library used consistently (e.g., MudBlazor): teams build…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#21-accessibility-a11y","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"21. Accessibility (a11y)","x":"Intent: The UI is usable by everyone, including assistive-technology users, and ideally enforced, not aspirational. Criteria - Semantic structure: correct…","i":"span div"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#22-responsive-design--cross-browserdevice","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"22. Responsive Design & Cross-Browser/Device","x":"Intent: The UI works across viewport sizes, input modes, and supported browsers. Criteria - Fluid/responsive layouts via the design system's grid/breakpoints; no fixed-width…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#23-front-end-performance--rendering","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"23. Front-End Performance & Rendering","x":"Intent: The UI loads and responds fast; rendering work is bounded. (Complements §12: this is the client side.) Criteria - Initial load: bundle/payload size controlled;…","i":"ShouldRender key"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#24-forms-validation--ux-safety","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"24. Forms, Validation & UX Safety","x":"Intent: Data entry is safe, forgiving, and consistent, users don't lose work or get confused by errors. Criteria - Validation parity: client-side validation for fast feedback…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#25-navigation-routing--information-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"25. Navigation, Routing & Information Architecture","x":"Intent: Users can find their way; routes are meaningful, guarded, and role-aware. Criteria - Route design: clean, bookmarkable, deep-linkable URLs; parameters typed and…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#26-front-end-security","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"26. Front-End Security","x":"Intent: The client doesn't become the weak link, XSS, token handling, and trust boundaries are correct. (Complements §11.) Criteria - Output encoding / XSS: no unsanitized HTML…","i":"MarkupString innerHTML"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#27-internationalization--localization","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"27. Internationalization & Localization","x":"Intent: The UI can be translated and respects culture, if in scope. (Score weight 0–1 if single-locale by design.) Criteria - Externalized strings: UI text in resource files, not…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#28-front-end-testing--quality","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"28. Front-End Testing & Quality","x":"Intent: The UI is verified at the right levels with stable, meaningful tests. (Complements §14.) Criteria - Component tests (e.g., bUnit) for rendering logic, parameters, events,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#29-resilience-reliability--business-continuity","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"29. Resilience, Reliability & Business Continuity","x":"Intent: The system survives partial failure and recovers from disaster within defined objectives. (Extends the resilience facets of §7/§12 into a first-class recovery story.)…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#30-compliance-privacy--data-governance","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"30. Compliance, Privacy & Data Governance","x":"Intent: Personal and regulated data is classified, governed, and handled lawfully across its lifecycle. (§11 defends against attackers; this answers to regulators.) Criteria -…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#31-cost-efficiency--finops","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"31. Cost Efficiency / FinOps","x":"Intent: Cloud spend is proportional to value and driven by data, not guesswork. (§17 mentions cost; this makes it a first-class axis.) Criteria - Right-sizing: compute/database…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#32-dependency--supply-chain-management","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"32. Dependency & Supply-Chain Management","x":"Intent: Third-party and inter-package dependencies are controlled, auditable, and evolve safely, especially critical for a framework that publishes packages. (Elevates §15's…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#33-developer-experience--inner-loop","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"33. Developer Experience & Inner Loop","x":"Intent: Developers build, run, test, and iterate locally with fast, low-friction feedback. (Promoted out of §17: that scores release/ops automation; this scores the inner loop.)…","i":"editorconfig local.props"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#34-architecture-governance--documentation","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"34. Architecture Governance & Documentation","x":"Intent: Decisions are recorded, conformance is enforced, and the system is documented so it stays coherent as it evolves. (Promoted out of §16: that scores the property of…","i":"CLAUDE.md"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#appendix-quick-scan-checklist","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"Appendix: Quick-Scan Checklist","x":"A 2-minute triage before the full evaluation: any \"no\" warrants a deeper look. - [ ] Can you draw the dependency graph and is it acyclic and inward-pointing? - [ ] Is the domain…"},{"u":"/docs/governance/common-ArchitectureScorecard.html","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.Common's architecture scores (replaces the former single-axis snapshot; see git…","i":"ResilienceCircuitBreakerFaultInjectionTests SpecificationsDoNotNavigateToOtherEntities CrossSourceSpecification.BuildAsync BaseIntegrationEvent.SchemaVersion AggregateRootEntityControllerBase ArchitectureEvaluationCriteria.md DomainInvariantViolationException LocalizedTextConventionTestsBase MMCA.Common.Testing.Architecture OpenIdConnectMetadataWarmupTask ResourceTranslationsAreComplete EventVersioningConventionTests"},{"u":"/docs/governance/common-ArchitectureScorecard.html#scorecard","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted column = Maturity·weight / Implementation·weight per row. Axis-gap findings: §17/§8 are mature-but-execution-deferred (mechanism shipped, deeper proof lives downstream);…","i":"SpecificationsDoNotNavigateToOtherEntities CrossSourceSpecification.BuildAsync BaseIntegrationEvent.SchemaVersion TransportDoesNotLeakIntoCoreLayers ArchitectureEvaluationCriteria.md CrossDataSourceDegradeConvention MMCA.Common.Testing.Architecture OpenIdConnectMetadataWarmupTask EventVersioningConventionTests ListPageQueryStateServiceTests PermissionAuthorizationHandler PiiErasureContractFitnessTests"},{"u":"/docs/governance/common-ArchitectureScorecard.html#indices","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Indices","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 314 ÷ 324 = 96.9% (unchanged on the twenty-seventh-wave re-score, 2026-08-14: no maturity score moved; the two proposed…"},{"u":"/docs/governance/common-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Dual-enforced Clean Architecture dependency rule (compile-time + fitness functions), §3 (impl 9): Source/Build/MMCA.Common.LayerEnforcement.targets:1-90 fails the build on…","i":"BaseIntegrationEvent.SchemaVersion MMCA.Common.Testing.Architecture EventVersioningConventionTests ResolveProjectReferences packages.lock.json FixedTimeEquals Result.Failure BeforeTargets Theory Fact"},{"u":"/docs/governance/common-ArchitectureScorecard.html#top-5-risks","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"Note (twenty-first wave, v1.121.0): earlier waves closed risks previously listed here (§29's restore drill, the §27 i18n train, §24 forms enforcement, §22's firefox gate, §23's…","i":"PiiErasureContractFitnessTests OutboxPollFilterProcessor NavigationContractTests required_status_checks PiiConventionTests CONTRIBUTING.md DEPLOYMENT.md IAnonymizable PiiRedactor COST.md main Pii"},{"u":"/docs/governance/common-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How this repo's quality relates to the other consumers (where the framework's quality propagates, where a repo diverges or under-uses it, and where a consumer does better than…"},{"u":"/docs/governance/common-RemediationBacklog.html","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md (canonical two-axis scoring: Maturity 96.9% / Implementation 84.8%, framework v1.152.0; the 2026-08-14 twenty-seventh-wave two-pass re-score…","i":"ArchitectureScorecard.md required_status_checks RedisDistributedLock IDistributedLock BenchmarkDotNet IsDirtyAccessor Performance baseline c911480 f292233 NoWarn verify"},{"u":"/docs/governance/common-RemediationBacklog.html#implementation-band-implementation--8-ranked-by-implpriority","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Implementation band (implementation <= 8, ranked by implPriority)","x":"Added 2026-07-28 when the ledger gained its second ranked axis. Until then implementation gaps were never ranked or scheduled, which is why consecutive steady-state cycles moved…","i":"ArchitecturalAnalysis.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-first-wave-2026-06-08","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: first wave (2026-06-08)","x":"Implemented in MMCA.Common, ✅ verified 2026-06-09: dotnet build -c Release is clean (0 warnings / 0 errors, all analyzers) and all 9 test projects pass (~1,611 tests, 0…","i":"MessageBusSettings.RetryLimit ConfigureBrokerTransport Directory.Packages.props IntegrationEventConsumer RetryMaxIntervalSeconds RetryMinIntervalSeconds DependencyVersionTests OutboxCleanupService UseMessageRetry MobileCardList BunitTestBase IAnonymizable"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-second-wave-2026-06-09","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: second wave (2026-06-09)","x":"✅ Verified: dotnet build -c Release clean (0/0) and all 9 test projects pass (1,511 tests, 0 failures). - ✅ 32 / 16: supply-chain. NuGet lock files (RestorePackagesWithLockFile,…","i":"RestorePackagesWithLockFile ServiceContractAttribute nuget.config CqrsMetrics WithMetrics AddMeter package Release dotnet snupkg build list"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-third-wave-front-end-2026-06-09","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: third wave (front-end, 2026-06-09)","x":"✅ Verified: build clean (0/0) and all 9 test projects pass (1,519 tests, 0 failures); UI tests 90 → 98 (8 new bUnit tests). - ✅ 19: UnsavedChangesGuard live-accessor. Added…","i":"Page.AssertNoAccessibilityViolationsAsync Deque.AxeCore.Playwright MobileInfiniteScrollList UnsavedChangesGuard MaxRenderedItems IsDirtyAccessor CurrentIsDirty PageLoading PageHeader Virtualize MMCATheme PageError"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-fourth-wave-breaking-changes--consumer-sweep-2026-06-09","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: fourth wave (breaking changes + consumer sweep, 2026-06-09)","x":"✅ Verified across all three repos (built/tested via local.props against Common source, no token): Common 1,523, ADC 1,241, Store 1,088 tests, 0 failures; all CI solutions build…","i":"AggregateConventionTests IntegrationEventConsumer UserNotification.Create EntityConventionTests OutboxCleanupService AddInboxMessages UserNotification BaseDomainEvent NoOpInboxStore InboxMessages EfInboxStore IDomainEvent"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-v1800-2026-06-26","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: v1.80.0 (2026-06-26)","x":"The single-axis backlog above is from the 2026-06-08/09 review (index 80%). The framework has since reached v1.82.0 and the canonical scoring was the in-repo, two-axis…","i":"PermissionAuthorizationHandler BaseDomainEvent.DateOccurred UserNotification.MarkAsRead PermissionRegistryBuilder AddAuthorizationPolicies ArchitectureScorecard.md GlobalRateLimitPartition PermissionPolicyProvider RateLimitPartitionTests RoleNames.ContentEditor UserNotification.ReadOn IPermissionRegistry"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-v1810v1820--governance-pass-2026-06-26","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: v1.81.0/v1.82.0 + governance pass (2026-06-26)","x":"Released since v1.80.0 (v1.81.0, v1.82.0) plus a sixth governance pass currently in flight (uncommitted). All of it lands in categories already scored 9-10, so the two-axis…","i":"ArchitectureEvaluationCriteria.md MMCA.Common.Aspire.Security SecurityHeadersMiddleware AddCommonSecurityHeaders ICspPolicyProvider MapCommonScalarUi Scalar.AspNetCore ValidAlgorithms RsaSha256 FACTS.md b9a6a28 COST.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-v1830v1840-2026-06-27","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: v1.83.0/v1.84.0 (2026-06-27)","x":"Released since v1.82.0 (v1.83.0, v1.84.0) plus a docs-only governance pass currently in flight (uncommitted). One score moved at this wave: §30 Implementation 7→8. The canonical…","i":"OpenIdConnectMetadataWarmupTask INotificationRecipientProvider ArchitectureScorecard.md IPushNotificationSender WarmupHostedService WarmupReadinessGate AddServiceDefaults PiiConventionTests PiiRedactorTests UserNotification IWarmupTask PiiRedactor"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-v1850-eighth-wave-under-8-implementation-remediation-2026-06-27","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress, v1.85.0 (eighth wave: under-8 Implementation remediation, 2026-06-27)","x":"The under-8 Implementation remediation (commit 78e5312, tag v1.85.0, HEAD 7082a5f) lifted every category scored Implementation one maturity score. Re-verified against current…","i":"MMCA.Common.Testing.Architecture ArchitectureRules.Slices.cs PasswordComplexityAttribute ArchitectureScorecard.md AuthModelValidationTests DataAnnotationsValidator ServiceContractAttribute TraceIdRatioBasedSampler SliceCohesionTestsBase ParentBasedSampler SliceCohesionTests NavigationFlow.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-v1860v1920-ninth-wave-i18n--re-score-2026-06-29","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress, v1.86.0→v1.92.0 (ninth wave: i18n + re-score, 2026-06-29)","x":"Re-scored against current source at framework v1.92.0 (HEAD 93ffcac, dirty tree). Canonical scoring is now Maturity 91.7% / Implementation 84.1% (was 92.8% / 85.0%) per the…","i":"PiiErasureContractFitnessTests WebApplicationExtensions.cs ArchitectureScorecard.md ConfigureBrokerTransport IntegrationEventConsumer User.PreferredCulture UseDelayedRedelivery cfg.UseMessageRetry PiiConventionTests DataSubjectSample PasswordHasher.cs IStringLocalizer"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-tenth-wave-focused-in-repo-remediation-2026-06-30","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: tenth wave (focused in-repo remediation, 2026-06-30)","x":"Four scores moved up on shipped, tested in-repo evidence; both indices rose for the first time in several waves: Maturity 91.7% → 92.9% (301/324), Implementation 84.1% → 84.9%…","i":"MMCA.Common.Testing.Architecture PaletteDark.PrimaryContrastText ResourceTranslationsAreComplete DatabaseRestoreDrillTests LocalizationResourceTests Directory.Packages.props PrimitivesSnapshotTests SupportedCultures.All PaletteDark.Primary WarningContrastText ErrorContrastText ACCESSIBILITY.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-eleventh-wave-adr-governance-2026-06-30","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: eleventh wave (ADR governance, 2026-06-30)","x":"No score moves. A full 34-category evidence re-score at framework v1.93.0 (HEAD 3e72bfa, dirty tree) re-confirmed every category at its tenth-wave value; indices hold at Maturity…","i":"AggregateRootEntityControllerBase EntityControllerBase OwnerOrAdminFilter OwnershipHelper Specification customer_id FACTS.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-twelfth-wave-under-8-implementation-lift-v1940-pending-2026-06-30","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: twelfth wave (under-8 Implementation lift, v1.94.0 pending, 2026-06-30)","x":"Two Implementation scores move up, Maturity holds: Implementation 84.9% → 85.3% (691/810), Maturity 92.9% (301/324) unchanged. Full Release build clean, 1685 tests pass. Held for…","i":"LocalizedTextConventionTestsBase ListPageQueryStateServiceTests SupportedCultures.PseudoLocale LocalizedTextConventionTests PseudoStringLocalizerFactory UseCommonRequestLocalization PseudoLocalizationE2ETests ListPageStateServiceTests LocalizationResourceTests PseudoLocalizer.Transform IStringLocalizerFactory PseudoLocalizationTests"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---fourteenth-wave-clean-tree-evidence-re-score-at-v11010-2026-07-03","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - fourteenth wave (clean-tree evidence re-score at v1.101.0, 2026-07-03)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.101.0 (HEAD 5e55be2, working tree clean: the recurring…","i":"ArchitectureScorecard.md FormsConventionTestsBase RegisterFormTests.cs Testing.Architecture Scalar.AspNetCore ValidationMessage FACTS.md slnx"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---defect-fix-wave-c-1c-7-2026-07-05","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - defect-fix wave C-1..C-7 (2026-07-05)","x":"Seven approved defect fixes, each behavior change landed with its pinning test flipped (or a new regression test) in the same change; build 0/0 and the full .slnx suite green.…","i":"Microsoft.Extensions.TimeProvider.Testing EntityServiceBase.GetAllForLookupAsync SessionCookieAuthenticationHandler OAuthControllerBase.CompleteAsync AuthenticatedServiceBase ChildEntityServiceBase LoginProtectionService LoggingQueryDecorator ITokenStorageService KeyNotFoundException OutboxCleanupService Uri.EscapeDataString"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-sixteenth-wave-clean-tree-re-score-at-v11060-2026-07-06","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: sixteenth wave (clean-tree re-score at v1.106.0, 2026-07-06)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.106.0 (HEAD 6f8b917, one commit past the v1.106.0 tag, working tree…","i":"ArchitecturalAnalysis.md ArchitectureScorecard.md Directory.Packages.props EncryptedStringConverter SECURITY.md FACTS.md b75fa8f Theory Fact"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---seventeenth-wave-evidence-re-score-at-v11080-2026-07-09","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - seventeenth wave (evidence re-score at v1.108.0, 2026-07-09)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.108.0 (git HEAD 6c3b3bc, working tree clean, one commit ahead of…","i":"ILiveChannelPublisher ACCESSIBILITY.md FACTS.md ci.yml"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---eighteenth-wave-runtime-performance-wave-2026-07-10","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - eighteenth wave (runtime performance wave, 2026-07-10)","x":"A cross-repo runtime-performance audit (4 parallel auditors: framework, ADC, Store, hosting/config) found the framework strong on read-path fundamentals (no-tracking, SQL…","i":"PublicEndpointOutputCachePolicy EFReadRepository.ApplyIncludes PooledConnectionLifetime HttpResilienceDefaults CachingQueryDecorator LocalView.FindEntry ExecuteUpdateAsync InProcessEventBus AllowAnonymous DetectChanges ExpandoObject CHANGELOG.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---remediation-wave-1-cross-repo-wave-plan-2026-07-11","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - remediation wave 1 (cross-repo wave plan, 2026-07-11)","x":"First wave of the 2026-07-11 cross-repo remediation plan (workspace plan file). Ships the shared §18/§19 fitness bases the ADC/Store maturity lifts need, closes the tenth-wave 20…","i":"StateManagementConventionTestsBase UIArchitectureConventionTestsBase ErrorMessages._localizer MobileInfiniteScrollList AllowedStaticMembers PrimaryContrastText WebVitalsCollector ErrorContrastText WebVitalsE2ETests DarkModeE2ETests NotificationBell CHANGELOG.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---eighteenth-wave-evidence-re-score-at-v11150-2026-07-12","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - eighteenth wave (evidence re-score at v1.115.0, 2026-07-12)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.115.0 (HEAD 37d0a3b, working tree clean, at the release tag). Three…","i":"ArchitectureScorecard.md MMCA.Common.UI.Maui PrimaryContrastText ErrorContrastText WebVitalsE2ETests DarkModeE2ETests MudDataGrid FACTS.md rgba"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twentieth-wave-evidence-re-score-at-v11170-2026-07-17","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twentieth wave (evidence re-score at v1.117.0, 2026-07-17)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.117.0 (HEAD 76d70cf, working tree clean). Four scores move.…","i":"ArchitectureScorecard.md NavigationContractTests required_status_checks AuthorizeAttribute NavigationFlow.md MMCA.Common.UI RouteAttribute RESPONSIVE.md FACTS.md bicep build Short"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-first-wave-evidence-re-score-at-v11210-2026-07-21","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-first wave (evidence re-score at v1.121.0, 2026-07-21)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.121.0 (HEAD 4a4fc05, working tree clean). One score moves.…","i":"ArchitectureScorecard.md required_status_checks BenchmarkDotNet CONTRIBUTING.md Notifications Performance baseline FACTS.md COST.md verify Short gate"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-second-wave-evidence-re-score-at-v11230-2026-07-23","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-second wave (evidence re-score at v1.123.0, 2026-07-23)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.123.0 (HEAD c911480, working tree clean). No score moves. Canonical…","i":"UnsavedChangesGuard.IsDirtyAccessor PiiErasureContractFitnessTests PasswordComplexityAttribute IIntegrationEventPublisher ArchitectureScorecard.md OpenApiContractTestsBase IConnectionMultiplexer EntityQueryPipeline IEventBus EditForm FACTS.md c911480"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-third-wave-evidence-re-score-at-v11280-2026-07-25","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-third wave (evidence re-score at v1.128.0, 2026-07-25)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.128.0 (HEAD 3dff29b, working tree clean). No score moves, the third…","i":"ArchitectureScorecard.md WebVitalsE2ETests ICommandHandler IQueryHandler pull_request permissions Unreleased FACTS.md TResult ci.yml github Result"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-fourth-wave-evidence-re-score-at-v11310-2026-07-28","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-fourth wave (evidence re-score at v1.131.0, 2026-07-28)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.131.0 (HEAD 2c52aa9, working tree clean). No score moves, the…","i":"ArchitectureScorecard.md OpenApiContractTestsBase AddCommonApiVersioning MMCA.Common.UI.Maui ICommandHandler ServiceContract AllowAnonymous AllowAnyOrigin IQueryHandler FACTS.md TResult CS1591"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-fifth-wave-evidence-re-score-at-v11350-2026-08-01","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-fifth wave (evidence re-score at v1.135.0, 2026-08-01)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.135.0 (HEAD f292233, working tree clean). One score moves, ending…","i":"EntityQueryService.GetAllForLookupAsync DomainInvariantViolationException ArchitectureScorecard.md InProcessDistributedLock HttpResilienceDefaults IConnectionMultiplexer RedisDistributedLock NuGetAuditSuppress IdempotencyFilter IDistributedLock v1.128.0..HEAD AddCaching"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-sixth-wave-evidence-re-score-at-v11420-2026-08-07","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-sixth wave (evidence re-score at v1.142.0, 2026-08-07)","x":"Full 34-category two-pass re-score at HEAD 710d29d (clean tree). No scores move: 27 categories re-confirmed fresh, and seven first-pass lift proposals were refuted on the…","i":"GetAllForLookupAsync packages.lock.json AddMeter FACTS.md orderBy OrderBy secrets l.Name navbar NoWarn where"},{"u":"/docs/governance/common-RemediationBacklog.html#deferred---2026-07-19-full-review-recorded-not-scheduled","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deferred - 2026-07-19 full review (recorded, not scheduled)","x":"The 2026-07-19 full framework review shipped its accepted fixes on the review branch (rollback on business failure + post-commit dispatch, outbox leases + dead-letter visibility,…","i":"MMCA.Common.Infrastructure MMCA.Common.UI.Tests MMCA.Common.UI.Maui IServiceCollection IMessageBus LangVersion extension IsFailure preview TResult CS1591 NoWarn"},{"u":"/docs/governance/common-RemediationBacklog.html#recorded---2026-07-31-consumer-discovered-defect-not-scheduled","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Recorded - 2026-07-31 consumer-discovered defect (not scheduled)","x":"Found downstream while implementing MMCA.ADC BR-239 (public speaker visibility), which needed a filtered lookup read. Recorded rather than fixed in place: the consumer already…","i":"EntityQueryService.GetAllForLookupAsync MMCA.Common.Shared.ValueObjects.Email IRepository.GetAllForLookupAsync QueryFieldService.Validate InvalidOperationException GetOrBuildLookupSelector BaseLookup.Name nameProperty asTracking ToString orderBy OrderBy"},{"u":"/docs/governance/common-RemediationBacklog.html#priority-6-highest-leverage","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔴 Priority 6: highest leverage","x":"The package ships reusable Blazor primitives with no fast test tier. - ~~(medium) No component tests for the UI library~~ RESOLVED: Tests/Presentation/MMCA.Common.UI.Tests…","i":"Page.AssertNoAccessibilityViolationsAsync PiiErasureContractFitnessTests AuditableBaseEntity.Delete Deque.AxeCore.Playwright EncryptedStringConverter MobileInfiniteScrollList MMCA.Common.Testing.E2E MMCA.Common.Testing.UI MMCA.Common.UI.Tests OutboxCleanupService UnsavedChangesGuard DeleteConfirmation"},{"u":"/docs/governance/common-RemediationBacklog.html#priority-3-score-3-weight-3-one-rung-from-a-4","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟠 Priority 3: score 3, weight 3 (one rung from a 4)","x":"- ~~(medium) No broker retry policy on the extracted-microservice path~~ RESOLVED (re-verified 2026-06-29): ConfigureBrokerTransport applies cfg.UseMessageRetry (exponential) on…","i":"DomainAggregateRootsHaveNoPublicConstructors ResilienceCircuitBreakerFaultInjectionTests Add_DifferentCurrencies_ReturnsFailure HandleBeforeInternalNavigationAsync MobileInfiniteScrollListTests.cs AggregateRootsHaveResultFactory MessageBusSettings.RetryLimit AggregateConventionTestsBase DomainExposesAggregateRoots DomainFactoriesReturnResult RestorePackagesWithLockFile UnsavedChangesGuardTests.cs"},{"u":"/docs/governance/common-RemediationBacklog.html#priority-2-score-3-weight-2-polish--hardening","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟡 Priority 2: score 3, weight 2 (polish / hardening)","x":"- (medium) No consumer-side idempotency/inbox for at-least-once broker delivery: duplicate side effects possible in any non-idempotent consumer. (low) ~~Same misleading…","i":"EntityQueryPipeline.MaxUnboundedResultLimit ApplicationSettings.MaxPageSize MessageBusSettings.EnableInbox ArchitectureRules.Slices.cs MobileInfiniteScrollList OpenApiContractTestsBase ServiceContractAttribute Directory.Build.targets AddCommonApiVersioning required_status_checks SliceCohesionTestsBase MMCA.Common.UI.Maui"},{"u":"/docs/governance/common-RemediationBacklog.html#already-at-level-4-protect-dont-regress","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"✅ Already at level 4: protect, don't regress","x":"1 SOLID · 2 Design Patterns · 3 Clean Architecture · 5 Vertical Slice (maturity 3→4 on the slice-cohesion fitness function) · 7 Microservices Readiness · 8 Data Architecture · 10…","i":"MessageBusSettings.EnableInbox NavigationContractTests IConnectionMultiplexer required_status_checks WebVitalsE2ETests IDistributedLock BenchmarkDotNet EditorRequired Performance baseline navbar verify"},{"u":"/docs/governance/common-RemediationBacklog.html#deliberate--accepted-documented-caps-not-scheduled-work","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔒 Deliberate / accepted (documented caps, not scheduled work)","x":"Moved out of the active priority queue on 2026-07-02 (user-approved). Its computed priority = (4 − 2) × 2 = 4 is the highest weighted gap of any open category, but the unmet §31…","i":"NavigationFlow.md ACCESSIBILITY.md CONTRIBUTING.md NUGET_API_KEY RESILIENCE.md RESPONSIVE.md CHANGELOG.md release.yml SECURITY.md main.bicep CLAUDE.md README.md"},{"u":"/docs/governance/common-RemediationBacklog.html#mostly-consumer-assessed-the-shared-commonui-surface-is-scored-here","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"⚪ Mostly consumer-assessed (the shared Common.UI surface is scored here)","x":"21 Accessibility · 26 Front-End Security (Assessable mainly in consumer apps; 26 shared surface is covered under 11.) - 22 Responsive: CLOSED at Maturity 4 / Implementation 9…","i":"LocalizedTextConventionTests PseudoLocalizationE2ETests AuthModelValidationTests NavigationContractTests PasswordComplexity NavigationFlow.md RegisterFormTests ValidationMessage ResxMudLocalizer Forbidden EditForm slnx"},{"u":"/docs/governance/store-ArchitectureScorecard.html","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.Store's architecture scores. This is Store's first in-repo governance artifact…","i":"CK_InventoryItem_AvailableQuantity_NonNegative ArchitectureEvaluationCriteria.md ConstructorDependencyCountTests StateManagementConventionTests UIArchitectureConventionTests ObservabilityConventionTests MobileInfiniteScrollList PseudoLocalizationTests GracefulShutdownTests Money.ToDisplayString RemediationBacklog.md ServiceInfoController"},{"u":"/docs/governance/store-ArchitectureScorecard.html#executive-summary","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Executive summary","x":"MMCA.Store is a .NET 10.0 (LangVersion preview) DDD/Clean Architecture e-commerce system (Catalog, Sales, Identity modules; Stripe checkout) extracted into independently-hosted…","i":"MMCA.Common.Testing.Architecture IntegrationEventContractTests LocalizedTextConventionTests TreatWarningsAsErrors DataResidencyTests dbo.OutboxMessages PiiConventionTests Store_Identity Store_Catalog Store_Sales MMCAStore"},{"u":"/docs/governance/store-ArchitectureScorecard.html#scorecard","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted = Maturity·weight / Implementation·weight. Axis-gap finding: §21 Accessibility is honestly M3/I8 (the chromium axe gate earns Implementation 8; Maturity caps at 3…","i":"CK_InventoryItem_AvailableQuantity_NonNegative MMCA.Store.CrossService.IntegrationTests FrameworkVersionConsistencyTests IntegrationEventContractTests.cs ConstructorDependencyCountTests StateManagementConventionTests SQLitePCLRaw.bundle_e_sqlite3 UIArchitectureConventionTests CultureInfo.InvariantCulture LocalizedTextConventionTests ObservabilityConventionTests TranslationCompletenessTests"},{"u":"/docs/governance/store-ArchitectureScorecard.html#indices","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Indices","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 313 ÷ 320 = 97.8% (re-confirmed with no moves on the 2026-08-14 re-score; §12, §21, and §22 are the three categories at…"},{"u":"/docs/governance/store-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Clean Architecture + DDD + CQRS depth, fitness-enforced: §3/§4 (impl 9): inherited framework substance, guarded by the shared NetArchTest suite (23 test classes,…","i":"GracefulShutdownTests IAnonymizable"},{"u":"/docs/governance/store-ArchitectureScorecard.html#top-5-risks","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"1. Accessibility maturity is capped pending a human pass: §21 (mat 3, weight 3): the 23-scan axe suite gates the deploy (impl 8), but the rubric pairs axe-in-CI with a recorded…","i":"BrandColorTokenTests FormsConventionTests deploy.needs a1de5a89 MudForm"},{"u":"/docs/governance/store-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How Store relates to MMCA.Common (the framework) and MMCA.ADC (the sibling consumer) is maintained once, for all three repos, in the workspace-internal…"},{"u":"/docs/governance/store-RemediationBacklog.html","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md (two-axis: Maturity 97.8% / Implementation 83.9%, full re-score 2026-07-28, re-confirmed with no score moves on the 2026-08-14 full…","i":"ArchitectureScorecard.md"},{"u":"/docs/governance/store-RemediationBacklog.html#priority-a11y--e2e-merge-gate-21-28-22","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔴 Priority: a11y / E2E merge gate (#21, #28, #22)","x":"The former single biggest maturity lever: 28 cleared 2026-07-03; 22 cleared on the 2026-07-17 re-score (the gate flip verified live) and reopened on the 2026-07-28 re-score when…","i":"github.event_name matrix.browser workflow_call deploy.needs deploy.yml browsers d057afc e2e.yml Theory needs Fact"},{"u":"/docs/governance/store-RemediationBacklog.html#priority-execution-quality-gaps-impl-not-maturity","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟠 Priority: execution-quality gaps (impl, not maturity)","x":"Ranked 2026-07-28 when the ledger gained its second ranked axis. Until then the items in this section were closed history plus two open levers, with no ranking and no inclusion…","i":"MMCA.Store.CrossService.IntegrationTests SqlServerIntegrationTestFixtureBase CultureInfo.InvariantCulture MobileInfiniteScrollList ProductVariantChanged NotifyStateChanged workflow_dispatch CatalogBrowse GetPagedAsync InventoryItem deploy.needs IsDrawerOpen"},{"u":"/docs/governance/store-RemediationBacklog.html#priority-minor--accept-or-polish","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟡 Priority: minor / accept-or-polish","x":"- [x] 32 Dependency & Supply-Chain, impl 7 → 8. DONE (2026-07-03, drift plan D8 + D9). Vulnerability gate is NuGetAudit + TreatWarningsAsErrors at restore, which fails…","i":"ServiceInfoController TreatWarningsAsErrors BrandColorTokenTests FormsConventionTests CustomerEmailRules NuGetAuditSuppress Store_Identity Store_Catalog Store_Sales ApiVersion Deprecated MMCAStore"},{"u":"/docs/governance/store-RemediationBacklog.html#defect-fix-wave-2026-07-05","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🐞 Defect-fix wave (2026-07-05)","x":"Four reviewed product defects fixed in one wave; every behavior change flipped its pinning test in the same change. - [x] S-1 Stripe network errors escaped the Result pattern.…","i":"Payment.Stripe.SessionRetrievalFailed Payment.Stripe.SessionCreationFailed Payment.Stripe.UnsupportedCurrency CartStateService.InitializeAsync ExportUserDataHandler HttpRequestException StripePaymentService CheckoutAndPayAsync DeleteUserHandler UserRole.IsAdmin CheckoutOutcome UserRole.Admin"},{"u":"/docs/governance/store-RemediationBacklog.html#deliberate--accepted-record-the-choice-dont-silently-leave-low","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate / accepted (record the choice; don't silently leave low)","x":"- ADR-042 device capability abstraction (latent, drift plan D8). The core extension point is converged (Store wires browser + MAUI capabilities via…","i":"AddBrowserDeviceCapabilities CultureInfo.InvariantCulture LocalizedTextConventionTests TranslationCompletenessTests UseMauiDeviceCapabilities Money.ToDisplayString ProductVariantChanged MMCA.Common.UI.Maui SliceCohesionTests DeepLinkListener ResxMudLocalizer DeviceUIModule"},{"u":"/docs/governance/store-RemediationBacklog.html#below-maturity-4-tracking-inclusion-policy-categories-scoring--4-maturity","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟠 Below-maturity-4 tracking (inclusion policy: categories scoring < 4 maturity)","x":"These categories score maturity 3; the ledger records them per its own line-4 inclusion policy (mirrors ADC's equivalent entries). - [x] 19 · State Management & Data Flow ·…","i":"StateManagementConventionTestsBase UIArchitectureConventionTestsBase StateManagementConventionTests UIArchitectureConventionTests ProductDetail.razor.cs OrderDetail.razor.cs ProductVariantsPanel StoreArchitectureMap OrderSummaryPanel OrderLinesPanel OPERATIONS.md sloAlertSpecs"},{"u":"/docs/governance/store-RemediationBacklog.html#resolved-this-cycle-2026-07-28-drift-wave-d1d2d5d6d7--e2e4e7e8","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Resolved this cycle (2026-07-28, drift wave: D1/D2/D5/D6/D7 + E2/E4/E7/E8)","x":"- [x] 29 Resilience: the DR drill was restoring a RETIRED database. The weekly dr-drill.yml had no rotation and fell through to its input default MMCAStore, the legacy archive no…","i":"AuthControllerBase.LoginAsync HandlerResultConventionTests PaymentReconciliationService DecoratorPipelineOrderTests PeriodicBackgroundService AddCommonRateLimiting skip_freshness_gates alertEmailAddress authIpPermitLimit Store_Identity RegisterAsync Store_Catalog"},{"u":"/docs/governance/store-RemediationBacklog.html#resolved-this-cycle-2026-07-25-performance-program-2","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Resolved this cycle (2026-07-25, performance program 2)","x":"Second evidence-led performance pass over Common/ADC/Store. Store's share shipped as two PRs plus the v1.127.0 framework sweep. - [x] Output cache shared across replicas. Catalog…","i":"AddStackExchangeRedisOutputCache Filter.Operator.NotSupported GetVariantCartInfoHandler BulkSetInventoryHandler IProductVariantService GetUnitPricesAsync IDistributedCache IntFilterStrategy OrderLines.Count PaymentInitiated EvictByTagAsync ProductVariants"},{"u":"/docs/governance/store-RemediationBacklog.html#resolved-this-cycle-2026-07-11-drift-convergence-drift-plan-d1-d13","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Resolved this cycle (2026-07-11 drift-convergence, drift plan D1-D13)","x":"- [x] 29 DR gates (drift plan D3). dr-freshness is now in deploy.needs (fails a deploy when the last successful dr-drill is stale), dr-drill.yml gained a weekly cron, and…","i":"ConstructorDependencyCountTests MMCA.Store.Gateway.Tests GracefulShutdownTests MMCA.Store.CI.slnf Store_Identity Store_Catalog workflow_call deploy.needs TimeProvider Store_Sales Directory db_owner"},{"u":"/docs/governance/store-RemediationBacklog.html#already-at-level-4-protect-dont-regress","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"✅ Already at level 4 (protect, don't regress)","x":"Both axes satisfied (maturity 4 AND implementation = 9), the true protect list: SOLID (1), Design Patterns (2), Clean Architecture (3), DDD (4), Data (8), API (9), Observability…","i":"CK_InventoryItem_AvailableQuantity_NonNegative FormsConventionTests IQueryable"},{"u":"/docs/guides/index.html","d":"Guides & Specifications","k":"Guides & Specifications","x":"The narrative documentation for the MMCA platform: adoption guides, business specifications, workflow analyses, and per-concern reference notes. Files are prefixed by the repo…"},{"u":"/docs/guides/index.html#framework-mmcacommon","d":"Guides & Specifications","k":"Guides & Specifications","t":"Framework (MMCA.Common)","x":"- Getting Started: stand up a new application from the MMCA.Templates scaffold, in six steps. - Build MMCA.ECommerce: the two-module store sample (Products + Orders) built end to…","i":"MMCA.Templates"},{"u":"/docs/guides/index.html#mmcastore-e-commerce","d":"Guides & Specifications","k":"Guides & Specifications","t":"MMCA.Store (e-commerce)","x":"- Business Specification - Business Workflow Analysis - Navigation Flow - Manual Screen-Reader Pass Runbook"},{"u":"/docs/guides/index.html#mmcaadc-conference","d":"Guides & Specifications","k":"Guides & Specifications","t":"MMCA.ADC (conference)","x":"- Business Specifications - Navigation Flow - Manual Screen-Reader Pass Runbook - Integration-Test Tier Rework Plan Related reading: the Architecture Decision Records and the…"},{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","x":"The automated layer (axe-core WCAG 2.1 AA scans in Tests/E2E/MMCA.ADC.E2E.Tests/AccessibilityTests.cs plus the shared Login/Register/Profile bases in MMCA.Common.Testing.E2E)…","i":"MMCA.Common.Testing.E2E RemediationBacklog.md"},{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#tools","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Tools","x":"Run against the app started via Aspire (dotnet run --project Source/Hosting/MMCA.ADC.AppHost), reaching the UI through the Gateway. Test with the keyboard only (no mouse) for the…","i":"dotnet start Ctrl Alt Cmd run"},{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#what-to-verify-on-every-flow","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"What to verify on every flow","x":"1. Landmarks and headings. The SR's landmark/heading list (NVDA Insert+F7) exposes a logical banner / navigation / main structure and a sensible h1...hN outline (one h1 per…","i":"MainLayout.razor navigation h1...hN banner Insert Shift main Tab"},{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#results-log","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Results log","x":"Record one row per pass. A flow with any unresolved blocker is a FAIL (open a backlog item for it)."},{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#after-a-pass","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"After a pass","x":"- File any defect as a remediation item; cross-link it from the §21 row's evidence. - Update the dated row above so the scorecard can cite a real, current manual pass (this is…"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","x":"Status: complete (Phase 4 broker-transport tier landed 2026-07-06; Phase 5 residual = coverlet only). - Phase 0 ✅: Tests/WebAPI revived as MMCA.Common.API middleware unit tests…","i":"Microsoft.Testing.Extensions.CodeCoverage ISessionBookmarkValidationService IdentityIntegrationTestFixture SpeakerUnlinkedFromUserHandler AnonymousConferenceReadTests SpeakerLinkedToUserHandler MMCA.ADC.Integration.slnf MMCA.ADC.IntegrationTests IIntegrationEventHandler AddForwardedJwtBearer AttendeeBookmarkTests IBookmarkCountService"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#recommended-strategy-two-tiers","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Recommended strategy: two tiers","x":"1. Primary: per-service WebApplicationFactory : one in-process host per service (Identity / Conference / Engagement), cross-service edges mocked. AddBrokerMessaging…","i":"DistributedApplicationTestingBuilder SpeakerUnlinkedFromUser WebApplicationFactory SpeakerLinkedToUser AddBrokerMessaging UserRegistered Program"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#three-code-facts-that-shape-the-rework-verified","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Three code facts that shape the rework (verified)","x":"- Only Conference.Service is WAF-incompatible: it ends with StartAsync() + self-HTTP/2 WarmupViaHttpAsync + WaitForShutdownAsync(). Identity/Engagement/Notification use…","i":"AddCommonAuthentication AddForwardedJwtBearer WebApplicationFactory WaitForShutdownAsync Conference.Service WarmupViaHttpAsync JwtTokenGenerator IssuerSigningKey JwtBearerOptions app.RunAsync StartAsync authority"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#databases","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Databases","x":"- SQLite in-memory for the fast bulk tier (no Docker, CI-friendly; DatabaseInitStrategy=EnsureCreated). - MsSql Testcontainers for a tagged SQL-fidelity subset (soft-delete…","i":"SQLServerDbContext DataSources migrations"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#project-structure","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Project structure","x":"- One WAF test project per service (MMCA.ADC.{Identity,Conference,Engagement}.IntegrationTests): can't reference two Program-bearing hosts in one project. - One…","i":"MMCA.ADC.CrossService.IntegrationTests IntegrationTestBase MMCA.Common.Testing JwtTokenGenerator IntegrationTests ProjectReference MMCA.Common.API WebAPI.Tests Conference Engagement Identity MMCA.ADC"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#ci","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"CI","x":"- Add the SQLite per-service tier to CI.slnf (seconds, no Docker) → restores the authz/CRUD merge gate (11) with no workflow change. - Keep the container-based MsSql + RabbitMQ…","i":"CI.slnf"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#phased-sequencing-fastest-win-first","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Phased sequencing (fastest win first)","x":"- Phase 0: re-home WebAPI.Tests middleware unit tests; drop the dead WebAPI reference; re-add to slnx+CI.slnf. ~16 tests green; removes a non-building project (16). - Phase 1:…","i":"ISessionBookmarkValidationService IBookmarkCountService OwnerOrAdminFilter ServiceTestFixture JwtBearerOptions AttendeeClaims OrganizerUser WebAPI.Tests TProgram CI.slnf slnx"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#key-risks","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Key risks","x":"- The non-Identity JwtBearerOptions in-process override is the trickiest piece: prove it on one Conference auth test before fanning out. - SQLite vs SQL-Server fidelity (owned…","i":"JwtBearerOptions"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#critical-files","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Critical files","x":"- Tests/Integration/MMCA.ADC.IntegrationTests/Infrastructure/TestWebApplicationFactory.cs (combined-host factory → split into per-service fixtures; its JWT config block is the…","i":"AddCommonAuthentication AddForwardedJwtBearer JwtTokenGenerator.cs MMCA.ADC.CI.slnf MMCA.ADC.slnx StartAsync partial Program public class"},{"u":"/docs/guides/adc-NavigationFlow.html","d":"Navigation Flow","k":"Guides & Specifications","x":"This document maps the site navigation flow for each actor in the MMCA.ADC application. Each mermaid diagram shows the pages accessible to that actor and the directional…"},{"u":"/docs/guides/adc-NavigationFlow.html#actors","d":"Navigation Flow","k":"Guides & Specifications","t":"Actors","x":"Roles & menu: Organizer is the only elevated role (default is Attendee). A Speaker is an attendee whose account is linked to a Speaker, surfaced via the speakerid claim. The left…","i":"IUIModule.NavItems speaker_id Organizer Attendee"},{"u":"/docs/guides/adc-NavigationFlow.html#1-anonymous-user","d":"Navigation Flow","k":"Guides & Specifications","t":"1. Anonymous User","x":"Pages accessible without authentication: home, login, register, and all public conference pages. ---"},{"u":"/docs/guides/adc-NavigationFlow.html#2-attendee-authenticated-user","d":"Navigation Flow","k":"Guides & Specifications","t":"2. Attendee (Authenticated User)","x":"Inherits all anonymous pages. Gains access to profile, feedback submission, and session bookmarking. Unauthenticated visitors are redirected to login. ---"},{"u":"/docs/guides/adc-NavigationFlow.html#3-speaker","d":"Navigation Flow","k":"Guides & Specifications","t":"3. Speaker","x":"Inherits all attendee pages. Gains access to the speaker dashboard for managing their own profile, viewing assigned sessions, and reviewing feedback ratings. ---"},{"u":"/docs/guides/adc-NavigationFlow.html#4-organizer","d":"Navigation Flow","k":"Guides & Specifications","t":"4. Organizer","x":"Authenticated users with the Organizer role. Inherits all attendee and public pages. Adds CRUD management for every conference entity (events, sessions, speakers, categories,…","i":"Organizer"},{"u":"/docs/guides/adc-NavigationFlow.html#5-functionality-flows-attendee--speaker","d":"Navigation Flow","k":"Guides & Specifications","t":"5. Functionality Flows (Attendee & Speaker)","x":"The diagrams in sections 1-4 map which pages each actor can reach. The diagrams below map how attendees and speakers accomplish each functionality, including inline actions…","i":"DeviceUIModule speaker_id route"},{"u":"/docs/guides/adc-NavigationFlow.html#navigation-patterns","d":"Navigation Flow","k":"Guides & Specifications","t":"Navigation Patterns","x":"- Unauthenticated users accessing protected pages are redirected to /login via the RedirectToLogin component. - Successful login/register redirects to Home (/) with a full page…","i":"RegisteredUser_AdminPages_ShouldBeForbidden Engagement.CheckIn IUIModule.NavItems Engagement.Points EventList.razor RedirectToLogin DeviceUIModule UserList.razor Routes.razor speaker_id attribute Authorize"},{"u":"/docs/guides/adc-specifications.html","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","x":"---"},{"u":"/docs/guides/adc-specifications.html#1-system-overview","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"1. System Overview","x":"ADC is a conference management system for the Atlanta Developers Conference. It provides backend services to manage multi-day conference events, sessions, speakers, rooms,…"},{"u":"/docs/guides/adc-specifications.html#2-domain-model","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"2. Domain Model","x":"Relationships: - Owns many Rooms (child entities) - Owns many EventSpeakers (child join entities linking Event ↔ Speaker) - Owns many EventQuestionAnswers (child feedback…","i":"Engagement.LivePolls Engagement.SessionQA User.LinkedSpeakerId Event.StartDate Session.EventId ContentEditor Event.EndDate EventSpeaker QuestionType Waitlisted Nominated Organizer"},{"u":"/docs/guides/adc-specifications.html#3-business-rules","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"3. Business Rules","x":"Reading guide: Some rules reference other rules defined later in the document (e.g., BR-63, BR-80 are defined in Section 10). Forward references use the BR- numbering…","i":"Event.QuestionModerationDefault Event.LastSessionizeRefreshBy Event.LastSessionizeRefreshOn SessionQuestionAnswerChanged SpeakerQuestionAnswerChanged EventQuestionAnswerChanged SessionCategoryItemChanged SpeakerCategoryItemChanged UserSessionBookmarkChanged Session.AccessibilityInfo Session.IsServiceSession SessionFeedbackSubmitted"},{"u":"/docs/guides/adc-specifications.html#4-use-cases--business-processes","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"4. Use Cases / Business Processes","x":"See UC-30 (Registration) and UC-31 (Login) in Section 12.2 for the current email + password authentication flows. --- Actors: Attendee, API consumer Preconditions: None (read…","i":"SpeakerQuestionAnswersController Event.LastSessionizeRefreshBy Event.LastSessionizeRefreshOn SpeakerQuestionAnswerChanged Engagement.LivePolls Engagement.SessionQA UserSessionBookmark skippedSoftDeleted IsServiceSession IsPlenumSession AllowAnonymous QuestionEntity"},{"u":"/docs/guides/adc-specifications.html#5-workflows--state-transitions","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"5. Workflows & State Transitions","x":"The Session.Status field is a free-text string imported from Sessionize. Default: null (for manually created sessions). Known Sessionize values: Accepted, Waitlisted, Accept…","i":"Session.Status ContentEditor IsConfirmed IsInformed Waitlisted Nominated Organizer Accepted Declined Decline Accept Queue"},{"u":"/docs/guides/adc-specifications.html#6-events--side-effects","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"6. Events & Side Effects","x":"Domain events are raised for entity mutations. Not all events have registered handlers: events without handlers serve as extension points for future requirements. Note: Only…","i":"SessionQuestionAnswerChanged SpeakerQuestionAnswerChanged EventQuestionAnswerChanged SessionCategoryItemChanged SpeakerCategoryItemChanged UserSessionBookmarkChanged SessionSpeakerChanged User.LinkedSpeakerId CategoryItemChanged EventSpeakerChanged UserPasswordChanged CategoryChanged"},{"u":"/docs/guides/adc-specifications.html#7-business-constraints--invariants","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"7. Business Constraints & Invariants","x":"---","i":"Speaker.LinkedUserId User.LinkedSpeakerId IsServiceSession Session.EventId QuestionEntity ContentEditor EventSpeaker nameProperty Waitlisted CreatedBy FirstName Nominated"},{"u":"/docs/guides/adc-specifications.html#8-external-integrations-business-perspective","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"8. External Integrations (Business Perspective)","x":"---","i":"Speaker.ProfilePicture Event.VenueMapUrl IsServiceSession IsPlenumSession QuestionSource SessionizeCode IsTopSpeaker RecordingUrl LiveUrl POST"},{"u":"/docs/guides/adc-specifications.html#9-glossary","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"9. Glossary","x":"---","i":"Event.IsPublished IsServiceSession IsPlenumSession ContentEditor IsTopSpeaker Organizer User.Role Admin Role true"},{"u":"/docs/guides/adc-specifications.html#ddd-structural-summary","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"DDD Structural Summary","x":"Why three bounded contexts instead of two: The original Events + Identity split grouped all conference-related entities together regardless of write profile. Separating…","i":"SessionQuestionAnswer SpeakerQuestionAnswer EventQuestionAnswer Question.IsRequired Session.EventId QuestionEntity SpeakerChanged ContentEditor QuestionType Room.EventId RoomChanged Organizer"},{"u":"/docs/guides/adc-specifications.html#10-specification-clarifications--addenda","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"10. Specification Clarifications & Addenda","x":"This section addresses gaps, ambiguities, and implicit design decisions identified during implementation review. New business rules are numbered BR-61+. API contract…","i":"SessionQuestionAnswersController SpeakerQuestionAnswersController TimeZoneInfo.ConvertTimeFromUtc EventQuestionAnswersController MMCA.ADC.Modules.Engagement RemoveSpeakerQuestionAnswer UpdateSpeakerQuestionAnswer AddSpeakerQuestionAnswer SessionFeedbackSubmitted EventFeedbackSubmitted CreateQuestionHandler SessionQuestionAnswer"},{"u":"/docs/guides/adc-specifications.html#11-api-contract-specifications","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"11. API Contract Specifications","x":"This section documents API design decisions that apply across all endpoints. --- All error responses use the RFC 9457 ProblemDetails format (the successor to RFC 7807, same…","i":"SessionQuestionAnswer SpeakerQuestionAnswer EventQuestionAnswer PaginationMetadata Session.Duration Speaker.FullName DomainException includeChildren FirstRowOnPage LastModifiedOn QuestionEntity TotalPageCount"},{"u":"/docs/guides/adc-specifications.html#12-authentication--identity-architecture","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"12. Authentication & Identity Architecture","x":"This section defines the authentication mechanism for both the Web UI (Blazor) and MAUI (mobile) clients, which share a common Razor class library. It replaces the device-based…","i":"CascadingAuthenticationState AuthenticationStateProvider Speaker.LinkedUserId User.LinkedSpeakerId UserPasswordChanged RefreshTokenExpiry UserIdentifierType currentPassword LinkedSpeakerId AllowAnonymous LastModifiedBy LastModifiedOn"},{"u":"/docs/guides/common-ACCESSIBILITY.html","d":"Accessibility (rubric §21)","k":"Guides & Specifications","x":"The shared MMCA.Common.UI surface targets WCAG 2.1 AA. Accessibility is enforced two ways: an automated axe-core gate in CI (the bulk of coverage) and a documented manual…","i":"MMCA.Common.UI"},{"u":"/docs/guides/common-ACCESSIBILITY.html#automated-coverage-axe-core-wcag-21-aa","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Automated coverage (axe-core, WCAG 2.1 AA)","x":"The ui-e2e CI job runs Playwright + axe-core against the backend-less gallery; chromium is the blocking merge gate (firefox/webkit advisory). Scanned states: Component render is…","i":"PrimitivesSnapshotTests RegisterPageE2ETests PrimaryContrastText ErrorContrastText DarkModeE2ETests PageLoadingState MMCA.Common.UI progressbar mmca_theme div"},{"u":"/docs/guides/common-ACCESSIBILITY.html#manual-screen-reader-pass","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Manual screen-reader pass","x":"Automation cannot judge reading order, focus management, or announcement quality, so the shared surface is walked manually. Checklist (re-run on any change to MainLayout, the…","i":"ValidationMessage MainLayout.razor PageLoadingState MainLayout EditForm main"},{"u":"/docs/guides/common-ACCESSIBILITY.html#known-limitations-tracked","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Known limitations (tracked)","x":"- ~~Dark-mode contrast (§20, not §21).~~ RESOLVED (2026-07-11). The two dark-palette WCAG AA contrast failures the prototype scan flagged (filled-primary button label ~2.65:1 on…","i":"PaletteDark.PrimaryContrastText WarningContrastText ErrorContrastText DarkModeE2ETests EF5350 rgba"},{"u":"/docs/guides/common-BUILD-BY-HAND.html","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","x":"This is the long-form walkthrough: every project, every file, and every load-bearing line that goes into an application on the MMCA.Common framework, in the order you would…","i":"Contoso.Support Tickets dotnet Orders Order new"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#what-you-will-build","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"What you will build","x":"A modular monolith with one business module and two hosts: - Orders (your business module): an Order aggregate with OrderComment children, opened through a Result-returning…","i":"AllowAnonymous OrderComment Result Order sql web"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-0-prerequisites-and-decisions","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 0: Prerequisites and decisions","x":"Install: - .NET 10 SDK (the framework targets net10.0 with LangVersion: preview for C extension types). - SQL Server reachable locally (LocalDB, a container, or the one Aspire…","i":"FrameworkVersionConsistencyTestsBase Directory.Packages.props MMCA.Common.API UseLocalMMCA LangVersion local.props install net10.0 package preview CS0103 dotnet"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-1-create-the-solution-and-the-build-plumbing","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 1: Create the solution and the build plumbing","x":"Scaffolded. dotnet new mmca-app writes every file in this phase. Read it to know what each one does; you do not need to type any of it. The plumbing files are the load-bearing,…","i":"Directory.Packages.props Directory.Build.props Contoso.Support.slnx local.props.template OrderIdentifierType PackageReference MMCA.Helpdesk auditSources editorconfig nuget.config global.json Contracts"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-2-scaffold-the-module-project-set","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 2: Scaffold the module project set","x":"Scaffolded. dotnet new mmca-app creates this project set for your first module, and pwsh build/add-module.ps1 adds another one later: it drives dotnet new mmca-module and then…","i":"Contoso.Support.Orders.Infrastructure Contoso.Support.Orders.Application Contoso.Support.Orders.Domain Contoso.Support.Orders.Shared Contoso.Support.Orders.API MMCA.Common.Infrastructure MMCA.Common.Application MMCA.Common.Domain MMCA.Common.Shared AddErrorResources MMCA.Common.API AllowAnonymous"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-3-the-vertical-slice-end-to-end-the-heart-of-it","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 3: The vertical slice end-to-end (the heart of it)","x":"Scaffolded. The generated module already contains this slice and six more, worked end to end. dotnet new mmca-command and dotnet new mmca-query add another one. This phase is the…","i":"EntityTypeConfigurationSQLServer ConcurrencyConventionTestsBase AddModuleOrdersInfrastructure ScanModuleApplicationServices AuditableAggregateRootEntity OrderOpenedIntegrationEvent OrderCommentIdentifierType IUnitOfWork.GetRepository AddApplicationDecorators IIntegrationEventHandler DomainEventDispatcher EntityControllerBase"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-4-dbcontext-model-and-migrations","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 4: DbContext model and migrations","x":"Partly scaffolded. The migrations project and its design-time factory are generated. Running dotnet ef migrations add InitialCreate is still yours, and for a module added later…","i":"ApplicationSettings.DatabaseInitStrategy InitializeDatabaseAsync SQLServerDbContext EnsureCreated InitialCreate DataSources migrations Migrate dotnet None add"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-5-compose-the-monolith-host-and-run-it","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 5: Compose the monolith host and run it","x":"Scaffolded. Both hosts, the AppHost, and the .resx pairs are generated. Read this phase before you touch any of them: the DI sequence, WaitFor(sql) rather than the database…","i":"MmcaCultureBootstrap.SetBrowserCultureAsync LocalizedTextConventionTestsBase LocalizationResourceTestsBase UseCommonRequestLocalization OrderOpenedIntegrationEvent UseCommonMiddlewarePipeline services.AddErrorResources AddApplicationDecorators YourModuleErrorResources EnsureSuccessStatusCode EndpointCultureApplier UseRequestLocalization"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-6-tests-and-the-architecture-fitness-map","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 6: Tests and the architecture-fitness map","x":"Scaffolded, with one deliberate gap. All three test projects and the map are generated. The IntegrationEventContractTests subclass is NOT: its frozen literal lists members…","i":"FrameworkVersionConsistencyTestsBase ConstructorDependencyCountTestsBase IntegrationEventContractTestsBase LocalizedTextConventionTestsBase MMCA.Common.Testing.Architecture SpecificationConventionTestsBase MicroserviceExtractionTestsBase ConcurrencyConventionTestsBase ControllerConventionTestsBase IntegrationEventContractTests LocalizationResourceTestsBase HandlerConventionTestsBase"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-7-upgrading-the-framework-version","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 7: Upgrading the framework version","x":"Not scaffolded. dotnet new mmca-app --framework-version picks the version you START on; moving to a later one is this phase. When a new MMCA.Common release ships, upgrade in one…","i":"FrameworkVersionConsistencyTestsBase Directory.Packages.props packages.lock.json UseLocalMMCA local.props your.slnx restore dotnet new"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-8-extract-a-module-into-its-own-service-the-payoff","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 8: Extract a module into its own service (the payoff)","x":"Not scaffolded. The generated solution carries the plumbing (the .Contracts proto convention and the .Service OpenAPI block in Directory.Build.props), but the extraction itself…","i":"GrpcResultExceptionInterceptor OrderOpenedIntegrationEvent MMCA.Common.Aspire.Hosting WithSQLServerDataSource AddGrpcServiceDefaults Directory.Build.props RequestVersionExact AddTypedGrpcClient WithJwksDiscovery MMCA.Common.Grpc Support_Identity OutboxMessages"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#verification-checklist","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Verification checklist","x":"1. Build green: dotnet build Contoso.Support.slnx with no warnings (TreatWarningsAsErrors + five analyzers). This is the primary automatable gate. 2. Unit + architecture tests…","i":"OrderOpenedIntegrationEvent Contoso.Support.slnx IArchitectureMap OutboxMessages InitialCreate OrderComment migrations AppHost dotnet build Order test"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#where-to-look-next","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Where to look next","x":"- Getting Started: the one-command path that writes phases 1 through 6 for you. If you are starting a new solution rather than adding the framework to an existing one, that is…","i":"CLAUDE.md README.md Helpdesk Tickets Ticket"},{"u":"/docs/guides/common-COST.html","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","x":"MMCA.Common is a library, so it cannot provision anything: right-sizing, scale rules, budgets, and per-service cost attribution live in the consumer apps' IaC (e.g. MMCA.ADC's…"},{"u":"/docs/guides/common-COST.html#what-the-framework-does-for-cost","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"What the framework does for cost","x":"- Telemetry ingestion is the real line item, so high-volume / low-value spans are dropped. OutboxPollFilterProcessor (MMCA.Common.Aspire) suppresses the recurring OutboxPoll…","i":"http.client.open_connections OutboxPollFilterProcessor TraceIdRatioBasedSampler ConfigureOpenTelemetry OutboxCleanupService AddServiceDefaults MMCA.Common.Aspire ParentBasedSampler SocketsHttpHandler request.duration active_requests AppDependencies"},{"u":"/docs/guides/common-COST.html#recommended-consumer-defaults-set-these-downstream","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Recommended consumer defaults (set these downstream)","x":"- Telemetry retention & sampling. Tune Log Analytics retention to the minimum the consumer's compliance window allows, and set Telemetry:TracesSampleRatio (the built-in…"},{"u":"/docs/guides/common-COST.html#cost-attribution--guardrail-samples-distilled-from-mmcaadc","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Cost-attribution & guardrail samples (distilled from MMCA.ADC)","x":"These belong in the consumer's IaC, not the library, but the framework documents the shape so every consumer attributes spend and guards surges the same way. The worked, deployed…"},{"u":"/docs/guides/common-COST.html#out-of-scope-for-the-framework-by-design","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Out of scope for the framework (by design)","x":"Provisioning, scale rules, budgets, per-service cost attribution, and surge/revert automation are consumer/IaC concerns and are not added to the library: see also ADR-009…"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","x":"MMCA.ECommerce is the simplest e-commerce application on the MMCA.Common framework: a Products catalog module and an Orders module with line items, behind a REST API host and a…","i":"MMCA.Templates dotnet new"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#before-you-start","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Before you start","x":"- .NET 10 SDK (the framework targets net10.0 with LangVersion: preview). - Docker Desktop (Aspire provisions SQL Server as a container). - EF Core tools: dotnet tool install…","i":"MMCA.Templates LangVersion install net10.0 preview dotnet pwsh tool ps1"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#2-generate-the-solution-with-the-products-module","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"2. Generate the solution with the Products module","x":"Five options do most of this guide's old work. Three remove an axis a catalog product does not have, and the code for an axis you turn off is never generated: --flat drops the…","i":"ProductCreatedIntegrationEvent ProductCreatedHandler RequesterUserId Created Opened Name"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#3-add-the-orders-module","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"3. Add the Orders module","x":"build/add-module.ps1 ships inside the solution you just generated. It runs dotnet new mmca-module with the shape options passed through, then performs every wire-up the template…","i":"ECommerceArchitectureMap.cs SQLServerMigrationsAssembly services.AddErrorResources OrderItemIdentifierType WithSQLServerDataSource Directory.Build.props OrdersErrorResources MMCA.ECommerce.slnx ChangeItemQuantity ECommerce_Products appsettings.json ProjectReference"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#4-reshape-products-into-a-catalog-product","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"4. Reshape Products into a catalog product","x":"The scaffolded module arrives as the template's worked example in your namespaces, already shaped by the flags in step 2: no children, no status, no requester, Name instead of…","i":"UpdateRequestsAreConcurrencyAware Microsoft.EntityFrameworkCore Product.Description.TooLong ModuleApplicationDbContext ProductCreateRequestMapper DomainEntityState.Updated Product.Description.Empty Directory.Packages.props DependencyInjection.cs TreatWarningsAsErrors Product.InvalidPrice Product.Name.TooLong"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#5-reshape-orders-into-an-order-with-line-items","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"5. Reshape Orders into an order with line items","x":"Orders keeps the child-collection pattern the template scaffolded, retargeted. -Child Item already did the naming (the entity is OrderItem, the slices are AddItem / EditItem /…","i":"UpdateRequestsAreConcurrencyAware Order.Item.ProductName.TooLong Total_ExcludesSoftDeletedItems EnsureStatusAllowsItemChanges Microsoft.EntityFrameworkCore Order.InvalidStatusTransition Order.Item.ProductName.Empty ChangeOrderStatusRequest.cs OrderPlacedIntegrationEvent ModuleApplicationDbContext Order.CustomerName.TooLong ChangeItemQuantityCommand"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#6-point-the-ui-at-the-new-domain","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"6. Point the UI at the new domain","x":"The scaffolded Blazor host already has the load-bearing parts: the typed ECommerceApiClient calling the API server-side through Aspire service discovery (no CORS, no token), the…","i":"MMCA.ECommerce.Orders.Shared string.IsNullOrWhiteSpace Snackbar.RequiredFields Dialog.Delete.Heading System.Globalization GetProductsAsync ProjectReference missingRequired SectionHeading PageHeading es.resx _field"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#7-create-the-migrations","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"7. Create the migrations","x":"Neither module has a migration yet: any shape flag makes mmca-app drop the template's sample one (it described the sample shape), and -SkipMigration deferred the Orders one to…","i":"editorconfig migrations Migrations dotnet add"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#8-the-two-one-time-fixups-then-run-it","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"8. The two one-time fixups, then run it","x":"Apply the two fixups the scaffold deliberately leaves to you (they are name-dependent, so no generated value could be right). First, sort the using directives and the identifier…","i":"ProductCreatedIntegrationEvent IntegrationEventContractTests OrderPlacedIntegrationEvent ArchitectureTests.cs AllowAnonymous editorconfig SCAFFOLD IDE0021 SA1210 SA1211 DELTA Open"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#verification-checklist","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Verification checklist","x":"1. Baseline green immediately after mmca-app, before any edit: 81 tests. 2. After build/add-module.ps1: still green at 99 tests, both modules' scaffolded suites running. 3. After…","i":"MMCA.ECommerce.slnx OutboxMessages InitialCreate dotnet build test"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#where-to-look-next","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Where to look next","x":"- MMCA.ECommerce: the finished result of this guide, build- and test-verified. - Getting started: the single-module path, the vertical-slice templates (mmca-command /…"},{"u":"/docs/guides/common-GETTING-STARTED.html","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","x":"MMCA.Common is a .NET 10 framework for DDD, Clean Architecture, and CQRS, shipped as a set of lockstep-versioned NuGet packages (the authoritative list and count live in…"},{"u":"/docs/guides/common-GETTING-STARTED.html#before-you-start","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Before you start","x":"- .NET 10 SDK. The framework targets net10.0 with LangVersion: preview for C extension types. - Docker Desktop. Aspire provisions SQL Server as a container, so you do not install…","i":"MMCA.Templates LangVersion install net10.0 preview dotnet tool"},{"u":"/docs/guides/common-GETTING-STARTED.html#1-install-the-template-pack","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"1. Install the template pack","x":"Four templates arrive: mmca-app (a whole solution), mmca-module (a business module across all five layers), and mmca-command / mmca-query (a single vertical slice)."},{"u":"/docs/guides/common-GETTING-STARTED.html#2-generate-the-solution","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"2. Generate the solution","x":"Three names, and they are independent: the solution (also your root namespace), the first module in plural PascalCase, and that module's aggregate root in singular PascalCase.…","i":"ProjectReference local.props Billing Invoice"},{"u":"/docs/guides/common-GETTING-STARTED.html#3-build-and-test-before-you-change-anything","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"3. Build and test before you change anything","x":"That is a warning-free build with TreatWarningsAsErrors and all five analyzers at error severity, and a passing test run including the architecture-fitness rules, with no…","i":"TreatWarningsAsErrors"},{"u":"/docs/guides/common-GETTING-STARTED.html#4-create-the-first-migration","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"4. Create the first migration","x":"The scaffold ships the migrations project and its design-time factory; the migration itself describes your entities, so it is yours to generate: Always pass --context…","i":"SQLServerDbContext DbSet"},{"u":"/docs/guides/common-GETTING-STARTED.html#5-run-it","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"5. Run it","x":"Run this from a real, interactive terminal. Launched from a headless or background shell the Aspire AppHost stalls at control-plane init and no dashboard appears. The dashboard…","i":"OrderOpenedIntegrationEvent AllowAnonymous POST GET sql web"},{"u":"/docs/guides/common-GETTING-STARTED.html#6-the-two-one-time-fixups","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"6. The two one-time fixups","x":"The scaffold deliberately does not hand these over, because renaming invalidates them and no fixed value is right for every name you could pick. Both are covered in full in the…","i":"IntegrationEventContractTestsBase Contoso.Support.Orders.Shared Zeta.App.Orders.Shared ArchitectureTests.cs editorconfig SCAFFOLD IDE0021 SA1211 Ticket DELTA using"},{"u":"/docs/guides/common-GETTING-STARTED.html#what-you-were-handed","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"What you were handed","x":"The Order aggregate arrives fully worked: a Result-returning factory, invariants, guarded mutations raising domain events, a child entity, soft-delete cascade, the caching pair,…","i":"AddApplicationDecorators Directory.Build.props OrderIdentifierType IArchitectureMap HandleFailure ModuleLoader ErrorType WaitFor global Result DbSet Order"},{"u":"/docs/guides/common-GETTING-STARTED.html#add-your-next-feature","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Add your next feature","x":"A vertical slice (the path every feature follows) is one command, run from the module's UseCases folder: Handlers, validators, and mappers are convention-scanned, so there is no…","i":"order.TransferToRequester AddErrorResources RequesterUserId AddDomainEvent Result.Combine ChangeStatus GetByIdAsync SaveChanges definition IsFailure CacheKey Comments"},{"u":"/docs/guides/common-GETTING-STARTED.html#surface-the-slice-at-the-edge","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Surface the slice at the edge","x":"The scaffold stops at the handler, and the template's closing instructions tell you to map the command in your module's controller. Every write in the generated app follows the…","i":"ThrowIfDomainExceptionAsync _transferRequesterUserId ChangeOrderStatusRequest Api.TransferOrderAsync EntityControllerBase TransferOrderCommand OrderDetail.es.resx ICacheInvalidating ChangeStatusAsync OrderDetail.razor OrderDetail.resx SupportApiClient"},{"u":"/docs/guides/common-GETTING-STARTED.html#then-what","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Then what","x":"- Upgrade the framework. Bump every MMCA.Common. entry in Directory.Packages.props together, in one pass. See Phase 7 and the versioning policy. - Add real authentication. Copy…","i":"Directory.Packages.props Authorize Contracts Service"},{"u":"/docs/guides/common-GETTING-STARTED.html#verification-checklist","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Verification checklist","x":"1. dotnet new mmca-app -n produced a solution that builds and tests green before you changed anything. 2. dotnet build .slnx is warning-free (TreatWarningsAsErrors + five…","i":"OutboxMessages InitialCreate migrations healthy YourApp dotnet build slnx test then add new"},{"u":"/docs/guides/common-GETTING-STARTED.html#where-to-look-next","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Where to look next","x":"- Templates: every parameter of all four templates, dropping the Blazor UI host, and how the pack is built. ADR-065 explains why it is derived from the reference app rather than…"},{"u":"/docs/guides/common-RESILIENCE.html","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","x":"MMCA.Common is a library, so it cannot operate a deployment: restores, RTO/RPO, and SLO alerting are executed in the consumer apps' IaC (e.g. MMCA.ADC's…"},{"u":"/docs/guides/common-RESILIENCE.html#what-the-framework-provides-and-verifies-in-repo","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"What the framework provides (and verifies in-repo)","x":"Failure isolation, graceful degradation, graceful startup, and the restore procedure itself are therefore demonstrated and tested centrally: the framework drills backup→restore…","i":"ResilienceCircuitBreakerFaultInjectionTests OpenIdConnectMetadataWarmupTask WarmupReadinessHealthCheckTests AddStandardResilienceHandler ConfigureHttpClientDefaults WarmupReadinessHealthCheck DatabaseRestoreDrillTests ConfigureBrokerTransport WarmupHostedServiceTests WarmupReadinessGateTests ResilienceHandlerTests WarmupHostedService"},{"u":"/docs/guides/common-RESILIENCE.html#baseline-slo--error-budget-template-consumers-fill-in","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"Baseline SLO / error-budget template (consumers fill in)","x":"Adopt and tune per app; ADC's filled-in version lives in infra/DISASTER-RECOVERY.md + the SLO metric-alerts in infra/main.bicep. Define RTO/RPO per service (ADC's worked…","i":"requests"},{"u":"/docs/guides/common-RESILIENCE.html#restore-drill-runbook-reference","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"Restore-drill runbook (reference)","x":"The only evidence backups actually restore is a periodic drill: restore a throwaway copy, confirm it comes back Online, record the measured restore time, then delete the copy.…"},{"u":"/docs/guides/common-RESPONSIVE.html","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","x":"This document is the supported-device and browser matrix for the shared MMCA.Common.UI component library. It makes the responsive contract explicit (the rubric §22 note that it…","i":"MMCA.Common.UI"},{"u":"/docs/guides/common-RESPONSIVE.html#breakpoints","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Breakpoints","x":"The framework keeps C viewport detection and CSS media queries aligned around one mobile threshold. The C 960px mobile cutoff and the CSS 1023.98px cutoff intentionally differ:…","i":"BreakpointConstants.IsMobileBreakpoint media i.e"},{"u":"/docs/guides/common-RESPONSIVE.html#touch-targets","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Touch targets","x":"Interactive controls on mobile surfaces meet a 48px minimum hit area (Material Design), exceeding both WCAG 2.5.8 Target Size (Minimum, AA, 24px) and WCAG 2.5.5 Target Size…"},{"u":"/docs/guides/common-RESPONSIVE.html#grid-density","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Grid density","x":"DataGridListPageBase exposes a DenseGrid property and a ToggleDensity() method. Derived list pages bind Dense=\"@DenseGrid\" on their MudDataGrid and surface a toggle. The chosen…","i":"ListPageQueryStateServiceTests ListPageStateServiceTests DataGridListPageBase ToggleDensity MudDataGrid DenseGrid TDto"},{"u":"/docs/guides/common-RESPONSIVE.html#browser-matrix","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Browser matrix","x":"The shared UI is tested against three Playwright engines in CI (.github/workflows/ci.yml, ui-e2e job): a real-browser axe (WCAG 2.1 AA) + render smoke against the backend-less…","i":"false"},{"u":"/docs/guides/common-TEMPLATES.html","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","x":"MMCA.Templates is a dotnet new pack that scaffolds solutions, modules, and vertical slices on the MMCA.Common framework. It exists because standing up a new app by hand meant 12…","i":"MMCA.Templates UseCases dotnet new"},{"u":"/docs/guides/common-TEMPLATES.html#mmca-app","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-app","x":"The module and aggregate names are independent, so --module Billing --aggregate Invoice is fine. Everything derived from them follows: routes, the Aspire database resource, the…","i":"IntegrationEventContractTestsBase Contoso.Support.Orders.Shared ShipmentLineIdentifierType Zeta.App.Orders.Shared ArchitectureTests.cs builder.AddProject ProjectReference Contoso.Support EditLineRequest RequesterUserId AddLineRequest AppHost.csproj"},{"u":"/docs/guides/common-TEMPLATES.html#mmca-module","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-module","x":"All six behave exactly as they do for mmca-app, and they are per module: a solution can hold a flat, status-less catalog module beside one whose aggregate owns a growing child…","i":"SQLServerMigrationsAssembly services.AddErrorResources Architecture.Tests.csproj OrderItemIdentifierType Directory.Build.props Migrations.SqlServer Contoso.Support ErrorResources ModuleLoader DataSources FirstModule RemoveItem"},{"u":"/docs/guides/common-TEMPLATES.html#buildadd-moduleps1","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"build/add-module.ps1","x":"Since 1.4.0 every solution mmca-app generates ships this script, and it is the supported way to add a second module. It runs mmca-module with your shape options passed through,…","i":"IntegrationEventContractTests migrations copyOnly dotnet diff Name slnx add git"},{"u":"/docs/guides/common-TEMPLATES.html#mmca-command-and-mmca-query","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-command and mmca-query","x":"Run these from the module's UseCases folder. Each creates a folder named after the slice holding its two files. --child-collection exists because both handlers load through…","i":"EntityControllerBase MMCA.Templates GetByIdAsync definition CacheKey Comments includes UseCases contain dotnet nameof Result"},{"u":"/docs/guides/common-TEMPLATES.html#how-the-pack-is-built","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"How the pack is built","x":"The template content is the MMCA.Helpdesk reference application itself, staged at pack time. There is no second copy of the solution, so the template cannot drift from the app…"},{"u":"/docs/guides/common-VERSIONING.html","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","x":"MMCA.Common publishes fifteen NuGet packages that are versioned and released together as a single unit. They share one version number so a consumer never has to reason about…","i":"MMCA.Common.UI.Maui release.yml"},{"u":"/docs/guides/common-VERSIONING.html#semantic-versioning","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Semantic Versioning","x":"Versions follow SemVer 2.0: MAJOR.MINOR.PATCH: - MAJOR: reserved (see \"Breaking changes within 1.x\" below). - MINOR: new capability, and the channel breaking changes currently…","i":"vMAJOR.MINOR.PATCH MAJOR.MINOR.PATCH v1.51.0"},{"u":"/docs/guides/common-VERSIONING.html#what-counts-as-breaking","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"What counts as breaking","x":"A change is breaking if it is any of: - Removing or renaming a public type/member, or changing a signature. - Changing the meaning of an existing configuration key, or changing a…","i":"Result"},{"u":"/docs/guides/common-VERSIONING.html#breaking-changes-within-1x","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Breaking changes within 1.x","x":"Breaking changes ship as MINOR bumps, not MAJOR ones, and the version number is therefore not a reliable breakage signal on its own. This is deliberate and follows from the…","i":"IIntegrationEventPublisher IntegrationEventPublisher WithSQLServerDataSource WithDataSource IEventBus v1.123.0 v1.79.0"},{"u":"/docs/guides/common-VERSIONING.html#consumer-rollout","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Consumer rollout","x":"Per project convention, framework upgrades are swept across all consumers in one pass: there are no opt-in flags or phased rollouts for a MMCA.Common change. When a release…"},{"u":"/docs/guides/common-VERSIONING.html#deprecation","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Deprecation","x":"There is no [Obsolete] grace period today. Because the lockstep sweep updates every first-party caller in the same change set, a superseded API is removed in the release that…","i":"Obsolete"},{"u":"/docs/guides/common-VERSIONING.html#supply-chain","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Supply chain","x":"- All package versions are centrally pinned (Directory.Packages.props). - NuGet lock files are committed for reproducible restores. - MassTransit is pinned to v8 by policy (v9…","i":"Directory.Packages.props MassTransit"},{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","x":"The automated layer (axe-core WCAG 2.1 AA scans in Tests/E2E/MMCA.Store.E2E.Tests/Workflows/AccessibilityTests.cs plus the shared Login/Register/Profile bases in…","i":"MMCA.Common.Testing.E2E"},{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#tools","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Tools","x":"Run against the app started via Aspire (dotnet run --project Source/Hosting/MMCA.Store.AppHost), reaching the Web UI at https://localhost:6002. Test with the keyboard only (no…","i":"dotnet start Ctrl Alt Cmd run"},{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#what-to-verify-on-every-flow","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"What to verify on every flow","x":"1. Landmarks and headings. The SR's landmark/heading list (NVDA Insert+F7) exposes a logical banner / navigation / main structure and a sensible h1...hN outline (one h1 per…","i":"Wcag21AaExceptMudPagerCombobox MainLayout.razor MMCA.Common.UI navigation h1...hN banner Insert Shift main Tab"},{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#results-log","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Results log","x":"Record one row per pass. A flow with any unresolved blocker is a FAIL (open a backlog item for it)."},{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#after-a-pass","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"After a pass","x":"- File any defect as a remediation item; cross-link it from the §21 row's evidence. - Update the dated row above so the scorecard can cite a real, current manual pass (this is…"},{"u":"/docs/guides/store-BusinessWorkflows.html","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications"},{"u":"/docs/guides/store-BusinessWorkflows.html#workflow-list-summary","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"Workflow List Summary","x":"---","i":"productId variantId imageId DELETE userId POST GET PUT"},{"u":"/docs/guides/store-BusinessWorkflows.html#1-identity-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"1. Identity Module Workflows","x":"Entry Point: POST /auth/register, AuthController.RegisterAsync(), AllowAnonymous Execution Path: Business Steps: 1. Validate registration input (email, password, first name, last…","i":"AuthController.RegisterAsync AuthController.LoginAsync User.RefreshTokenExpiry Customer.ChangeAddress CustomerAddressChanged Customer.ChangeEmail CustomerEmailChanged RequireAuthenticated Customer.ChangeName CustomerNameChanged User.RefreshToken CustomerCreated"},{"u":"/docs/guides/store-BusinessWorkflows.html#2-catalog-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"2. Catalog Module Workflows","x":"Entry Point: POST /categories, Admin only, [Idempotent] Response: 201 Created with CategoryDTO Entry Point: PUT /categories/{id}/name, Admin only Entry Point: PUT…","i":"CatalogFeatures.ProductImages ProductVariantPriceChanged ProductVariantCartInfoDTO ProductVariantSkuChanged IProductVariantService ProductVariantRemoved ProductNameChanged ParentCategoryId ProductImageData CategoryDeleted ProductImageDTO ProductDeleted"},{"u":"/docs/guides/store-BusinessWorkflows.html#3-sales-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"3. Sales Module Workflows","x":"Entry Point: POST /shoppingcarts/{customerId}/shoppingcartitems, Authenticated (owner or admin via OwnerOrAdminFilter) Decision Points: - Product variant doesn't exist - NotFound…","i":"ShoppingCartItemQuantityAdjusted InventoryItem.AvailableQuantity OrderPaymentFailedSagaHandler BulkSetInventoryResultDTO Order.InventoryRestored ProductVariant.NotFound ShoppingCartItemRemoved IProductVariantService ShoppingCartCheckedOut StripePaymentIntentId ShoppingCart.Status ShoppingCartCleared"},{"u":"/docs/guides/store-BusinessWorkflows.html#4-ui-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"4. UI Workflows","x":"The UI provides a complete shopping experience through the CartDrawer component and Blazor pages. The CartDrawer is the only cart UI: there is no dedicated cart page. It is a…","i":"ICartStateService IUIModule OnChange"},{"u":"/docs/guides/store-BusinessWorkflows.html#5-cross-module-interactions","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"5. Cross-Module Interactions","x":"Module dependency: Sales declares a hard dependency on Catalog (RequiresDependencies = true). When Catalog is disabled, a DisabledProductVariantService stub is registered and…","i":"DisabledProductVariantService IProductVariantService UserRegisteredHandler RequiresDependencies GetUnitPricesAsync GetIdBySkuAsync SkuExistsAsync ExistsAsync true"},{"u":"/docs/guides/store-BusinessWorkflows.html#6-external-interactions","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"6. External Interactions","x":"---","i":"StripePaymentService IDbContextFactory SmtpEmailSender"},{"u":"/docs/guides/store-BusinessWorkflows.html#7-cross-cutting-concerns-participating-in-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"7. Cross-Cutting Concerns Participating in Workflows","x":"---","i":"TransactionalCommandDecorator ProfilingCommandDecorator CachingCommandDecorator ProfilingQueryDecorator ICacheInvalidating OwnerOrAdminFilter IdempotencyFilter ITransactional ApiVersion Idempotent"},{"u":"/docs/guides/store-BusinessWorkflows.html#8-end-to-end-customer-journey","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"8. End-to-End Customer Journey","x":"Alternative Flows: - Payment fails - Order status PaymentFailed - customer can retry (create new Stripe session) - Cancel order - Status Cancelled (from PendingPayment,…","i":"StripePaymentIntentId PaymentFailed Cancelled"},{"u":"/docs/guides/store-BusinessWorkflows.html#9-potentially-missing-or-incomplete-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"9. Potentially Missing or Incomplete Workflows","x":"--- This document is derived from source code analysis. All workflows, decisions, and behaviors described above are confirmed implementations traceable to the referenced source…","i":"OrderPaymentFailedSagaHandler OrderCancelledSagaHandler MarkAsDelivered SmtpEmailSender User.Deactivate UserDeactivated"},{"u":"/docs/guides/store-NavigationFlow.html","d":"Navigation Flow","k":"Guides & Specifications","x":"This document maps the site navigation flow for each actor in the MMCA.Store application. Each mermaid diagram shows the pages accessible to that actor and the directional…","i":"NavigationFlow.md"},{"u":"/docs/guides/store-NavigationFlow.html#actors","d":"Navigation Flow","k":"Guides & Specifications","t":"Actors","x":"Roles and enforcement: Admin is the only elevated role (registration creates a Customer). The 14 admin pages carry page-level [Authorize(Roles = \"Admin\")], regression-gated in CI…","i":"customer_id Authorize Customer Admin Roles"},{"u":"/docs/guides/store-NavigationFlow.html#1-anonymous-user","d":"Navigation Flow","k":"Guides & Specifications","t":"1. Anonymous User","x":"Pages accessible without authentication: home, login, register, and the public catalog. Add-to-cart on the product detail page sits inside an AuthorizeView; an anonymous visitor…","i":"AuthorizeView"},{"u":"/docs/guides/store-NavigationFlow.html#2-customer-authenticated-user","d":"Navigation Flow","k":"Guides & Specifications","t":"2. Customer (Authenticated User)","x":"Inherits all anonymous pages. Gains the profile page, the cart drawer (a layout component, not a route), checkout, and their own orders. Unauthenticated visitors deep-linking to…","i":"OrphanOrderRecovery Specification Authorize"},{"u":"/docs/guides/store-NavigationFlow.html#3-admin","d":"Navigation Flow","k":"Guides & Specifications","t":"3. Admin","x":"Inherits all customer pages, plus the admin CRUD surfaces for all three modules. Every page below carries [Authorize(Roles = \"Admin\")]; a customer deep-linking to any of them…","i":"Authorize Roles"},{"u":"/docs/guides/store-NavigationFlow.html#authorization-model","d":"Navigation Flow","k":"Guides & Specifications","t":"Authorization Model","x":"Three cooperating layers; the API is always the boundary: 1. Page-level route guards. The 14 admin pages carry [Authorize(Roles = \"Admin\")] and /profile / /orders carry…","i":"OwnershipHelper.GetOwnershipSpecification OwnerOrAdminFilter mmca_auth_access AuthorizeView customer_id Authorize c4adff2 Roles"},{"u":"/docs/guides/store-Specification.html","d":"MMCA Business Specification Document","k":"Guides & Specifications"},{"u":"/docs/guides/store-Specification.html#1-system-overview","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"1. System Overview","x":"MMCA is an e-commerce platform built with .NET 10.0 using DDD and Clean Architecture. The business logic is organized as modules (Catalog, Sales, Identity) that have been…"},{"u":"/docs/guides/store-Specification.html#2-core-business-entities","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"2. Core Business Entities","x":"Description: A classification grouping for products. Supports hierarchical (parent-child) structures for nested categorization (e.g., \"Jewelry\" \"Rings\"). Key Properties:…"},{"u":"/docs/guides/store-Specification.html#3-business-workflows","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"3. Business Workflows","x":"Trigger: A new user submits registration with first name, last name, email, and password. Steps: 1. Validate registration request (email format, password requirements) 2. Verify…","i":"IInventoryAllocationService.DecrementAsync CatalogFeatures.ProductImages payment_intent.payment_failed EventUtility.ConstructEvent IProductImageStorageService checkout.session.completed ProductImageStorageService OrderCancelledSagaHandler checkout.session.expired Order.InventoryRestored IProductVariantService ExecuteUpdateAsync"},{"u":"/docs/guides/store-Specification.html#4-order-status-state-machine","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"4. Order Status State Machine","x":"Cancellable States: PendingPayment, PaymentInitiated, PaymentFailed Manual Payment States: PendingPayment, PaymentInitiated, PaymentFailed Terminal States: Cancelled, Delivered ---"},{"u":"/docs/guides/store-Specification.html#5-business-rules","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"5. Business Rules","x":"---","i":"ProductVariantConfiguration.cs InventoryItemInvariants.cs AdjustInventoryHandler.cs ShoppingCartInvariants.cs CategoryConfiguration.cs CheckOutDomainService.cs CustomerConfiguration.cs UserRegisteredHandler.cs CancelOrderHandler.cs CategoryInvariants.cs CustomerInvariants.cs AddressInvariants.cs"},{"u":"/docs/guides/store-Specification.html#6-use-cases","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"6. Use Cases","x":"---"},{"u":"/docs/guides/store-Specification.html#7-domain-events-and-state-changes","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"7. Domain Events and State Changes","x":"---"},{"u":"/docs/guides/store-Specification.html#8-external-integrations","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"8. External Integrations","x":"Purpose: Processes online customer payments for orders. Business Impact: Enables the system to collect payments from customers and confirm payment success or failure…","i":"payment_intent.payment_failed EventUtility.ConstructEvent checkout.session.completed checkout.session.expired Result.Failure StripeSettings WebhookSecret SecretKey"},{"u":"/docs/guides/store-Specification.html#9-authorization-model","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"9. Authorization Model","x":"Ownership Enforcement: The OwnerOrAdminFilter validates that the route parameter id (CustomerIdentifierType) matches the authenticated user's customer ID, or that the user has…","i":"OwnerOrAdminFilter customer_id user_id email role iat jti sub"},{"u":"/docs/guides/store-Specification.html#10-cross-module-communication","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"10. Cross-Module Communication","x":"The system enforces strict module boundaries. Modules communicate only through shared interface contracts: Confirmed behaviors: - Sales module cannot directly access Catalog…","i":"DisabledProductVariantService IProductVariantService RequiresDependencies true"},{"u":"/docs/guides/store-Specification.html#11-user-interface","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"11. User Interface","x":"The UI is a Blazor Server + WebAssembly hybrid (InteractiveAuto render mode) using MudBlazor component library. It supports multiple hosting targets: - Web (Server + WASM):…","i":"UIModuleConfiguration.IsModuleEnabled ICartStateService InteractiveAuto configuration moduleName IUIModule Assembly NavItems"},{"u":"/docs/guides/store-Specification.html#12-cross-cutting-infrastructure","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"12. Cross-Cutting Infrastructure","x":"The IdempotencyFilter (applied via [Idempotent] attribute on Create endpoints) caches the first response for a given Idempotency-Key header value for 24 hours. Duplicate requests…","i":"TransactionalCommandDecorator ProfilingCommandDecorator CachingCommandDecorator ProfilingQueryDecorator ICacheInvalidating IDataSourceService IDbContextFactory IdempotencyFilter ITransactional SemaphoreSlim UseDataSource Idempotent"},{"u":"/docs/guides/store-Specification.html#13-testing","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"13. Testing","x":"- Full customer journey: Register - Browse - Add to Cart - Checkout - Admin Pay - Deliver - Order lifecycle: all state transitions including cancellation with inventory…","i":"MMCA.Store.Integration.slnf MMCA.Store.IntegrationTests WebApplicationFactory STORE_TEST_SQL_BASE"},{"u":"/docs/guides/store-Specification.html#14-missing-or-unclear-business-logic","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"14. Missing or Unclear Business Logic","x":"Observation: The SMTP email service infrastructure is implemented, but no domain event handlers trigger email notifications for events like order confirmation, payment receipt,…","i":"InventoryItemsController InventoryItemList MarkAsDelivered User.Deactivate UserDeactivated CategoryId Delivered GetPaged GetById GetAll Lookup Paid"},{"u":"/docs/guides/store-Specification.html#15-seed-data-initial-system-state","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"15. Seed Data (Initial System State)","x":"The system seeds the following data at startup: Users: - Admin: one seeded administrator account (Admin role, no Customer record; credentials are environment-specific and not…","i":"ExistsAsync"},{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"Senior Software Architect Ivan Ball-llovera Cloud-native enterprise architecture on the Microsoft stack I design and ship production-grade .NET platforms: modular monoliths that…"},{"u":"/","t":"Architecture that earns its keep","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"I am a Senior Software Architect with more than 25 years designing and delivering scalable, cloud-native systems on the Microsoft stack. My focus is Domain-Driven Design, Clean…"},{"u":"/","t":"The MMCA platform","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"A production-grade .NET 10 framework and a set of reference apps that demonstrate modern enterprise architecture end to end. It is built as a modular monolith that extracts…"},{"u":"/","t":"Deep dives on enterprise .NET","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"A long-form series turning the framework's decisions into teachable patterns, every claim grounded in real source. The three most recent: Run & extract · No. 33 Resilience and…"},{"u":"/","t":"Speaking & giving back","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"I help run two community-driven Atlanta technology conferences and keep production-grade patterns free and in the open. See talks & community work Organizer & speaker Two Atlanta…"},{"u":"/resume.html","d":"Résumé","k":"Site","x":"Résumé Ivan Ball-llovera Senior Software Architect 25+ years designing and delivering scalable, cloud-native systems on the Microsoft stack: Domain-Driven Design, Clean…"},{"u":"/resume.html","t":"Professional summary","d":"Résumé","k":"Site","x":"Senior Software Architect with 25+ years designing and delivering scalable, cloud-native systems on the Microsoft stack. Deep expertise in Domain-Driven Design, Clean…"},{"u":"/resume.html","t":"Core competencies","d":"Résumé","k":"Site","x":"Architecture & design Domain-Driven Design, Clean Architecture, CQRS, Modular Monolith → Microservices, Event-Driven Architecture, Outbox Pattern, gRPC, API Gateway (YARP),…"},{"u":"/resume.html","t":"Professional experience","d":"Résumé","k":"Site","x":"Senior Software Engineer · Assurant June 2025 – Present · Architect-level scope: platform, security, and cross-team technical decisions Re-architected the AR.com renters quote…"},{"u":"/resume.html","t":"Featured project · MMCA platform","d":"Résumé","k":"Site","x":"Personal / open source · github.com/ivanball/MMCA.Common A production-grade .NET 10 reference platform demonstrating modern enterprise architecture end-to-end. The conference…"},{"u":"/resume.html","t":"Education","d":"Résumé","k":"Site","x":"B.S., Computer Science University of Havana (Faculty of Mathematics), Havana, Cuba (1994 – 1999)"},{"u":"/resume.html","t":"Languages","d":"Résumé","k":"Site","x":"English · Spanish (bilingual)"},{"u":"/resume.html","t":"Certifications","d":"Résumé","k":"Site","x":"✓ Azure Administrator Associate (AZ-104, 2025) ✓ Azure AI Fundamentals (AI-900, 2024) ✓ Azure Data Fundamentals (DP-900, 2021) ✓ Azure Fundamentals (AZ-900, 2021) → In progress…"},{"u":"/resume.html","t":"Professional development","d":"Résumé","k":"Site","x":"Continuously prototypes emerging technologies, with a current focus on Clean Architecture using .NET 10, Blazor, .NET MAUI, and ASP.NET Core Web API, and on AI-assisted…"},{"u":"/platform.html","d":"The MMCA Platform","k":"Site","x":"Featured work · Open source The MMCA platform A production-grade .NET 10 platform that demonstrates modern enterprise architecture end to end: an open-source framework of fifteen…"},{"u":"/platform.html","t":"MMCA.Common","d":"The MMCA Platform","k":"Site","x":"A NuGet package framework for building modular-monolith applications with DDD, Clean Architecture, and CQRS, and the extension points to extract a module into its own…"},{"u":"/platform.html","t":"Three reference applications","d":"The MMCA Platform","k":"Site","x":"The framework is consumed by real apps. Each one exercises the patterns differently, and the conference app ran a live event on Azure. Conference MMCA.ADC A production-deployed…"},{"u":"/platform.html","t":"From one graph, laptop to cloud","d":"The MMCA Platform","k":"Site","x":"Orchestrated locally with .NET Aspire, then deployed to Azure Container Apps from the same declarative model. The .NET Aspire dashboard: services, databases, and the broker as…"},{"u":"/platform.html","t":"Architectural styles the codebase commits to","d":"The MMCA Platform","k":"Site","x":"The recurring ideas every repo is built on, summarized from the onboarding primer's orientation chapter. Each is taught in full at its first concrete appearance in the reference…"},{"u":"/platform.html","t":"A two-axis architecture scorecard","d":"The MMCA Platform","k":"Site","x":"Every repo is scored against a 34-category rubric on two axes, Maturity (how complete the decision is) and Implementation (how well it is realized in code), with evidence and…"},{"u":"/platform.html","t":"Architecture Decision Records","d":"The MMCA Platform","k":"Site","x":"84 ADRs capture the context and trade-offs behind each cross-cutting pattern, so the design is teachable, not tribal knowledge. Every entry below links to the full record. 001…"},{"u":"/platform.html","t":"The reference library","d":"The MMCA Platform","k":"Site","x":"The full architecture documentation, maintained in this site's repository as its canonical home and rendered as browsable pages: every Architecture Decision Record, the…"},{"u":"/platform.html","t":"Use it, read it, or follow along","d":"The MMCA Platform","k":"Site","x":"The framework is Apache 2.0 and published to nuget.org. The fastest way in is the reference app: one module wired end to end through all five layers, with the extraction path…"},{"u":"/platform.html","t":"Get each deep dive by email","d":"The MMCA Platform","k":"Site","x":"One message per article, no digests and no other mail. Email address Subscribe"},{"u":"/writing.html","d":"Writing","k":"Site","x":"Writing Deep dives on enterprise .NET A long-form series that turns the MMCA framework's architecture decisions into teachable patterns, every claim grounded in real source. Read…"},{"u":"/writing.html","t":"Get each deep dive by email","d":"Writing","k":"Site","x":"One message per article, no digests and no other mail. Email address Subscribe"},{"u":"/speaking.html","d":"Speaking & Community","k":"Site","x":"Speaking & community Talks and giving back For more than 20 years I have been an active contributor to the Microsoft developer communities in Atlanta and South Florida: teaching,…"},{"u":"/speaking.html","t":"Recent sessions","d":"Speaking & Community","k":"Site","x":"Atlanta Cloud + AI Conference · 2026 The App You're Using Right Now Building Atlanta Cloud + AI's own platform with Claude in the loop A field report, not a slide deck about…"},{"u":"/speaking.html","t":"Organizing two Atlanta conferences","d":"Speaking & Community","k":"Site","x":"I help convene developers in person, giving the local community direct, no-cost access to expert content on the Microsoft platform. Lead organizer Atlanta Cloud + AI Conference…"},{"u":"/speaking.html","t":"User groups","d":"Speaking & Community","k":"Site","x":"An active participant in Atlanta's Microsoft technology user-group ecosystem, the same community network from which the conferences draw their speakers and attendees. • Atlanta…"},{"u":"/speaking.html","t":"Open source & mentorship","d":"Speaking & Community","k":"Site","x":"My MMCA framework is Apache-2.0 licensed and documented with architecture decision records, so the patterns are not just usable but teachable. I mentor developers one on one,…"},{"u":"/speaking.html","t":"What I speak on","d":"Speaking & Community","k":"Site","x":"Sessions and workshops for conferences, user groups, and teams. Clean Architecture & DDD on .NET Modular monolith → microservices The transactional outbox Database-per-service…"},{"u":"/contact.html","d":"Contact","k":"Site","x":"Contact Let's connect Happy to talk architecture, the MMCA platform, speaking at your conference or user group, or comparing notes on .NET and Azure. The fastest ways to reach…"},{"u":"/contact.html","t":"Three places to start","d":"Contact","k":"Site","x":"Open source The MMCA platform A .NET 10 framework and three reference apps, graded in the open against a 34-category rubric. See the architecture → Writing Deep dives on…"},{"u":"https://medium.com/@ivanball76/retries-are-not-a-recovery-plan-resilience-handlers-rto-rpo-and-a-restore-you-actually-drilled-3c7474814123","t":"Resilience and recovery objectives","d":"Article no. 33","k":"Run & extract","x":"Standard resilience on every outbound client, plus declared RTO/RPO and a drilled restore.","e":1},{"u":"https://medium.com/@ivanball76/extracting-a-module-to-a-grpc-service-live-799926cf8a32","t":"Extracting a module to a gRPC service","d":"Article no. 32","k":"Run & extract","x":"A step-by-step extraction of an in-process module into its own gRPC service, database, and auth.","e":1},{"u":"https://medium.com/@ivanball76/aspire-one-command-brings-up-the-whole-distributed-app-379b5cffdeed","t":"Aspire: one command","d":"Article no. 31","k":"Run & extract","x":"Model services, databases, and the broker as one Aspire graph that runs from laptop to Azure with one command.","e":1},{"u":"https://medium.com/@ivanball76/defending-the-api-edge-three-controls-that-cover-the-whole-surface-d958ecae1091","t":"Rate limiting and brute-force protection","d":"Article no. 30","k":"Auth & the edge","x":"Two layers that cover the whole API edge: endpoint rate limits plus lockout-based brute-force defense on identity.","e":1},{"u":"https://medium.com/@ivanball76/resource-ownership-authorization-which-rows-you-may-touch-not-just-which-actions-cb8e78867bae","t":"Resource-ownership authorization","d":"Article no. 29","k":"Auth & the edge","x":"Beyond roles and permissions: which rows you may touch, enforced per resource.","e":1},{"u":"https://medium.com/@ivanball76/generic-entity-controllers-and-the-dynamic-query-contract-adr-034-2b5c799bc69f","t":"Generic entity controllers","d":"Article no. 28","k":"Auth & the edge","x":"A write-once REST surface every entity inherits, plus a bounded dynamic query contract that is never open SQL.","e":1},{"u":"https://medium.com/@ivanball76/one-rotating-refresh-token-and-reuse-detection-that-makes-theft-self-limiting-fab42234a04a","t":"One rotating refresh token","d":"Article no. 27","k":"Auth & the edge","x":"A short-lived JWT plus one server-stored refresh token that rotates on every use, with reuse detection that makes a stolen token end its own session.","e":1},{"u":"https://medium.com/@ivanball76/google-and-github-login-without-leaking-tokens-external-oauth-behind-your-own-jwts-d68ba5e3aca4","t":"External OAuth login behind your own JWTs","d":"Article no. 26","k":"Auth & the edge","x":"Sign in with Google or GitHub without leaking provider tokens: external identity exchanged for your own JWTs at the boundary.","e":1},{"u":"https://medium.com/@ivanball76/browser-session-cookie-auth-for-blazor-ssr-surviving-the-f5-eb0ea317820e","t":"Browser session-cookie auth for Blazor SSR","d":"Article no. 25","k":"Auth & the edge","x":"HttpOnly session cookies and an SSR-time scheme so [Authorize] passes during prerender, with the API still the boundary.","e":1},{"u":"https://medium.com/@ivanball76/permission-based-authorization-capabilities-over-role-checks-ea6574cbee27","t":"Permission-based authorization over roles","d":"Article no. 24","k":"Auth & the edge","x":"A capability layer over RBAC: permission policies that resolve on demand from a central registry.","e":1},{"u":"https://medium.com/@ivanball76/delete-automapper-explicit-compile-time-dto-mapping-that-you-can-actually-test-9c7013cc5d3f","t":"Delete AutoMapper: manual DTO mapping","d":"Article no. 23","k":"Auth & the edge","x":"Why source-generated, per-entity mappers beat reflection-based mapping for clarity and speed.","e":1},{"u":"https://medium.com/@ivanball76/ephemeral-by-design-sub-second-live-channels-over-one-signalr-hub-0248050e0c8b","t":"Live channels over one SignalR hub","d":"Article no. 22","k":"Auth & the edge","x":"Sub-second ephemeral events (polls, Q&A, live counts) fanned out over the existing notification hub, with nothing persisted.","e":1},{"u":"https://medium.com/@ivanball76/notifications-as-a-vertical-slice-in-app-inbox-real-time-push-native-push-and-email-c59d5a4f3b69","t":"Notifications as a vertical slice","d":"Article no. 21","k":"Auth & the edge","x":"A notifications feature built as a clean vertical slice across every layer.","e":1},{"u":"https://medium.com/@ivanball76/problem-details-across-http-and-grpc-rfc-9457-9f20157cf7de","t":"Problem Details across HTTP and gRPC","d":"Article no. 20","k":"Auth & the edge","x":"One error contract mapped consistently to HTTP Problem Details and gRPC status.","e":1},{"u":"https://medium.com/@ivanball76/the-self-invalidating-cache-that-lives-in-the-pipeline-not-your-handlers-e11548062d2f","t":"The self-invalidating cache","d":"Article no. 19","k":"Auth & the edge","x":"A caching decorator where commands invalidate and queries populate, plus an authenticated output-cache tier at the API edge.","e":1},{"u":"https://medium.com/@ivanball76/idempotency-in-one-attribute-safe-retries-for-http-apis-065848fd03f4","t":"Idempotency in one attribute","d":"Article no. 18","k":"Auth & the edge","x":"Dedup client retries with an Idempotency-Key header and cached replay, plus a consumer-side inbox for brokers.","e":1},{"u":"https://medium.com/@ivanball76/password-hashing-done-right-pbkdf2-sha512-600k-iterations-timing-safe-d64ddb802403","t":"Password hashing done right","d":"Article no. 17","k":"Auth & the edge","x":"The non-negotiables of password storage in .NET, done correctly and tested.","e":1},{"u":"https://medium.com/@ivanball76/cross-service-auth-without-a-shared-secret-jwks-dual-fetch-478e6f688c7e","t":"JWKS cross-service auth","d":"Article no. 16","k":"Auth & the edge","x":"Validate another service's RS256 tokens via JWKS discovery, with no shared secret crossing a boundary.","e":1},{"u":"https://medium.com/@ivanball76/event-schema-versioning-never-silently-reshape-an-event-93cd5d4a156d","t":"Event-schema versioning","d":"Article no. 15","k":"Data & persistence","x":"Every integration event carries a schema version; breaking changes get a new event type and an upcaster, never a silent reshape.","e":1},{"u":"https://medium.com/@ivanball76/self-ordering-modules-discovered-kahn-ordered-and-extractable-2ce7283a26b5","t":"Self-ordering modules","d":"Article no. 14","k":"Data & persistence","x":"Modules declare their dependencies and load in topological order, so registration is never hand-sequenced.","e":1},{"u":"https://medium.com/@ivanball76/optimistic-concurrency-that-survives-the-round-trip-rowversion-from-database-to-dto-and-back-93d4a794716f","t":"Optimistic concurrency: RowVersion round-trips","d":"Article no. 13","k":"Data & persistence","x":"Carry the RowVersion from database to DTO and back, so a concurrent edit fails fast as a conflict instead of silently overwriting.","e":1},{"u":"https://medium.com/@ivanball76/ef-core-include-chains-are-a-trap-navigation-populators-decouple-eager-loading-c378fa4497ac","t":"Navigation populators","d":"Article no. 12","k":"Data & persistence","x":"Eager-load relationships that cross containers and data sources without N+1 or a leaky abstraction.","e":1},{"u":"https://medium.com/@ivanball76/one-entity-model-three-databases-polyglot-persistence-behind-a-single-attribute-760e77974d5d","t":"Polyglot persistence: one model, three engines","d":"Article no. 11","k":"Data & persistence","x":"SQL Server, Cosmos, and SQLite behind a single entity model, with the engine chosen by attribute.","e":1},{"u":"https://medium.com/@ivanball76/database-per-service-inside-a-monolith-and-why-265092eb03f1","t":"Database-per-service inside a monolith","d":"Article no. 10","k":"Data & persistence","x":"Give each module its own database and outbox before you extract it, so extraction changes hosting, not data.","e":1},{"u":"https://medium.com/@ivanball76/the-transactional-outbox-in-net-10-never-lose-an-event-again-f5a9b7a89e51","t":"The transactional outbox","d":"Article no. 9","k":"Core patterns","x":"Events that survive a crash: persist them atomically with your data, then dispatch at least once.","e":1},{"u":"https://medium.com/@ivanball76/compose-validators-dont-copy-them-a-reusable-fluentvalidation-kit-8865a6003a9c","t":"Compose validators, don't copy them","d":"Article no. 8","k":"Core patterns","x":"A validation kit that composes FluentValidation rules instead of copy-pasting them across features.","e":1},{"u":"https://medium.com/@ivanball76/the-cqrs-decorator-pipeline-logging-caching-and-transactions-without-touching-a-handler-fb7679b8bde8","t":"The CQRS decorator pipeline","d":"Article no. 7","k":"Core patterns","x":"Thin command and query handlers wrapped by a Scrutor decorator chain whose order is load-bearing.","e":1},{"u":"https://medium.com/@ivanball76/specifications-over-linq-spaghetti-composable-reusable-query-intent-8a40dafcbd3d","t":"Specifications over LINQ spaghetti","d":"Article no. 6","k":"Core patterns","x":"Compose queries from reusable specification objects instead of scattering LINQ across handlers.","e":1},{"u":"https://medium.com/@ivanball76/kill-the-anemic-domain-model-rich-aggregates-with-factory-methods-that-return-result-44f2e3d89794","t":"Kill the anemic domain model","d":"Article no. 5","k":"Core patterns","x":"Push behavior into rich aggregates with factory methods and invariants instead of bags of public setters.","e":1},{"u":"https://medium.com/@ivanball76/stop-throwing-exceptions-for-control-flow-the-result-railway-in-c-7a02050b554e","t":"The Result railway in C#","d":"Article no. 4","k":"Core patterns","x":"Model expected failures as Result values with a transport-agnostic error type, and keep exceptions for the genuinely exceptional.","e":1},{"u":"https://medium.com/@ivanball76/what-good-architecture-actually-means-a-34-category-rubric-you-can-score-yourself-against-4002291a6b6a","t":"The 34-category architecture rubric","d":"Article no. 3","k":"Orientation","x":"A two-axis rubric for scoring architecture on maturity and implementation, so 'good architecture' stops being a vibe.","e":1},{"u":"https://medium.com/@ivanball76/modular-monolith-to-microservices-without-the-rewrite-8c3603614f12","t":"Modular monolith to microservices","d":"Article no. 2","k":"Orientation","x":"The cornerstone idea: build the monolith now and extract a service later with no rewrite, via module discovery, gRPC contracts, and a YARP gateway.","e":1},{"u":"https://medium.com/@ivanball76/i-open-sourced-the-enterprise-net-77f9200f3728","t":"Open-sourced and graded against 34 categories","d":"Article no. 1","k":"Orientation","x":"Why I open-sourced a production .NET framework and scored it against a 34-category architecture rubric, gaps and all.","e":1}]}
\ No newline at end of file
+{"v":1,"n":1212,"r":[{"u":"/docs/adr/index.html","d":"Architecture Decision Records","k":"Architecture Decision Records","x":"Accepted ADRs explaining why the core cross-cutting patterns exist. Read these before changing a pattern they describe: they capture context and trade-offs that aren't obvious…","i":"ApplicationLayer_DoesNotUseRawQueryableSurfaces PushNotificationSettings.ChannelKeyPattern ApiParameterDescriptorBackfillProvider IWriteRepository.SetOriginalRowVersion ServiceInfoVersioningContractTestsBase MMCA.Common.LayerEnforcement.targets SoftDeletedUserCache.MarkerDuration SessionCookieAuthenticationHandler AggregateRootEntityControllerBase ConfigureEndpointsWithHealthProbe OAuthControllerBase.CompleteAsync UpdateRequestsAreConcurrencyAware"},{"u":"/docs/adr/index.html#writing-a-new-adr","d":"Architecture Decision Records","k":"Architecture Decision Records","t":"Writing a new ADR","x":"Copy the structure of an existing record: Status (Proposed / Accepted / Superseded, date and link when superseding), Context (the forces and the problem), Decision (what we…"},{"u":"/docs/adr/001-manual-dto-mapping.html","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records"},{"u":"/docs/adr/001-manual-dto-mapping.html#status","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Status","x":"Accepted. Mechanism clarified 2026-06-26: the per-entity mappers are Riok.Mapperly source-generated (compile-time), not hand-written line by line. The decision to avoid runtime…"},{"u":"/docs/adr/001-manual-dto-mapping.html#context","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Context","x":"Domain entities must be mapped to DTOs for API responses. The two common approaches are: 1. Manual mapping classes (IEntityDTOMapper ) 2. Convention-based reflection mapping…","i":"IEntityDTOMapper TEntity TDTO TId"},{"u":"/docs/adr/001-manual-dto-mapping.html#decision","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Decision","x":"Use explicit, per-entity DTO mappers (each a Riok.Mapperly [Mapper] partial class whose MapToDTO body is source-generated at compile time) registered via Scrutor assembly…","i":"IEntityRequestMapper IEntityDTOMapper SpeakerDTOMapper TIdentifierType TCreateRequest UserMapping TEntityDTO MapToDTOs UseMapper MapToDTO partial TEntity"},{"u":"/docs/adr/001-manual-dto-mapping.html#rationale","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Rationale","x":"- Compile-time safety: Mapping errors surface at build time, not runtime. Property renames break the build rather than silently mapping null. - Testability: Each mapper is a…","i":"SpeakerDTOMapper MapToDTO null"},{"u":"/docs/adr/001-manual-dto-mapping.html#trade-offs","d":"ADR-001: Manual DTO Mapping over AutoMapper","k":"Architecture Decision Records","t":"Trade-offs","x":"- More files (30 DTO mappers across Store + ADC: 19 in ADC, 11 in Store, plus the parallel IEntityRequestMapper classes). The interface's default MapToDTOs implementation is…","i":"IEntityRequestMapper MapToDTOs"},{"u":"/docs/adr/002-navigation-populators.html","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records"},{"u":"/docs/adr/002-navigation-populators.html#status","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Status","x":"Accepted"},{"u":"/docs/adr/002-navigation-populators.html#context","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Context","x":"The application supports multiple database backends (SQL Server, Cosmos DB, SQLite). EF Core's .Include() works for SQL Server but fails for Cosmos DB cross-container…","i":"IDataSourceService.HaveIncludeSupport NavigationMetadataProvider declaringType IsCollection Navigation targetType Include"},{"u":"/docs/adr/002-navigation-populators.html#decision","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Decision","x":"Each entity that has unsupported navigations gets a INavigationPopulator implementation. A DeclarativeNavigationPopulator base class (added in MMCA.Common) allows populators to…","i":"DeclarativeNavigationPopulator ChildNavigationDescriptor FKNavigationDescriptor INavigationDescriptor INavigationPopulator NavigationLoader Product.Category Event.Rooms TEntity WHERE"},{"u":"/docs/adr/002-navigation-populators.html#rationale","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Rationale","x":"- Multi-DB support: The query pipeline automatically falls back from Include to NavigationPopulator when the data source reports navigations as unsupported. - Batch efficiency:…","i":"DeclarativeNavigationPopulator"},{"u":"/docs/adr/002-navigation-populators.html#trade-offs","d":"ADR-002: NavigationPopulators for Cross-Container Loading","k":"Architecture Decision Records","t":"Trade-offs","x":"- Extra abstraction layer for SQL Server (where Include works fine). Mitigated: the populator is only called when the query pipeline's metadata says navigations are unsupported.…","i":"NullNavigationPopulator"},{"u":"/docs/adr/003-outbox-dual-dispatch.html","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#status","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-19 (integration-event routing via IMessageBus, lease-based claims for safe scale-out, dead-letter visibility, post-commit dispatch; see Revision below).…","i":"IMessageBus"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#context","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Context","x":"Domain events must be reliably published after aggregate changes are persisted. Two failure modes exist: 1. In-process dispatch fails (e.g., handler throws): the event is lost if…"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#decision","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Decision","x":"Use a dual-dispatch strategy: 1. Outbox persistence: Domain events are serialized into OutboxMessage rows within the same database transaction as the aggregate changes. This…","i":"DomainEventDispatcher BackgroundService SaveChangesAsync OutboxProcessor OutboxMessage"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#rationale","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Rationale","x":"- Guaranteed delivery: The outbox table is written atomically with the aggregate changes. Even if the process crashes after persistence, the background processor catches up. -…","i":"OutboxPollFilterProcessor ProcessingDelaySeconds BrokerMessageBus OutboxProcessor BrokerEventBus IMessageBus OutboxPoll"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#trade-offs","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Trade-offs","x":"- Domain event handlers must be idempotent (this is a good practice regardless). - The outbox table grows until processed entries are cleaned up: OutboxCleanupService purges rows…","i":"OutboxCleanupService HasMoreEligibleWork ProcessedOn MaxRetries RetryCount"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-07-19","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-07-19)","x":"Four changes from the 2026-07-19 full review: 1. Integration events route through the outbox to IMessageBus, never local dispatch. An IIntegrationEvent raised via AddDomainEvent…","i":"DomainEventSaveChangesInterceptor outbox.dead_letter.count OutboxCleanupService ExecuteUpdateAsync IIntegrationEvent type_unresolvable integrationEvent OutboxProcessor AddDomainEvent OutboxMessage IMessageBus LockedUntil"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-07-24","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Three capture-side corrections found in a code review. None change the dual-dispatch decision; they close gaps between what it promised and what the interceptor did. 1. Capture…","i":"ExecuteInTransactionAsync RemoveDomainEvents IAggregateRoot SavingChanges RetryCount DbContext LastError catch"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-08-01","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"One retry-pacing correction. The dual-dispatch decision is unchanged; the Trade-offs above described a cadence the processor no longer has. 1. Retry backoff is explicit, and it…","i":"RetryBackoffBaseSeconds"},{"u":"/docs/adr/003-outbox-dual-dispatch.html#revision-2026-08-07","d":"ADR-003: Outbox Pattern with Dual Dispatch","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"One retry-pacing refinement. The decision and the curve are unchanged; the waits are no longer identical across a batch. 1. The retry backoff carries random jitter. The…"},{"u":"/docs/adr/004-authentication-dual-fetch.html","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records"},{"u":"/docs/adr/004-authentication-dual-fetch.html#status","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Status","x":"Accepted."},{"u":"/docs/adr/004-authentication-dual-fetch.html#context","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Context","x":"When the modular monolith is extracted into per-module service hosts behind a gateway (ADR-008), every service must authenticate the same end-user JWT, but only one service…"},{"u":"/docs/adr/004-authentication-dual-fetch.html#decision","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Decision","x":"Validate cross-service tokens with asymmetric (RS256) signatures plus JWKS / OIDC discovery, keeping the symmetric (HS256) shared-secret path as the in-process monolith default.…","i":"TokenValidationParameters.ValidAlgorithms id_token_signing_alg_values_supported OpenIdConnectMetadataWarmupTask JwtSettings.SigningAlgorithm BuildValidationParameters MapOidcDiscoveryEndpoint response_types_supported AddCommonAuthentication subject_types_supported AddForwardedJwtBearer WithJwksDiscovery RsaPublicKeyPath"},{"u":"/docs/adr/004-authentication-dual-fetch.html#rationale","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Rationale","x":"- No shared signing key. Only Identity can mint tokens; every other service holds only the public key it fetched, so a compromised non-Identity service cannot forge tokens, and…"},{"u":"/docs/adr/004-authentication-dual-fetch.html#trade-offs","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Trade-offs","x":"- More moving parts than a shared secret. RS256 needs key generation, distribution of the public half, a JWKS endpoint, and discovery wiring, versus one symmetric string. -…"},{"u":"/docs/adr/004-authentication-dual-fetch.html#related","d":"ADR-004: Cross-Service Token Validation via JWKS / OIDC Discovery","k":"Architecture Decision Records","t":"Related","x":"ADR-007 (gRPC calls forward the validated JWT downstream via JwtForwardingClientInterceptor), ADR-008 (the extraction that split issuer and validator into separate processes),…","i":"JwtForwardingClientInterceptor"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html#status","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Status","x":"Accepted. Updated 2026-06-27 (documented the [Pii] → IAnonymizable build-time guard and PiiRedactor).","i":"IAnonymizable PiiRedactor Pii"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html#context","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Context","x":"The framework's default deletion model is soft-delete: AuditableBaseEntity.Delete() sets IsDeleted = true and EF Core global query filters exclude the row from normal queries.…","i":"AuditableBaseEntity.Delete OutboxMessage IsDeleted true"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html#decision","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Decision","x":"Separate the two concerns and provide an extension point for each, rather than overloading soft-delete: 1. Soft-delete stays the default for lifecycle/state management (hide +…","i":"MMCA.Common.Domain.Attributes.PiiAttribute MMCA.Common.Domain.Interfaces MMCA.Common.Domain.Privacy EncryptedStringConverter PiiConventionTestsBase OutboxCleanupService IAnonymizable PiiRedactor Anonymize Result User Pii"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html#rationale","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Rationale","x":"- Right tool per concern: soft-delete answers \"is this record active?\"; erasure answers \"has this person's data been removed?\". Conflating them (e.g. hard-deleting inside…","i":"Delete"},{"u":"/docs/adr/005-soft-delete-vs-erasure.html#trade-offs","d":"ADR-005: Soft-Delete vs. Right-to-Erasure","k":"Architecture Decision Records","t":"Trade-offs","x":"- Erasure is opt-in per entity, but the opt-in is guarded: an aggregate exposing a [Pii]-marked property that does not implement IAnonymizable fails the architecture fitness…","i":"IAnonymizable Pii"},{"u":"/docs/adr/006-database-per-service.html","d":"ADR-006: Database per Service","k":"Architecture Decision Records"},{"u":"/docs/adr/006-database-per-service.html#status","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-07). Supersedes the earlier \"deliberately one shared database\" stance. Clarified 2026-06-27: the single context class became one sealed context class per engine…","i":"Name"},{"u":"/docs/adr/006-database-per-service.html#context","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Context","x":"When the modules were first extracted into independently-deployable services, all services in an app still pointed at a single shared SQL database with a single OutboxMessages…","i":"CrossDataSourceDegradeConvention EntityDataSourceRegistry DataSourceResolver DbContextFactory OutboxProcessor OutboxMessages"},{"u":"/docs/adr/006-database-per-service.html#decision","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Decision","x":"Adopt database-per-service: each service owns its own physical database with its own OutboxMessages table. - One sealed concrete context class per engine, one instance per…","i":"CrossDataSourceDegradeConvention PhysicalDbContextFactory ApplicationDbContext INavigationPopulator DataSourceResolver SQLServerDbContext ADC_Notification CosmosDbContext OutboxProcessor SqliteDbContext ADC_Conference ADC_Engagement"},{"u":"/docs/adr/006-database-per-service.html#rationale","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Rationale","x":"- Removes the shared-outbox race (the sharpest cost of the shared DB) without an OriginService filter: physical isolation is simpler and stronger than a logical filter. - Real…","i":"OriginService"},{"u":"/docs/adr/006-database-per-service.html#trade-offs","d":"ADR-006: Database per Service","k":"Architecture Decision Records","t":"Trade-offs","x":"- No cross-database FKs or transactions. Relationships that span services degrade to scalar IDs; consistency across services is eventual (outbox + broker), not transactional. -…"},{"u":"/docs/adr/007-grpc-extraction.html","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records"},{"u":"/docs/adr/007-grpc-extraction.html#status","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Status","x":"Accepted."},{"u":"/docs/adr/007-grpc-extraction.html#context","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Context","x":"Once modules became separate service processes, the in-process interface calls between them (e.g. Conference → Engagement's IBookmarkCountService, Engagement → Conference's…","i":"ISessionBookmarkValidationService IBookmarkCountService Result"},{"u":"/docs/adr/007-grpc-extraction.html#decision","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Decision","x":"Use gRPC, exposed through MMCA.Common.Grpc, with a contract-package convention: - .Contracts projects hold the .proto definitions plus a gRPC adapter that implements the same…","i":"SessionBookmarkValidationServiceGrpcAdapter GrpcResultExceptionInterceptor JwtForwardingClientInterceptor Directory.Build.props AddTypedGrpcClient SocketsHttpHandler MMCA.Common.Grpc HandleFailure IReadOnlyList RpcException serviceName Contracts"},{"u":"/docs/adr/007-grpc-extraction.html#rationale","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Rationale","x":"- No business-logic rewrite: the gRPC adapter implements the interface modules already depend on; swapping in-process for cross-process is a registration change. - Transport…","i":"MicroserviceExtractionTests ServiceContract MassTransit version proto"},{"u":"/docs/adr/007-grpc-extraction.html#trade-offs","d":"ADR-007: Synchronous Cross-Service Calls via gRPC Contracts","k":"Architecture Decision Records","t":"Trade-offs","x":"- Bidirectional pairs need care. Conference ↔ Engagement is a mutual gRPC pair; the AppHost deliberately omits a reciprocal WaitFor to avoid a startup deadlock: transient \"peer…","i":"Http1AndHttp2 WaitFor Http2"},{"u":"/docs/adr/008-service-extraction-topology.html","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records"},{"u":"/docs/adr/008-service-extraction-topology.html#status","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Status","x":"Accepted."},{"u":"/docs/adr/008-service-extraction-topology.html#context","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Context","x":"ADC began as a modular monolith: one MMCA.ADC.WebAPI host loaded every module (Identity, Conference, Engagement, Notification) in-process via the ModuleLoader, sharing one…","i":"MMCA.ADC.WebAPI ModuleLoader"},{"u":"/docs/adr/008-service-extraction-topology.html#decision","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Decision","x":"Extract one service host per module: MMCA.ADC.{Identity,Conference,Engagement,Notification}.Service and front them with a single YARP reverse-proxy Gateway (MMCA.ADC.Gateway,…","i":"MicroserviceExtractionTests MMCA.ADC.Gateway MMCA.ADC.WebAPI ModuleLoader Notification Conference Engagement Identity MMCA.ADC Modules Service Module"},{"u":"/docs/adr/008-service-extraction-topology.html#rationale","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Rationale","x":"- No business-logic rewrite. Because a service is just the monolith with one module enabled, extraction was a hosting/wiring change, not a domain change, and the module-isolation…","i":"ModuleLoader"},{"u":"/docs/adr/008-service-extraction-topology.html#trade-offs","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Trade-offs","x":"- Operational complexity. Four deployables plus a Gateway, service discovery, a broker, and per-service databases, versus one process. Mitigated locally by Aspire orchestration…","i":"MMCA.Common.API ServiceDefaults Http1AndHttp2 Http2"},{"u":"/docs/adr/008-service-extraction-topology.html#applicability","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Applicability","x":"This ADR is framed around ADC (the first repo extracted), but the same topology is now the framework's standard extraction shape, not an ADC-only choice. MMCA.Store followed it:…","i":"MMCA.Store.Gateway MMCA.Store.WebAPI MMCA.Store Identity Catalog Service Sales"},{"u":"/docs/adr/008-service-extraction-topology.html#related","d":"ADR-008: Extraction of the Modular Monolith into Per-Module Services + Gateway","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (outbox dual dispatch), ADR-004 (cross-service token validation via JWKS), ADR-006 (database per service), and ADR-007 (gRPC cross-service calls) are the facet decisions…"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#status","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-14)"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#context","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Context","x":"The framework already supplies the mechanisms for surviving partial failure: a standard Polly resilience handler (timeout / retry / circuit breaker), the outbox for at-least-once…","i":"ConfigureHttpClientDefaults AddTypedServiceClient AddTypedGrpcClient MMCA.Common.Aspire"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#decision","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Decision","x":"1. Resilience is a framework invariant, not a per-call choice. Every outbound HttpClient and gRPC client registered through the framework's extension methods (AddTypedGrpcClient,…","i":"MMCA.Common.Grpc.Tests ResilienceHandlerTests AddTypedServiceClient AddTypedGrpcClient MMCA.Common.Aspire HttpClient"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#rationale","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Rationale","x":"- Invariant over discipline. A fitness function turns \"remember to add resilience\" into a build gate: the same approach the framework already uses for the layer rules and the…"},{"u":"/docs/adr/009-resilience-and-recovery-objectives.html#trade-offs","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Trade-offs","x":"- The named gate (ResilienceHandlerTests, MMCA.Common.Grpc.Tests) asserts that the gRPC client path (AddTypedGrpcClient) registers the standard handler, not the runtime behavior…","i":"ResilienceCircuitBreakerFaultInjectionTests MMCA.Common.Grpc.Tests ResilienceHandlerTests AddTypedServiceClient AddTypedGrpcClient"},{"u":"/docs/adr/010-integration-event-schema-versioning.html","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records"},{"u":"/docs/adr/010-integration-event-schema-versioning.html#status","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-19). Updated 2026-06-27 (Helpdesk enforcement gap closed; all three consumers now gate the convention). Updated 2026-08-14 (ADC now gates seven events, and a…"},{"u":"/docs/adr/010-integration-event-schema-versioning.html#context","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Context","x":"Integration events cross service boundaries (Identity → Conference, Conference ↔ Engagement, …) and are resolved by consumers solely by their type string: the outbox serializes…","i":"OutboxMessage.FromDomainEvent DateOccurred EventType MessageId"},{"u":"/docs/adr/010-integration-event-schema-versioning.html#decision","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Decision","x":"1. Every integration event carries an explicit SchemaVersion. BaseIntegrationEvent exposes public virtual int SchemaVersion = 1;. It is serialized with the payload…","i":"MMCA.Common.Testing.Architecture EventConventionTestsBase BaseIntegrationEvent IIntegrationEvent UserRegisteredV2 SchemaVersion virtual public int"},{"u":"/docs/adr/010-integration-event-schema-versioning.html#rationale","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Rationale","x":"- A signal, enforced. A version field plus a build-gating convention test turns \"remember the contract\" into something the tooling checks: the same invariant-over-discipline…","i":"virtual"},{"u":"/docs/adr/010-integration-event-schema-versioning.html#trade-offs","d":"ADR-010: Integration-Event Schema Versioning & Upcaster Policy","k":"Architecture Decision Records","t":"Trade-offs","x":"- SchemaVersion is a signal, not a mechanism: by itself it does not stop a consumer breaking on a real reshape. The load-bearing half is the discipline (new type + upcaster); the…","i":"MMCA.Helpdesk.Architecture.Tests EventVersioningConventionTests ProductCreatedIntegrationEvent MMCA.Store.Architecture.Tests TicketOpenedIntegrationEvent MMCA.ADC.Architecture.Tests OrderPlacedIntegrationEvent EventConventionTestsBase CommonArchitectureMap ArchitectureTests.cs EventConventionTests MMCA.ECommerce"},{"u":"/docs/adr/011-single-locale-i18n.html","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records"},{"u":"/docs/adr/011-single-locale-i18n.html#status","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Status","x":"Superseded by ADR-027 (2026-06-27). Originally Accepted (2026-06-19). The \"if multi-locale is ever required\" scope below is the blueprint ADR-027 implements; this record is…"},{"u":"/docs/adr/011-single-locale-i18n.html#context","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Context","x":"The MMCA applications (the ADC conference app, the Store) and the MMCA.Common.UI library currently ship a single locale (en-US). The architecture rubric scores…","i":"MMCA.Common.UI"},{"u":"/docs/adr/011-single-locale-i18n.html#decision","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Decision","x":"1. Single-locale (en-US) is an explicit non-goal for now. User-facing strings are inline in markup; dates/numbers use invariant or fixed formatting where appropriate. 2. The…","i":"RequestLocalization"},{"u":"/docs/adr/011-single-locale-i18n.html#rationale","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Rationale","x":"- Recording the decision converts an implicit rubric-zero into a conscious, revisitable choice: the same posture as the single-region DR acceptance in ADR-009. - Premature i18n…"},{"u":"/docs/adr/011-single-locale-i18n.html#trade-offs","d":"ADR-011: Single-Locale by Design (No Internationalization)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Adding a locale later touches every view plus the formatting paths: a real but bounded effort, accepted. - Hard-coded strings make a future extraction larger; mitigated by the…"},{"u":"/docs/adr/012-grpc-host-transport.html","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records"},{"u":"/docs/adr/012-grpc-host-transport.html#status","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Status","x":"Accepted (re-verified against source 2026-08-14)."},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-06-22-store-converged-to-profile-a","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-06-22): Store converged to Profile A","x":"Store originally chose Profile B, but its cross-service gRPC failed in Azure Container Apps. With Http1AndHttp2 Kestrel + transport: 'auto' ingress on a cleartext endpoint there…","i":"IProductVariantService.ExistsAsync IUserSalesExportService HTTP_1_1_REQUIRED WithJwksDiscovery AddItemCommand Http1AndHttp2 transport identity gateway httpGet Http2"},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-09-adc-notification-adds-a-mixed-endpoint-profile-per-endpoint-protocols","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-09): ADC Notification adds a mixed-endpoint profile (per-endpoint protocols)","x":"The live-channel push pipeline (ADR-039) gave ADC's Notification service an inbound cleartext gRPC server (LiveChannelPushService.PushToChannel, called best-effort by Engagement…","i":"LiveChannelPushService.PushToChannel engagementService.WithReference services__notification__grpc__0 appsettings.Development.json additionalPortMappings notificationService Http1AndHttp2 httpGet WaitFor Http2 grpc http"},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-25-probe-listeners-are-adcs-answer-not-tcp-probes-and-gateway-routed-jwks-is-a-local-only-rule","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-25): probe listeners are ADC's answer, not TCP probes; and gateway-routed JWKS is a local-only rule","x":"Two claims above were written from an earlier state of the code and no longer describe either app. 1. ADC probes never touch the traffic endpoint; TCP probes were then…","i":"HTTP_1_1_REQUIRED WithJwksDiscovery identityApp.name Program.cs tcpSocket transport identity gateway httpGet Http1 grpc"},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-07-28-the-probe-listener-is-the-single-pattern-in-both-apps-no-tcp-probes-anywhere","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-07-28): the probe listener is the single pattern in both apps (no TCP probes anywhere)","x":"Store PR 55 (commit 297064bb, merged 2026-07-27) ported ADC's dedicated probe listener to Store, so the Store-only tcpSocket exception recorded in the 2026-07-25 update above is…","i":"HealthProbe__Port Http1AndHttp2 tcpSocket httpGet Http1 Http2"},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-07-the-probe-listener-moved-into-mmcacommon-and-notifications-grpc-endpoint-carries-a-second-service","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-07): the probe listener moved into MMCA.Common, and Notification's gRPC endpoint carries a second service","x":"1. One shared framework method, not a per-service file. The KestrelConfiguration.cs copies the two updates above cite no longer exist in either app. The pattern was extracted…","i":"UserNotificationExportGrpcService MMCA.ADC.Notification.Contracts services__notification__grpc__0 identityService.WithReference appsettings.Development.json HttpProtocols.Http1AndHttp2 redeclareCleartextEndpoint ConfigureEndpointDefaults KestrelConfiguration.cs additionalPortMappings ASPNETCORE_ENVIRONMENT LiveChannelGrpcService"},{"u":"/docs/adr/012-grpc-host-transport.html#update-2026-08-14-stores-sales-runs-the-mixed-endpoint-profile-too-so-no-pure-profile-b-host-remains","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Update (2026-08-14): Store's Sales runs the mixed-endpoint profile too, so no pure Profile B host remains","x":"Sales gained an inbound gRPC edge of its own (IUserSalesExportService, the Identity-driven data-subject export), and it resolved that the same way ADC's Notification did: not by…","i":"identityService.WithReference appsettings.Development.json UserSalesExportGrpcService AddSalesUserExportClient services__sales__grpc__0 IUserSalesExportService additionalPortMappings RequireAuthorization HealthProbe__Port Http1AndHttp2 salesService _grpc.sales"},{"u":"/docs/adr/012-grpc-host-transport.html#context","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Context","x":"Once modules were extracted into separate service hosts (ADR-008) that call each other synchronously over gRPC (ADR-007), each service's Kestrel had to serve both REST traffic…","i":"HTTP_1_1_REQUIRED Http1AndHttp2 HttpClient Http2"},{"u":"/docs/adr/012-grpc-host-transport.html#decision","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Decision","x":"Pick one of two coherent transport profiles per app, and wire the gateway forwarder and JWKS discovery to match. Use when services must serve gRPC on cleartext (any bidirectional…","i":"builder.ConfigureEndpointsWithHealthProbe UserNotificationExportGrpcService HttpProtocols.Http1AndHttp2 ConfigureEndpointDefaults LiveChannelGrpcService HttpVersion.Version20 RequestVersionOrLower HttpProtocols.Http2 RequestVersionExact HTTP_1_1_REQUIRED WithJwksDiscovery Http1AndHttp2"},{"u":"/docs/adr/012-grpc-host-transport.html#rationale","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Rationale","x":"- The Kestrel protocol choice is the root constraint; the gateway-forward mode and the JWKS authority are downstream consequences, not independent knobs. Documenting them as a…","i":"HTTP_1_1_REQUIRED Http2"},{"u":"/docs/adr/012-grpc-host-transport.html#trade-offs","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two profiles to keep straight. A service that gains an inbound gRPC edge must migrate from Profile B to Profile A and flip ForwardHttp2 and the JWKS wiring together, or it…","i":"appsettings.Development.json additionalPortMappings appsettings.json Http1AndHttp2 ForwardHttp2 transport http2"},{"u":"/docs/adr/012-grpc-host-transport.html#related","d":"ADR-012: gRPC-Host Transport Convention (Http2-only h2c vs. Http1AndHttp2 + ALPN)","k":"Architecture Decision Records","t":"Related","x":"- ADR-004 (cross-service token validation via JWKS / OIDC discovery), ADR-007 (gRPC cross-service calls), ADR-008 (monolith → services + gateway topology), ADR-039 (live-channel…"},{"u":"/docs/adr/013-result-pattern.html","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records"},{"u":"/docs/adr/013-result-pattern.html#status","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-21 (exception-handler chain / ProblemDetails edge contract documented)."},{"u":"/docs/adr/013-result-pattern.html#context","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Context","x":"Operations at every layer fail in expected ways: input is invalid, a domain invariant is broken, a requested entity is missing, a uniqueness conflict occurs, the caller lacks…"},{"u":"/docs/adr/013-result-pattern.html#decision","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Decision","x":"Model expected failures as values using Result / Result (MMCA.Common.Shared.Abstractions), not exceptions. - A Result is either success or failure; a failure carries one or more…","i":"OperationCanceledExceptionHandler ApiControllerBase.HandleFailure MMCA.Common.Shared.Abstractions GrpcResultExceptionInterceptor AddCommonExceptionHandlers OperationCanceledException ValidationExceptionHandler DbUpdateExceptionHandler DomainExceptionHandler GlobalExceptionHandler UnprocessableEntity ValidationException"},{"u":"/docs/adr/013-result-pattern.html#rationale","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Rationale","x":"- Failures are in the signature. A method that can fail returns Result , so the caller cannot silently ignore the failure path the way an uncaught exception allows. - Category,…","i":"Result.Failure HandleFailure ErrorType IsFailure requestId Result"},{"u":"/docs/adr/013-result-pattern.html#trade-offs","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Trade-offs","x":"- More ceremony at call sites than letting an exception bubble; the combinators absorb most of it. - Two error channels coexist (Result for expected, exceptions for exceptional).…","i":"GlobalExceptionHandler ErrorType Result"},{"u":"/docs/adr/013-result-pattern.html#related","d":"ADR-013: Result Pattern over Exceptions for Flow Control","k":"Architecture Decision Records","t":"Related","x":"ADR-007 (Result over the wire via gRPC), ADR-014 (the decorator pipeline returns Result.Failure to short-circuit a command before it reaches the handler).","i":"Result.Failure"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#status","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-07-19 (Transactional semantics: rollback on business failure + post-commit event dispatch; see Revision below)."},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#context","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Context","x":"Commands and queries share cross-cutting concerns: validation, transactions, cache invalidation, logging / timing, and feature gating. Putting that logic inside each handler…"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#decision","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Decision","x":"Use single-responsibility handlers behind a Scrutor-composed decorator pipeline. - ICommandHandler and IQueryHandler (MMCA.Common.Application) are one handler per use case, each…","i":"ModuleLoader.DiscoverAndRegister ScanModuleApplicationServices ProfilingCommandDecorator AddApplicationDecorators AddApplicationProfiling MMCA.Common.Application ProfilingQueryDecorator ICacheInvalidating AddInfrastructure ICommandHandler IQueryCacheable AddApplication"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#rationale","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Rationale","x":"- Thin, testable handlers. A handler has no transaction, logging, or caching plumbing, so it is unit-tested in isolation. - One place to read and change the pipeline. The order…"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#trade-offs","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Trade-offs","x":"- Registration order is the reverse of execution order (a Scrutor foot-gun), mitigated by the inline ordering comments in AddApplicationDecorators(). - Decorators must be…","i":"AddApplicationDecorators"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#revision-2026-07-19","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-07-19)","x":"Two Transactional-decorator semantics changed with the 2026-07-19 full review: - A returned business failure now rolls the transaction back. Previously a handler returning…","i":"DbContextFactory.ExecuteInTransactionAsync DomainEventSaveChangesInterceptor RollbackTransaction Result.Failure IsFailure Result"},{"u":"/docs/adr/014-cqrs-decorator-pipeline.html#related","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Related","x":"ADR-013 (Result, the short-circuit currency of the pipeline), ADR-003 (handlers raise domain events that the outbox drains after SaveChanges; its 2026-07-19 revision pairs with…","i":"SaveChanges"},{"u":"/docs/adr/015-architecture-fitness-functions.html","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records"},{"u":"/docs/adr/015-architecture-fitness-functions.html#status","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Status","x":"Accepted"},{"u":"/docs/adr/015-architecture-fitness-functions.html#context","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Context","x":"The codebase rests on invariants that are easy to state and easy to erode by accident: clean- architecture layer flow (Domain depends on nothing above it), module isolation (no…","i":"SchemaVersion"},{"u":"/docs/adr/015-architecture-fitness-functions.html#decision","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Decision","x":"Enforce architectural invariants as automated checks that gate the build, in two layers. 1. Compile-time guard. MMCA.Common.LayerEnforcement.targets (imported for every Source/…","i":"MMCA.Common.LayerEnforcement.targets MMCA.Common.Testing.Architecture HelpdeskArchitectureMap CommonArchitectureMap StoreArchitectureMap AdcArchitectureMap IArchitectureMap ProjectReference dotnet test"},{"u":"/docs/adr/015-architecture-fitness-functions.html#rationale","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Rationale","x":"- Invariant over discipline. Turning \"do not do X\" into a red build is the only enforcement that scales. It is the same lever used by the layer rules, the resilience gate…","i":"IArchitectureMap"},{"u":"/docs/adr/015-architecture-fitness-functions.html#trade-offs","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Trade-offs","x":"- The tests assert structure / registration, not runtime behavior. ADR-009's test proves a client wires resilience, not that its policy values are correct; parameter tuning stays…","i":"FrameworkSanityTests IArchitectureMap"},{"u":"/docs/adr/015-architecture-fitness-functions.html#related","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Related","x":"ADR-009 (resilience gate), ADR-010 (event-version gate), ADR-016 (MassTransit pin gate), and ADR-006/007/008 (the transport and module-isolation rules the suite enforces)."},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#status","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Amended (2026-07-28): the fitness function now gates two commercial-license majors (MassTransit and SixLabors.ImageSharp), so the decision is restated as…","i":"MassTransit.Azure.ServiceBus.Core Directory.Packages.props SixLabors.ImageSharp"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#context","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common publishes its MMCA.Common. NuGet package set (see FACTS.md for the authoritative list and count) consumed by three downstream repos: the two production apps (Store,…","i":"Directory.Packages.props Infrastructure MassTransit MT_LICENSE FACTS.md Domain"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#decision","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Decision","x":"1. Version the whole MMCA.Common. package set in lockstep. All packages share one version (MinVer, derived from a single vX.Y.Z git tag); a release tags every package (see…","i":"MassTransit.Azure.ServiceBus.Core RestorePackagesWithLockFile DependencyVersionTestsBase MMCA.Common.Infrastructure Directory.Packages.props MassTransit.RabbitMQ SixLabors.ImageSharp MassTransit MT_LICENSE FACTS.md Obsolete vX.Y.Z"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#rationale","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Rationale","x":"- One version, one compatibility story. Lockstep removes the N-package matrix: \"everything on vX.Y.Z\" is the only supported combination, which is the right trade for a small…","i":"vX.Y.Z"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#trade-offs","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Trade-offs","x":"- A consumer cannot adopt a single package in isolation: it takes the whole set at the new version. - Lockstep will bump a package whose code did not change (acceptable: the…","i":"MassTransit.Azure.ServiceBus.Core Directory.Packages.props dependabot.yml"},{"u":"/docs/adr/016-lockstep-versioning-masstransit-pin.html#related","d":"ADR-016: Lockstep Package Versioning and the MassTransit-v8 Pin","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the fitness function that enforces the pins), ADR-003 / ADR-006 (MassTransit is the broker transport behind the outbox and database-per-service flows)."},{"u":"/docs/adr/017-request-idempotency.html","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records"},{"u":"/docs/adr/017-request-idempotency.html#status","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-01: the guard around execute-and-store is now an IDistributedLock resolved from DI (Redis-backed wherever a connection multiplexer is registered, which…","i":"IDistributedLock ObjectResult NoContent"},{"u":"/docs/adr/017-request-idempotency.html#context","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Context","x":"Write endpoints (POST / PUT / PATCH) are exposed to client retries and double-submits: a flaky network, an impatient user double-clicking, or a resilience pipeline re-issuing a…","i":"Result"},{"u":"/docs/adr/017-request-idempotency.html#decision","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Decision","x":"Provide opt-in, client-driven request idempotency as an MVC action filter in MMCA.Common.API. - Opt-in per action. [Idempotent] (IdempotentAttribute, a ServiceFilterAttribute…","i":"IdempotencySettings.CacheExpirationHours InProcessDistributedLock IConnectionMultiplexer ServiceFilterAttribute KeyedSemaphoreStripe RedisDistributedLock IdempotentAttribute AddInfrastructure IdempotencyFilter IDistributedLock StatusCodeResult MMCA.Common.API"},{"u":"/docs/adr/017-request-idempotency.html#rationale","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Rationale","x":"- Safety at the edge, not in every handler. Deduplication lives in one filter, so a handler stays a thin use case (ADR-014) and does not grow ad-hoc \"did I already do this?\"…","i":"Idempotent"},{"u":"/docs/adr/017-request-idempotency.html#trade-offs","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cross-instance mutual exclusion follows Redis, so it is a deployment property, not a guarantee. Every ADC and Store service host registers a Redis IConnectionMultiplexer when a…","i":"IConnectionMultiplexer StatusCodeResult IAnonymizable ObjectResult Idempotent Location redis"},{"u":"/docs/adr/017-request-idempotency.html#related","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (handler idempotency for outbox/event consumers, a distinct concern), ADR-013 (Result is the response the filter caches/replays), ADR-014 (the filter keeps the handler…","i":"ICacheService"},{"u":"/docs/adr/018-polyglot-persistence.html","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records"},{"u":"/docs/adr/018-polyglot-persistence.html#status","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Status","x":"Accepted. The framework plumbing is complete, covered by unit and integration tests (DataSourceResolverTests, CrossDataSourceDegradeConventionTests, EntityTypeConfigurationTests,…","i":"CrossDataSourceDegradeConventionTests CosmosConfigurationPortabilityTests MultiSourceSqliteIntegrationTests EntityTypeConfigurationTests DataSourceResolverTests FACTS.md Session Room"},{"u":"/docs/adr/018-polyglot-persistence.html#context","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Context","x":"ADR-006 (database-per-service) splits storage along the Name axis: several physically separate databases, all on the same engine (SQL Server), one per service. A second,…","i":"DataSourceKey Engine Name"},{"u":"/docs/adr/018-polyglot-persistence.html#decision","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Decision","x":"Support three storage engines behind one entity model and one set of repository abstractions, selected per entity configuration. 1. DataSource engine enum: SQLServer (full…","i":"CrossDataSourceDegradeConvention EntityTypeConfigurationSQLServer EntityTypeConfigurationCosmos EntityTypeConfigurationSqlite SQLServerMigrationsAssembly CosmosIntIdValueGenerator SQLServerConnectionString EntityDataSourceRegistry EntityTypeConfiguration CosmosConnectionString SqliteConnectionString ApplicationDbContext"},{"u":"/docs/adr/018-polyglot-persistence.html#rationale","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Rationale","x":"- Right store per access pattern, as a configuration decision. The engine becomes an attribute on a configuration class, not a rewrite. The same domain entity, application…"},{"u":"/docs/adr/018-polyglot-persistence.html#trade-offs","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Trade-offs","x":"- No cross-engine JOINs, FKs, or transactions. This is the ADR-006 cost made sharper: across engines it is a hard limit, not a deployment choice. A query spanning engines (for…","i":"SpecificationsDoNotNavigateToOtherEntities CrossSourceSpecification specifications keys"},{"u":"/docs/adr/018-polyglot-persistence.html#related","d":"ADR-018: Polyglot Persistence (Multiple Storage Engines Behind One Model)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (database-per-service: the Name axis this ADR's Engine axis is orthogonal to; they share DataSourceKey), ADR-002 (navigation populators bridge the relationships the…","i":"DataSourceKey"},{"u":"/docs/adr/019-rate-limiting.html","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records"},{"u":"/docs/adr/019-rate-limiting.html#status","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Status","x":"Accepted. Revised 2026-08-01 (the auth-ip per-IP anonymous-authentication limiter, which the shared auth controller applies to login and register by default, is recorded as the…"},{"u":"/docs/adr/019-rate-limiting.html#context","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Context","x":"Every service exposes read and write endpoints to the public internet through the gateway (ADR-008). Abusive or runaway clients (scrapers, credential stuffing, retry storms, a…"},{"u":"/docs/adr/019-rate-limiting.html#decision","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Decision","x":"Rate limiting is layered, and the always-on global limiter is authenticated-only. 1. A global limiter that only caps authenticated callers. AddCommonRateLimiting…","i":"HttpContext.Connection.RemoteIpAddress EnableRateLimitingAttribute UseCommonMiddlewarePipeline AttributeUsage.Inherited LoginProtectionService AddCommonRateLimiting RateLimitPolicyAuthIp GetCustomAttributes UseForwardedHeaders AuthControllerBase EnableRateLimiting EndpointDataSource"},{"u":"/docs/adr/019-rate-limiting.html#rationale","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Rationale","x":"- Limit the traffic that is both attributable and expensive. An authenticated request is tied to a principal and usually drives the database; capping per-principal stops a single…"},{"u":"/docs/adr/019-rate-limiting.html#trade-offs","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Trade-offs","x":"- The global limiter only protects the authenticated surface. The anonymous surface is covered endpoint by endpoint instead: login and register carry the auth-ip limiter by…","i":"ForwardLimit"},{"u":"/docs/adr/019-rate-limiting.html#related","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the JWKS/discovery traffic the limiter exempts, and the authenticated principal it keys on), ADR-008 (the gateway edge this protects), ADR-017 (request idempotency, the…"},{"u":"/docs/adr/020-permission-based-authorization.html","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records"},{"u":"/docs/adr/020-permission-based-authorization.html#status","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-25, amended 2026-07-10)."},{"u":"/docs/adr/020-permission-based-authorization.html#context","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Context","x":"Authorization started as pure role-based access control (RBAC). Endpoints declared the role they required with [Authorize(Policy = ...)] against named policies: RequireOrganizer,…","i":"RequireAuthenticatedUser RequireAuthenticated RequireOrganizer RequireAttendee RequireAdmin RequireRole Authorize Policy"},{"u":"/docs/adr/020-permission-based-authorization.html#decision","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Decision","x":"Add a permission (capability) layer over RBAC, opt-in and backward-compatible. - A central registry maps roles to permissions. IPermissionRegistry / PermissionRegistry…","i":"DefaultAuthorizationPolicyProvider PermissionAuthorizationHandler AuthClaimTypes.Permission PermissionRegistryBuilder AddAuthorizationPolicies PermissionPolicyProvider RequireAuthenticatedUser MMCA.Common.Shared.Auth RoleNames.ContentEditor HasPermissionAttribute PermissionRequirement IPermissionRegistry"},{"u":"/docs/adr/020-permission-based-authorization.html#rationale","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Rationale","x":"- Capabilities decouple endpoints from roles. A route says what it does (conference:sessions:manage), and who may do it is a registry decision, so adding ContentEditor with a…","i":"ContentEditor"},{"u":"/docs/adr/020-permission-based-authorization.html#trade-offs","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Trade-offs","x":"- It is still RBAC, not ABAC. The model resolves role to permission; it does not evaluate resource or attribute conditions. Per-resource ownership (\"a customer may read only…","i":"ConferencePermissions OwnerOrAdminFilter AddPermissions IAnonymizable Idempotent Grant"},{"u":"/docs/adr/020-permission-based-authorization.html#related","d":"ADR-020: Permission-Based Authorization Layered over Roles","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the authenticated principal and claims this keys on, including the optional permission claim), ADR-008 (each extracted service authorizes independently, so the registry…","i":"permission"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#status","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-09; adoption reviewed 2026-07-15)."},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#context","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Context","x":"ADR-003 makes integration-event delivery at-least-once: the outbox guarantees a published event is not lost, and the MassTransit broker redelivers on consumer failure.…"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#decision","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Decision","x":"Add an opt-in inbox that records each successfully-processed integration event by its MessageId and skips redeliveries. - Every event carries a MessageId. BaseDomainEvent stamps…","i":"IX_InboxMessages_MessageId IntegrationEventConsumer SessionFeedbackSubmitted SpeakerUnlinkedFromUser EventFeedbackSubmitted AlreadyProcessedAsync ProductVariantChanged OutboxCleanupService SpeakerLinkedToUser AddBrokerMessaging MarkProcessedAsync AttendeeCheckedIn"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#rationale","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Rationale","x":"- Dedup once, not in every handler. A single consume-edge check turns \"every handler author must remember to be idempotent against redelivery\" into a framework guarantee for the…","i":"NoOpInboxStore"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#trade-offs","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Trade-offs","x":"- Not exactly-once. The crash-after-handler-before-inbox window reprocesses once, so handlers must stay idempotent for it; the inbox narrows the duplicate window, it does not…","i":"InboxMessages EnableInbox MessageId"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#related","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox and at-least-once delivery whose consumer side this deduplicates; handler idempotency is still required for the crash window), ADR-006 (the inbox lives in the…","i":"OutboxCleanupService"},{"u":"/docs/adr/022-browser-session-cookie-auth.html","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records"},{"u":"/docs/adr/022-browser-session-cookie-auth.html#status","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Status","x":"Accepted."},{"u":"/docs/adr/022-browser-session-cookie-auth.html#context","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Context","x":"The apps are Blazor Web Apps: a server-rendered (SSR) prerender pass runs on the first request, then an interactive phase (Blazor Server or WebAssembly) takes over.…","i":"Authorization localStorage Authorize"},{"u":"/docs/adr/022-browser-session-cookie-auth.html#decision","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Decision","x":"Carry the session in HttpOnly cookies and add an authentication scheme that reads them during SSR prerender. The mechanism ships in MMCA.Common.API (SessionCookies/) with a…","i":"SessionCookieAuthenticationHandler CookieSessionRefresher mmca_auth_refresh HttpContext.User mmca_auth_access SessionCookieJar MMCA.Common.API MMCA.Common.UI Authorize DELETE POST"},{"u":"/docs/adr/022-browser-session-cookie-auth.html#rationale","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Rationale","x":"- Fixes the fresh-GET prerender gap. Without a server-readable session, every deep-link or F5 to an [Authorize] page would redirect to /login despite a valid session; the cookie…","i":"Authorize"},{"u":"/docs/adr/022-browser-session-cookie-auth.html#trade-offs","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Trade-offs","x":"- A non-validating auth scheme exists. SessionCookieAuthenticationHandler trusts a cookie it does not cryptographically verify. This is sound only because (a) the cookie is…","i":"SessionCookieAuthenticationHandler ISessionCookieSync"},{"u":"/docs/adr/022-browser-session-cookie-auth.html#related","d":"ADR-022: Browser Session-Cookie Authentication for Blazor SSR","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the JWT/JWKS validation the API performs on every call, which is why the SSR handler can skip signature validation), ADR-008 (the gateway and topology the UI talks to),…"},{"u":"/docs/adr/023-security-response-headers.html","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records"},{"u":"/docs/adr/023-security-response-headers.html#status","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02)."},{"u":"/docs/adr/023-security-response-headers.html#context","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Context","x":"Every client-facing host (the YARP Gateway and the Blazor UI web host in each app) must stamp the same hardened HTTP response headers: X-Content-Type-Options, X-Frame-Options,…"},{"u":"/docs/adr/023-security-response-headers.html#decision","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Decision","x":"Ship one security-headers middleware in MMCA.Common.Aspire (MMCA.Common.Aspire.Security), registered with AddCommonSecurityHeaders(configuration?, configure?) and inserted early…","i":"SecurityHeadersSettings.ContentSecurityPolicy SecurityHeadersMiddlewareTests MMCA.Common.Aspire.Security SecurityHeadersMiddleware AddCommonSecurityHeaders MMCA.Common.Aspire.Tests UseCommonSecurityHeaders BlazorCspPolicyProvider SecurityHeadersSettings StaticCspPolicyProvider AddCommonBlazorCsp ICspPolicyProvider"},{"u":"/docs/adr/023-security-response-headers.html#rationale","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Rationale","x":"- One hardened default, defined once. Centralizing the header set removes per-host drift and makes a new edge host secure by default rather than by remembering to copy headers. -…","i":"ICspPolicyProvider"},{"u":"/docs/adr/023-security-response-headers.html#trade-offs","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Trade-offs","x":"- The baseline CSP is intentionally incomplete. An API/Gateway host gets default-src 'self'-style protection but no script-src/style-src discipline unless it registers a fuller…","i":"SecurityHeadersSettings.ContentSecurityPolicy AddCommonSecurityHeaders BlazorCspPolicyProvider AddCommonBlazorCsp ICspPolicyProvider MMCA.Common.UI.Web TryAddSingleton ApiSettings"},{"u":"/docs/adr/023-security-response-headers.html#related","d":"ADR-023: Centralized Security-Response-Headers Middleware with a Pluggable CSP","k":"Architecture Decision Records","t":"Related","x":"ADR-019 (rate limiting, the other always-on edge protection living in the same Aspire layer), ADR-022 (browser session-cookie auth, the other browser-edge security control),…"},{"u":"/docs/adr/024-push-notifications.html","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records"},{"u":"/docs/adr/024-push-notifications.html#status","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-15). Revised 2026-08-07 (transactional email recorded as an app-level concern outside the channel model; see Revision below). Revised…","i":"Enabled"},{"u":"/docs/adr/024-push-notifications.html#context","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Context","x":"The framework needs to deliver user-facing notifications (an organizer broadcasting a schedule change, a per-user alert). Two delivery models each fail on their own. A pure…"},{"u":"/docs/adr/024-push-notifications.html#decision","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Decision","x":"Deliver notifications over two channels from one application use case, with the transport and the recipient policy both behind abstractions. - A durable per-user inbox plus a…","i":"NullNotificationRecipientProvider PushNotificationSettings.Enabled INotificationRecipientProvider SignalRPushNotificationSender SendPushNotificationHandler SignalRLiveChannelPublisher MMCA.Common.Infrastructure NullPushNotificationSender IPushNotificationSender MMCA.Common.Application CancellationToken.None NotificationHubService"},{"u":"/docs/adr/024-push-notifications.html#rationale","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Rationale","x":"- Each channel covers the other's failure mode. The inbox guarantees eventual delivery to offline users; the push gives connected users immediacy. Persisting the inbox before…","i":"INotificationRecipientProvider IPushNotificationSender IMessageBus"},{"u":"/docs/adr/024-push-notifications.html#trade-offs","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Fan-out write amplification. One UserNotification row is written per recipient, so a broadcast to a large audience is a large insert. This is fine for the current per-event /…","i":"NullPushNotificationSender AddPushNotifications PushNotification UserNotification Authorization access_token IsRead ReadOn"},{"u":"/docs/adr/024-push-notifications.html#related","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox dual-dispatch path, which is distinct: that carries service-to-service integration events, this carries user-facing notifications), ADR-004 (the /hubs…","i":"MMCA.ADC.Notification.Service SendPushNotificationHandler NullNativePushSender Http1AndHttp2 access_token Http2"},{"u":"/docs/adr/024-push-notifications.html#revision-2026-08-07","d":"ADR-024: Two-Channel User Notifications (Transient SignalR Push + Durable Inbox)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Records transactional email, a delivery path the channel model above never mentions. The decision is unchanged: this closes a documentation gap so the asymmetry reads as…","i":"OrderPaymentFailedSagaHandler SendPushNotificationHandler IPushNotificationSender ILiveChannelPublisher IPushDeviceRegistrar IDomainEventHandler AddInfrastructure INativePushSender OrderPaidHandler PushNotification UserNotification SmtpEmailSender"},{"u":"/docs/adr/025-startup-warmup-readiness.html","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records"},{"u":"/docs/adr/025-startup-warmup-readiness.html#status","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27). Revised 2026-07-28: /health/ready now excludes optional-tagged checks as well as live-tagged ones (see Decision), and the absence of a warm-up timeout was…","i":"optional live"},{"u":"/docs/adr/025-startup-warmup-readiness.html#context","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Context","x":"On the Azure Container Apps Consumption plan a replica that has been idle is CPU-throttled, and a scale-from-zero or scaled-out replica starts cold. The first authenticated…"},{"u":"/docs/adr/025-startup-warmup-readiness.html#decision","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Decision","x":"Ship a small warm-up subsystem in MMCA.Common.Aspire, wired into AddServiceDefaults() so every host gets it. - A readiness gate that starts closed. WarmupReadinessGate…","i":"OpenIdConnectMetadataWarmupTask OperationCanceledException WarmupReadinessHealthCheck MapDefaultEndpoints WarmupHostedService WarmupReadinessGate AddServiceDefaults AddWarmupReadiness IHttpClientFactory MMCA.Common.Aspire TaskTimeoutSeconds cancellationToken"},{"u":"/docs/adr/025-startup-warmup-readiness.html#rationale","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Rationale","x":"- Keep cold replicas out of rotation, briefly. Gating readiness on warm-up means the platform does not send a user request to a replica that is still doing its first handshakes,…","i":"AddServiceDefaults"},{"u":"/docs/adr/025-startup-warmup-readiness.html#trade-offs","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Trade-offs","x":"- A replica can enter rotation not fully warm. The gate is opened in a finally once the Task.WhenAll over every registered task returns, that is, once each task has completed,…","i":"ConfigurationManager TimeoutException stoppingToken Task.WhenAll WaitAsync finally"},{"u":"/docs/adr/025-startup-warmup-readiness.html#related","d":"ADR-025: Startup Warm-Up and Readiness Gating for Cold-Start Mitigation","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the OIDC discovery document the built-in task pre-fetches, and the auth-side view of the same cold-start), ADR-009 (the Polly resilience pipeline that absorbs the lazy…"},{"u":"/docs/adr/026-caching-strategy.html","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","i":"ICacheService"},{"u":"/docs/adr/026-caching-strategy.html#status","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-10, 2026-07-23, 2026-07-25, 2026-08-14). Amended by ADR-077 (2026-08-13): Tier 1's substrate gains a third, opt-in implementation…","i":"HybridCacheService"},{"u":"/docs/adr/026-caching-strategy.html#context","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Context","x":"The framework needs caching in two distinct places. Inside the application pipeline, query results are memoized and invalidated on mutation (the Caching decorators of ADR-014,…","i":"ICacheInvalidating IQueryCacheable"},{"u":"/docs/adr/026-caching-strategy.html#decision","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Decision","x":"Cache in two tiers, each with its own substrate. - One abstraction. ICacheService (MMCA.Common.Application/Interfaces/ICacheService.cs) exposes GetAsync / SetAsync / RemoveAsync…","i":"builder.Services.AddStackExchangeRedisOutputCache OutputCacheOptions.AddPublicEndpointPolicy AbsoluteExpirationRelativeToNow PublicEndpointOutputCachePolicy CacheOptions.DefaultExpiration CacheOptions.DefaultDuration DistributedCacheEntryOptions DistributedCacheService IConnectionMultiplexer LoginProtectionService MemoryDistributedCache AddCommonHybridCache"},{"u":"/docs/adr/026-caching-strategy.html#rationale","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Rationale","x":"- One substrate, swapped by environment. Keeping ICacheService as the only thing application code sees lets the deployment decide memory vs distributed. The auto-swap (presence…","i":"ICacheInvalidating IDistributedCache ICacheService"},{"u":"/docs/adr/026-caching-strategy.html#trade-offs","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Trade-offs","x":"- Memory mode is per-replica. In the in-process store each replica caches independently; a scaled-out deployment that did not wire Redis would see cross-replica staleness bounded…","i":"ICacheService.IncrementAsync AddRedisDistributedCache DistributedCacheService StackExchangeRedisCache IConnectionMultiplexer AddOutputCache AddRedisClient RemoveAsync WRONGTYPE NoCache absexp sldexp"},{"u":"/docs/adr/026-caching-strategy.html#related","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the Caching decorators and IQueryCacheable / ICacheInvalidating markers that consume this substrate), ADR-019 (output caching as the anonymous-traffic lever, and…","i":"LoginProtectionService HybridCacheService ICacheInvalidating IQueryCacheable IncrementAsync ICacheService WRONGTYPE"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-24","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Three substrate corrections from a code review. 1. An optional key namespace (Cache:KeyPrefix). Services sharing one cache instance also share one keyspace, and nothing stopped…","i":"RedisCacheOptions.InstanceName ICacheService.IncrementAsync DistributedCacheService EvictionReason.Replaced KeyedSemaphoreStripe RemoveByPrefixAsync MemoryCacheService IMemoryCache InstanceName CacheKey INCR"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-25","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"An audit against the code. No behavior changed; the ADR text did. 1. The IncrementAsync entry above was wrong. It described a Redis INCR override. There is no such override, and…","i":"DistributedCacheService StackExchangeRedisCache IncrementAsync AddCaching INCR"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-07-28","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-07-28)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. Tier 2. Store Catalog's…","i":"AddStackExchangeRedisOutputCache DistributedCacheService MMCA.Common.API ICacheService AddCaching"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-01","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. AddCaching. Now at…","i":"AddStackExchangeRedisOutputCache DistributedCacheService RemoveByPrefixAsync ScanAndDeleteAsync IncrementAsync AddCaching remarks returns"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-07","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Line anchors only, re-verified against the current source. No decision, no behavior, and no substantive prose changed. 1. AddCaching. Now at…","i":"AddStackExchangeRedisOutputCache DistributedCacheService MemoryDistributedCache TimeSpan.FromSeconds MemoryCacheService IDistributedCache MMCA.Common.API CacheOptions AddCaching"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-13","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-13)","x":"Tier 1 is amended by ADR-077, which is where the decision and its trade-offs are recorded. The three points that change the reading of this record: 1. A third substrate, opted…","i":"Microsoft.Extensions.Caching.Hybrid DistributedCacheService StackExchangeRedisCache AddCommonHybridCache HybridCacheService MemoryCacheService IDistributedCache IncrementAsync AddCaching WRONGTYPE prefix INCR"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-14","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"One substrate correction plus a line-anchor re-verification. No decision and no behavior changed. 1. The 30-second default now has a named home, CacheOptions.DefaultDuration.…","i":"AddStackExchangeRedisOutputCache AbsoluteExpirationRelativeToNow CacheOptions.DefaultDuration DistributedCacheEntryOptions AddRedisDistributedCache DistributedCacheService HybridCacheEntryOptions TimeSpan.FromSeconds app.UseOutputCache HybridCacheService DefaultExpiration DefaultDuration"},{"u":"/docs/adr/027-multi-locale-i18n.html","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records"},{"u":"/docs/adr/027-multi-locale-i18n.html#status","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27, amended 2026-07-02, 2026-07-03, 2026-07-09, and 2026-07-29; corrected 2026-08-01: the pseudo-locale CI gate is required on all three browser engines, and…"},{"u":"/docs/adr/027-multi-locale-i18n.html#context","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Context","x":"ADR-011 recorded single-locale (en-US) as a deliberate, revisitable non-goal and sketched what re-introducing i18n would entail. That revisit has now happened: the framework adds…","i":"InteractiveAuto Error Code"},{"u":"/docs/adr/027-multi-locale-i18n.html#decision","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Decision","x":"1. Supported cultures are an explicit allowlist: en-US (default) + es. Adding a locale is adding a .es.resx sibling set and one allowlist entry, not new infrastructure. 2.…","i":"MmcaCultureBootstrap.SetBrowserCultureAsync ErrorHttpMapping.BuildErrorsExtension DomainInvariantViolationException CultureInfo.DefaultThreadCurrent LocalizedTextConventionTestsBase MMCA.Common.Testing.Architecture SupportedCultures.ResolveClosest ApiControllerBase.HandleFailure ResourceTranslationsAreComplete SupportedCultures.PseudoLocale CookieRequestCultureProvider CultureInfo.InvariantCulture"},{"u":"/docs/adr/027-multi-locale-i18n.html#rationale","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Rationale","x":"- Keying error localization on the existing Error.Code is the cheapest correct extension point. The codes are already stable and already cross the wire; localizing at the edge…","i":"ResourcesPath Error.Code resx"},{"u":"/docs/adr/027-multi-locale-i18n.html#trade-offs","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every view and every user-facing message is touched: a large, mostly mechanical sweep, accepted as the cost ADR-011 always named. - WASM Spanish formatting needs ICU…","i":"InvariantGlobalization ResxMudLocalizer MudTranslations BlazorWebView MudLocalizer"},{"u":"/docs/adr/027-multi-locale-i18n.html#related","d":"ADR-027: Multi-Locale Internationalization (Supersedes ADR-011)","k":"Architecture Decision Records","t":"Related","x":"ADR-011 (superseded), ADR-013 (the Error.Code this localizes on), ADR-015 (the i18n gates now live here: the MA0076 culture-less formatting build gate and the…","i":"ResourceTranslationsAreComplete BlazorWebView Error.Code MA0076"},{"u":"/docs/adr/028-dark-theme-mode.html","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records"},{"u":"/docs/adr/028-dark-theme-mode.html#status","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27; revised 2026-07-15)."},{"u":"/docs/adr/028-dark-theme-mode.html#context","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Context","x":"MMCATheme (MMCA.Common.UI/Theme/MMCATheme.cs) has always defined a complete, brand-tuned PaletteDark alongside PaletteLight, but MudThemeProvider was hard-wired to light: no…","i":"MudThemeProvider InteractiveAuto PaletteLight PaletteDark IsDarkMode MMCATheme ref"},{"u":"/docs/adr/028-dark-theme-mode.html#decision","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Decision","x":"1. Bind the existing theme. The shared MainLayout renders a single component (MMCA.Common.UI/Layout/MainLayout.razor:14), which owns the four Mud providers plus the Day/Dark…","i":"ThemeService.InitializeAsync User.PreferredCulture User.PreferredTheme MMCATheme.Instance MmcaThemeProviders OnAfterRenderAsync InteractiveServer systemPrefersDark window.matchMedia MudThemeProvider MMCA.Common.UI ThemeService"},{"u":"/docs/adr/028-dark-theme-mode.html#rationale","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Rationale","x":"- Reusing the i18n cookie/profile machinery means one persistence model for both user preferences, instead of two subtly different ones. Theme and locale are the same shape of…","i":"BrandColorTokenTests"},{"u":"/docs/adr/028-dark-theme-mode.html#trade-offs","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Trade-offs","x":"- The same FOUC hazard as locale is not yet closed for theme. The SSR data-theme/inline-script read is unimplemented (Decision 3), so the first paint can briefly flash the wrong…","i":"MainLayout User"},{"u":"/docs/adr/028-dark-theme-mode.html#related","d":"ADR-028: Day/Dark Theme Mode","k":"Architecture Decision Records","t":"Related","x":"ADR-027 (shares the cookie source-of-truth and the User preference migration, and is the model for the theme no-flash SSR bootstrap that is not yet wired), ADR-022 (the SSR…","i":"User"},{"u":"/docs/adr/029-authentication-brute-force-protection.html","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#status","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27). Updated 2026-07-02 (the check/increment/reset call sequence was hoisted into AuthenticationServiceBase ; the adoption note and the \"convention the consumer…","i":"AuthenticationServiceBase TUser"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#context","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Context","x":"ADR-019's global rate limiter is authenticated-only: it caps requests per authenticated principal and deliberately exempts anonymous traffic. The highest-value anonymous attack…","i":"RateLimitPolicyAuthIp AuthControllerBase"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#decision","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Decision","x":"Provide a framework ILoginProtectionService (MMCA.Common.Application.Auth) with a single implementation LoginProtectionService (MMCA.Common.Infrastructure.Auth), registered…","i":"RegistrationRateLimitWindowMinutes CheckRegistrationRateLimitAsync IncrementRegistrationCountAsync MMCA.Common.Infrastructure.Auth ICacheService.IncrementAsync IncrementFailedAttemptsAsync MaxRegistrationsPerIpPerHour MMCA.Common.Application.Auth FailedAttemptWindowMinutes AuthenticationServiceBase ResetFailedAttemptsAsync DistributedCacheService"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#rationale","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Rationale","x":"- Complements ADR-019 rather than duplicating it. ADR-019 carries two limiter layers and this is the third on top of them: its global limiter caps authenticated throughput per…","i":"Result"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#trade-offs","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cache-scoped state weakens under scale-out without Redis. In memory mode the counters are per-replica and evaporate on restart, so a multi-replica deployment that did not wire…","i":"AuthenticationServiceBase ILoginProtectionService AuthenticationService TUser"},{"u":"/docs/adr/029-authentication-brute-force-protection.html#related","d":"ADR-029: Authentication Brute-Force Protection and Registration Throttling","k":"Architecture Decision Records","t":"Related","x":"ADR-019 (the layered limiter: an authenticated-only global cap that exempts this anonymous surface, plus the per-IP auth-ip window that now sits on the same two endpoints),…","i":"ICacheService Result Error"},{"u":"/docs/adr/030-startup-sole-migrator.html","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records"},{"u":"/docs/adr/030-startup-sole-migrator.html#status","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27)."},{"u":"/docs/adr/030-startup-sole-migrator.html#context","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Context","x":"Under database-per-service (ADR-006), each service owns its own database and its own migrations project, so something must apply pending migrations on every deploy. The…","i":"ApplicationSettings.DatabaseInitStrategy DatabaseInitializationExtensions EnsureCreated"},{"u":"/docs/adr/030-startup-sole-migrator.html#decision","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Decision","x":"In Azure Container Apps, every service host runs ApplicationSettingsDatabaseInitStrategy = Migrate in production and is the sole migrator of its own database: it applies its…","i":"ApplicationSettings__DatabaseInitStrategy __EFMigrationsHistory DatabaseInitStrategy MigrateAsync minReplicas deploy.yml migrations database Migrate dotnet sqlcmd update"},{"u":"/docs/adr/030-startup-sole-migrator.html#rationale","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Rationale","x":"- One migrator, one mechanism. The code that owns the schema applies the schema; there is no second tool to keep in lockstep and no ordering race between a deploy step and…","i":"__EFMigrationsHistory"},{"u":"/docs/adr/030-startup-sole-migrator.html#trade-offs","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Auto-migrate-in-production is what \"None\" exists to prevent. An unintended or destructive migration would ship itself on the next deploy. The apps accept this; the build-time…","i":"minReplicas"},{"u":"/docs/adr/030-startup-sole-migrator.html#related","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (database-per-service: why each service owns and migrates its own database), ADR-025 (readiness gating keeps traffic off a still-migrating replica), ADR-009 (RTO/RPO +…"},{"u":"/docs/adr/030-startup-sole-migrator.html#revision-2026-08-07","d":"ADR-030: Each Service Self-Applies Its Migrations at Startup (Sole Migrator)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"The sole-migrator decision extends to seed data: the same startup owner that applies the schema also runs the module seeders, in the same call, on the same boot. The Decision…","i":"moduleLoader.SeedAllAsync ModuleLoader.SeedAllAsync ConferenceModuleDbSeeder InitializeDatabaseAsync __EFMigrationsHistory DatabaseInitStrategy builder.Build IModuleSeeder ExistsAsync DbSeeder Guid int"},{"u":"/docs/adr/031-feature-flag-management.html","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records"},{"u":"/docs/adr/031-feature-flag-management.html#status","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-27)."},{"u":"/docs/adr/031-feature-flag-management.html#context","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Context","x":"The apps need to decouple release from deploy: ship code dark, flip a kill switch, or roll a feature out to a percentage of users without a redeploy. A flag has to be enforceable…","i":"FeatureGate"},{"u":"/docs/adr/031-feature-flag-management.html#decision","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Decision","x":"Standardize on Microsoft.FeatureManagement, configured from the \"FeatureManagement\" configuration section and registered once in AddAPI (services.AddFeatureManagement() +…","i":"ApiControllerBase.HandleFailure Microsoft.FeatureManagement.Mvc IFeatureManager.IsEnabledAsync services.AddFeatureManagement FeatureGateCommandDecorator Microsoft.FeatureManagement FeatureGateQueryDecorator Error.NotFoundError ConferenceFeatures EngagementFeatures ErrorType.NotFound CatalogFeatures"},{"u":"/docs/adr/031-feature-flag-management.html#rationale","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Rationale","x":"- Release decoupled from deploy. A kill switch or a percentage rollout becomes a configuration change, not a code change: the central reason feature management exists. - Two…"},{"u":"/docs/adr/031-feature-flag-management.html#trade-offs","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Trade-offs","x":"- The two enforcement points must agree. A flag gated on the controller but not the handler (or vice versa) is a half-protected feature; no fitness rule asserts both are wired,…","i":"IsEnabledAsync"},{"u":"/docs/adr/031-feature-flag-management.html#related","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the decorator pipeline whose outermost slot FeatureGate fills, and the ordering that puts it first), ADR-013 (the Result / Error and ProblemDetails edge the disabled…","i":"FeatureGate Result Error"},{"u":"/docs/adr/032-password-hashing.html","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records"},{"u":"/docs/adr/032-password-hashing.html#status","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-29, adoption note revised 2026-07-06, registration note revised 2026-08-01)."},{"u":"/docs/adr/032-password-hashing.html#context","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Context","x":"Identity stores a credential as a (salt, hash) pair, never plaintext. The framework needs one canonical hasher that every consuming Identity flow shares, so the key-derivation…"},{"u":"/docs/adr/032-password-hashing.html#decision","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Decision","x":"Provide a single IPasswordHasher (MMCA.Common.Application.Interfaces.Infrastructure, IPasswordHasher.cs:6) with one implementation PasswordHasher…","i":"MMCA.Common.Application.Interfaces.Infrastructure CryptographicOperations.FixedTimeEquals RandomNumberGenerator.GetBytes AuthenticationServiceBase Rfc2898DeriveBytes.Pbkdf2 HashAlgorithmName.SHA512 IdentityModuleDbSeeder AuthenticationService ChangePasswordHandler LegacyHmacSaltSize AddInfrastructure ComputeLegacyHash"},{"u":"/docs/adr/032-password-hashing.html#rationale","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Rationale","x":"- One framework-owned primitive, not per-app crypto. Putting the algorithm, work factor, salt size, and comparison in a single shared type means a future hardening (raising…","i":"IsLegacy"},{"u":"/docs/adr/032-password-hashing.html#trade-offs","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Trade-offs","x":"- The legacy branch is a permanent correctness dependency that looks deletable. Its load-bearing role is invisible from the method body alone, so it is the single most…","i":"VerifyPassword Iterations"},{"u":"/docs/adr/032-password-hashing.html#related","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (cross-service JWT / JWKS authentication: the hasher gates credential verification that issues the tokens that ADR-004 then validates across services), ADR-005…","i":"EncryptedStringConverter"},{"u":"/docs/adr/033-resource-ownership-authorization.html","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records"},{"u":"/docs/adr/033-resource-ownership-authorization.html#status","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02, revised 2026-07-25)."},{"u":"/docs/adr/033-resource-ownership-authorization.html#context","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Context","x":"ADR-020 added a permission (capability) layer over RBAC: it answers \"what may this role do\", resolving a role to a permission so an endpoint can require a capability instead of a…","i":"OwnerOrAdminFilter GET"},{"u":"/docs/adr/033-resource-ownership-authorization.html#decision","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Decision","x":"Provide a row/resource-level ownership axis in MMCA.Common.API (the Authorization folder), with two enforcement points keyed on the caller's owner claim (customerid by default)…","i":"ShoppingCartsController.GetAllForLookupAsync ShoppingCartByCustomerSpecification ShoppingCartsController.GetAllAsync AggregateRootEntityControllerBase CustomersController.CreateAsync CustomersController.GetAllAsync OrdersByCustomerSpecification GetOwnershipSpecification OwnerOrAdminFilterOptions ICurrentUserService.Role OwnershipHelper.IsAdmin settings.OwnerClaimType"},{"u":"/docs/adr/033-resource-ownership-authorization.html#rationale","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Rationale","x":"- Reject-one and filter-many are genuinely two mechanisms. A single-resource route has an id to compare, so a short action filter that 403s on a mismatch is the cheapest correct…","i":"IEntityQueryService Specification Criteria TEntity And TId"},{"u":"/docs/adr/033-resource-ownership-authorization.html#trade-offs","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per controller/handler. Neither point is automatic: a controller that forgets the [ServiceFilter] or omits the ownership spec from a query leaks across customers, the…","i":"OwnerOrAdminFilter ServiceFilter customer_id null"},{"u":"/docs/adr/033-resource-ownership-authorization.html#related","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Related","x":"ADR-020 (the role/permission RBAC layer this complements, and whose explicit 020-permission-based-authorization.md:74 scope-out this fills), ADR-034 (the generic entity query…","i":"IEntityQueryService Specification ForbidResult Result"},{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-07-25","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"An audit against the code. No behavior changed; the ADR text did. 1. The per-mutation check's failure shape was described as one branch, and it is two. ValidateOwnershipAsync was…","i":"ICurrentUserService.Role ValidateOwnershipAsync OwnerOrAdminFilter AllowMissingOwner OrdersController Error.Forbidden"},{"u":"/docs/adr/033-resource-ownership-authorization.html#revision-2026-08-01","d":"ADR-033: Resource-Ownership Authorization (Row-Level + Action Filter)","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Anchor-only correction. No behavior changed; OrdersController was refactored (a constructor parameter added, GetOwnershipSpecification() and the IsAdmin property extracted,…","i":"GetOwnershipSpecification ValidateOwnershipAsync OrdersController Error.Forbidden Error.NotFound IsAdmin"},{"u":"/docs/adr/034-generic-entity-query-layer.html","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/034-generic-entity-query-layer.html#status","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-06-30). Amended (2026-07-23): the filter strategy registry now also covers long/long? via LongFilterStrategy. Amended (2026-07-25): the keyed by-id fast path is…","i":"TryGetFastPathIncludes LongFilterStrategy long"},{"u":"/docs/adr/034-generic-entity-query-layer.html#context","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Context","x":"Every module exposes many entities, and most of them need the same read and write surface: list, page, look up for a dropdown, fetch by id, create, delete. Hand writing a…"},{"u":"/docs/adr/034-generic-entity-query-layer.html#decision","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Decision","x":"Give every entity a generic REST resource surface and a bounded dynamic query contract, supplied by two controller bases over a shared query pipeline. 1. Generic read controller.…","i":"EntityQueryPipeline.MaxUnboundedResultLimit QueryFieldService.ApplyFieldSelection QueryFilterService.RegisterStrategy IApplicationSettings.MaxPageSize QueryFilterService.ApplyFilters QueryFieldService.ApplySorting MaxUnboundedResultLimit QueryFilterModelBinder INavigationPopulator EntityQueryPipeline SupportedOperators IFilterStrategy"},{"u":"/docs/adr/034-generic-entity-query-layer.html#rationale","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Rationale","x":"- Write the resource surface once, inherit it everywhere. The verbs, routes, filter contract, pagination, and header are defined once on the two bases; a concrete controller…","i":"QueryFilterService.ValidateFilters MaxUnboundedResultLimit DTOToEntityPropertyMap INavigationPopulator DTOMapper.MapToDTOs SupportedOperators IEntityDTOMapper IFilterStrategy MaxPageSize"},{"u":"/docs/adr/034-generic-entity-query-layer.html#trade-offs","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- The wire contract tracks the entity model. Filterable, sortable, and projectable surface is the entity's property set. A model change is an API change unless mediated by the…","i":"QueryFilterService.ValidateFilters MaxUnboundedResultLimit DTOToEntityPropertyMap IFilterStrategy virtual"},{"u":"/docs/adr/034-generic-entity-query-layer.html#related","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-001 (manual DTO mapping: the generic controllers project through IEntityDTOMapper), ADR-002 (navigation populators: the unsupported-include path), ADR-013 (Result pattern at…","i":"IEntityDTOMapper HandleFailure result.Errors Idempotent"},{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-07-24","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Filter and pagination corrections from a code review. The contract shape is unchanged; these close cases where the engine widened a result set instead of narrowing it, or…","i":"IFilterStrategy.CanParseValue PaginationMetadata.PageSize MaxUnboundedResultLimit DTOToEntityPropertyMap Filter.Value.Invalid ValidateFilters FirstOrDefault TotalItemCount ApplyFilters GetByIdAsync int.MaxValue includeFKs"},{"u":"/docs/adr/034-generic-entity-query-layer.html#revision-2026-07-25","d":"ADR-034: Generic Entity Controllers with a Dynamic Query Contract","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"Citation maintenance from an ADR audit. No decision or behavior changed; the source anchors above were rebased to their current declaration and call-site lines. 1. The fast-path…","i":"IsPrimaryKeyOnlyLookup TryGetFastPathIncludes"},{"u":"/docs/adr/035-optimistic-concurrency.html","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records"},{"u":"/docs/adr/035-optimistic-concurrency.html#status","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02). Amended 2026-07-16: a child-entity overload of SetOriginalRowVersion was added (see Decision).","i":"SetOriginalRowVersion"},{"u":"/docs/adr/035-optimistic-concurrency.html#context","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Context","x":"Every mutable aggregate in the framework is edited through a load-modify-save handler: the update use case fetches the tracked entity, applies the request, and calls…","i":"SaveChangesAsync Conflict"},{"u":"/docs/adr/035-optimistic-concurrency.html#decision","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Decision","x":"Give every auditable entity a database-managed RowVersion concurrency token, round-trip it through the client on updates, and stamp the client's last-seen value as EF's original…","i":"MMCA.Common.Domain.Interfaces.IRowVersioned IWriteRepository.SetOriginalRowVersion ConcurrencyConventionTestsBase MMCA.Store.Architecture.Tests DbUpdateConcurrencyException MMCA.ADC.Architecture.Tests AddRowVersionToAllEntities ConfigureConcurrencyTokens DbUpdateExceptionHandler SetOriginalRowVersion AuditableBaseEntity ErrorType.Conflict"},{"u":"/docs/adr/035-optimistic-concurrency.html#rationale","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Rationale","x":"- Database-managed token over a hand-maintained version field. A SQL Server rowversion auto-increments on the server on every write; no domain code sets or reads it (the setter…","i":"DbUpdateExceptionHandler SetOriginalRowVersion DbUpdateException rowversion WHERE"},{"u":"/docs/adr/035-optimistic-concurrency.html#trade-offs","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in at the caller, not just the type. A null or empty RowVersion skips the check, so a client that never echoes the token still gets last-write-wins. The fitness function…","i":"AddRowVersionToAllEntities DbUpdateExceptionHandler IsConcurrencyToken DbUpdateException UpdateRequest rowversion RowVersion byte"},{"u":"/docs/adr/035-optimistic-concurrency.html#related","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Related","x":"ADR-017 (HTTP request idempotency, which dedups retries of the same request, the mirror-image concern to two distinct edits racing here), ADR-021 (consumer-side inbox, which…","i":"AuditableBaseEntity RowVersion"},{"u":"/docs/adr/036-external-oauth-login.html","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records"},{"u":"/docs/adr/036-external-oauth-login.html#status","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-02, migration attribution corrected 2026-07-06, native-callback redirect branch added 2026-07-17 per ADR-043, email-verified account-takeover guard before…"},{"u":"/docs/adr/036-external-oauth-login.html#context","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Context","x":"The framework's Identity story so far is entirely first-party: a user registers with an email and password, the credentials are hashed (ADR-032), and Identity mints its own RS256…","i":"AddPermissions User"},{"u":"/docs/adr/036-external-oauth-login.html#decision","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Decision","x":"Add an opt-in external-login path that federates Google/GitHub sign-in at the edge and immediately exchanges the external identity for the app's own local JWT pair, linking the…","i":"IAuthenticationService.ExternalLoginAsync OAuthControllerBase.CompleteAsync AddExternalLoginProviderFields Auth.ExternalLoginNotSupported Auth.ExternalEmailNotVerified ConfigurationOAuthUISettings Auth.ExternalEmailInvalid User.LinkExternalProvider AddExternalAuthProviders AddCommonAuthentication AuthenticationResponse IAuthenticationService"},{"u":"/docs/adr/036-external-oauth-login.html#rationale","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Rationale","x":"- Terminate federation at the edge, keep one internal identity. Exchanging the external principal for a local JWT the moment the callback returns means every downstream concern…","i":"ExternalLoginAsync ClientId POST User GET"},{"u":"/docs/adr/036-external-oauth-login.html#trade-offs","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per app, and easy to half-wire. The flow needs four cooperating pieces (scheme registration, the controller subclass, the service override, and the OAuthUIBaseUrl…","i":"Auth.ExternalLoginNotSupported Auth.ExternalEmailNotVerified IExternalLoginEmailVerifier OAuth__UIBaseUrl IsExternalLogin email_verified ExternalLogin LoginProvider ProviderKey ClientId User"},{"u":"/docs/adr/036-external-oauth-login.html#related","d":"ADR-036: External OAuth Login (Federated Google/GitHub) with Local-JWT Exchange","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the RS256/JWKS token this flow exchanges the external identity for, and validates everywhere after), ADR-022 (the browser cookies that carry the resulting session),…","i":"User.Anonymize CompleteAsync"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#status","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-06; revised 2026-07-24, 2026-07-25, 2026-08-15)."},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#context","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Context","x":"Transparent database encryption (TDE) protects the data files as a whole, but it decrypts transparently for anyone who can query the database, so a leaked backup restored on a…"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#decision","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Decision","x":"Provide a single framework-owned EF Core value converter that transparently encrypts string columns at rest with authenticated encryption, applied per property in an entity…","i":"MMCA.Common.Infrastructure.Persistence.Encryption ArgumentNullException.ThrowIfNull RandomNumberGenerator.GetBytes EncryptedStringConverterTests MMCA.Common.Infrastructure EncryptedStringConverter CryptographicException IReadOnlyDictionary ArgumentException FromBase64String FrozenDictionary AesGcm.Decrypt"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#rationale","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Rationale","x":"- Authenticated, not merely confidential. AES-GCM binds a 128-bit tag to the ciphertext (EncryptedStringConverter.cs:81, :201), so a tampered or truncated value fails to decrypt…","i":"AesGcm.Decrypt string"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#trade-offs","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Latent today, proven by tests rather than production. The plumbing is complete and unit-tested, but no entity configuration wires it, so the encrypt/decrypt round-trip, the…","i":"EncryptedStringConverterTests CryptographicException HasConversion byte"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#related","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (soft-delete vs erasure: the other sensitive-data control, which names this converter as the mechanism for erasure fields that must stay retrievable,…"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-07-24","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Documented a constraint the converter always had but did not state: the ciphertext is non-deterministic. Every write uses a fresh random nonce, which is the correct property for…","i":"Email Where"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-07-25","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-07-25)","x":"Documentation-only correction, no behavior change. Item 1 of the Decision still illustrated the converter with builder.Property(e = e.Email), contradicting the 2026-07-24…","i":"EncryptedStringConverter.cs SocialSecurityNumber builder.Property e.Email"},{"u":"/docs/adr/037-field-level-encryption-at-rest.html#revision-2026-08-15","d":"ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)","k":"Architecture Decision Records","t":"Revision (2026-08-15)","x":"Behavior change, not a documentation correction. The stored layout is now a versioned envelope: Base64 of [key version (1)] [nonce (12)] [ciphertext (N)] [tag (16)] rather than…","i":"SaveChanges ciphertext DbContext version nonce key tag"},{"u":"/docs/adr/038-supply-chain-provenance.html","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records"},{"u":"/docs/adr/038-supply-chain-provenance.html#status","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-06; revised 2026-07-21)."},{"u":"/docs/adr/038-supply-chain-provenance.html#context","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common is a published framework: it packs its NuGet packages and pushes them to GitHub Packages on every v tag (release.yml:3-5), where the two production apps and the…","i":"Directory.Build.props nuget.config"},{"u":"/docs/adr/038-supply-chain-provenance.html#decision","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Decision","x":"Treat supply-chain integrity as a set of build-gating controls, the same invariant-over-discipline posture ADR-015 applies to architecture rules. Four controls, each a hard gate:…","i":"SQLitePCLRaw.bundle_e_sqlite3 RestorePackagesWithLockFile MMCA.Common.Infrastructure Directory.Packages.props Directory.Build.props TreatWarningsAsErrors packageSourceMapping NuGetAuditSuppress packages.lock.json MMCA.Common.slnx nuget.config NuGetAudit"},{"u":"/docs/adr/038-supply-chain-provenance.html#rationale","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Rationale","x":"- Provenance is a gate, not a document. A hard-failing SBOM step means the bill of materials cannot silently go missing on a release: the artifact is produced or the release…","i":"Directory.Build.props NuGetAuditSuppress dotnet list"},{"u":"/docs/adr/038-supply-chain-provenance.html#trade-offs","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The SBOM is generated and archived, not yet signed or attested. The gate proves a bill of materials exists for each release (release.yml:58); it does not add cryptographic…","i":"NuGetAuditSuppress nuget.config dotnet list"},{"u":"/docs/adr/038-supply-chain-provenance.html#related","d":"ADR-038: Supply-Chain Provenance (SBOM Release Gate + Lock Files + Vulnerability Audit)","k":"Architecture Decision Records","t":"Related","x":"ADR-016 (lockstep versioning + the MassTransit-v8 license pin; this record extends dependency governance from versioning and licensing into supply-chain provenance and…"},{"u":"/docs/adr/039-live-channel-push.html","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records"},{"u":"/docs/adr/039-live-channel-push.html#status","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-09)."},{"u":"/docs/adr/039-live-channel-push.html#context","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Context","x":"Conference-day features (live polls, session Q&A, live result counters) need sub-second fan-out of small events to whoever is looking at a page right now. The existing…"},{"u":"/docs/adr/039-live-channel-push.html#decision","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Decision","x":"One realtime transport, two publisher boundaries: - NotificationHub stays the single hub and gains its first client-invokable methods: JoinChannel / LeaveChannel map the calling…","i":"PushNotificationSettings.ChannelKeyPattern SignalRLiveChannelPublisher NullLiveChannelPublisher IPushNotificationSender NotificationHubService ILiveChannelPublisher AddPushNotifications ReceiveChannelEvent LeaveChannelAsync JoinChannelAsync NotificationHub OnChannelEvent"},{"u":"/docs/adr/039-live-channel-push.html#rationale","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Rationale","x":"- One WebSocket per client keeps connection management, token refresh, reconnect, and backplane behavior in one place; channel membership is a property of the existing…","i":"IMessageBus"},{"u":"/docs/adr/039-live-channel-push.html#trade-offs","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Ephemeral means lossy: a client that connects after an event was published never sees it. Features must treat channel events as cache-invalidation hints over fetchable state,…","i":"NotificationCallback"},{"u":"/docs/adr/039-live-channel-push.html#revision-2026-07-24","d":"ADR-039: Live channel push (ephemeral events over the notification hub)","k":"Architecture Decision Records","t":"Revision (2026-07-24)","x":"Two corrections from a code review; the best-effort, per-session-ordered decision is unchanged. 1. Broadcasts are enqueued after commit, not during the command. CastVoteHandler…","i":"BoundedChannelFullMode.DropOldest SessionQuestionUpvoteChanged LivePollVoteChanged ToggleUpvoteHandler CastVoteHandler DroppedCount itemDropped TryWrite"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#status","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10). Amended (2026-07-10): explicit query-string variance parity with the built-in default policy (the initial release accidentally dropped it, collapsing every…","i":"ContentEditor SponsorsCache NowNextCache bypassRoles Organizer"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#context","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Context","x":"The framework's read-scaling design leans on ASP.NET Core output caching: anonymous-readable endpoints ([AllowAnonymous] GETs like event/session/speaker catalogs) carry named…","i":"AuthDelegatingHandler BookmarkCountsCache AllowAnonymous Authorization NowNextCache"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#decision","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Decision","x":"MMCA.Common.API ships PublicEndpointOutputCachePolicy, an IOutputCachePolicy that mirrors the built-in default policy with one deliberate difference: it does not disable cache…","i":"ConferenceReadAudience.PrivilegedRoles PublicEndpointOutputCachePolicy DbUpdateConcurrencyException IOutputCachePolicy MMCA.Common.API AllowAnonymous Authorization ContentEditor NowNextCache extension Organizer reference"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#rationale","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Rationale","x":"- The response payload, not the request's auth state, is what determines cacheability. For a user-independent payload, Authorization is noise; refusing to cache on it turns the…","i":"Authorization"},{"u":"/docs/adr/040-authenticated-output-caching-for-public-reads.html#trade-offs","d":"ADR-040: Authenticated output caching for public reads","k":"Architecture Decision Records","t":"Trade-offs","x":"- Consumers must audit which named policies move to AddPublicEndpointPolicy. Policies on permission-gated endpoints (e.g. an organizer dashboard) must NOT move; if such an…","i":"AddStackExchangeRedisOutputCache AddRedisDistributedCache AddPublicEndpointPolicy BookmarkCountsCache IDistributedCache EvictByTagAsync AddOutputCache NowNextCache maxReplicas minReplicas TryAdd"},{"u":"/docs/adr/041-observability-and-telemetry.html","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records"},{"u":"/docs/adr/041-observability-and-telemetry.html#status","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10). Amended (2026-07-23) to document the Telemetry:DisableHttpClientMetrics and Telemetry:DisableRuntimeMetrics cost knobs and to correct the…","i":"OutboxProcessor RecordDuration OutboxMetrics OutboxProcess HttpClient finally reason"},{"u":"/docs/adr/041-observability-and-telemetry.html#context","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Context","x":"The framework is a modular monolith whose modules extract into standalone services (ADR-008), so the same telemetry has to make sense whether a request stays in one process or…","i":"HttpClient"},{"u":"/docs/adr/041-observability-and-telemetry.html#decision","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Decision","x":"Standardize telemetry in the shared Aspire service defaults, add framework-specific instrumentation for the CQRS and outbox paths, and expose cost knobs with fail-safe defaults.…","i":"APPLICATIONINSIGHTS_CONNECTION_STRING CqrsMetrics.CommandDuration.Record HttpContext.TraceIdentifier OTEL_EXPORTER_OTLP_ENDPOINT AddOpenTelemetryExporters IsInstrumentationDisabled OutboxPollFilterProcessor outbox.dead_letter.count TraceIdRatioBasedSampler CorrelationIdMiddleware ConfigureOpenTelemetry TryGetTraceSampleRatio"},{"u":"/docs/adr/041-observability-and-telemetry.html#rationale","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Rationale","x":"- Instrument only where auto-instrumentation is blind. The CQRS RED histograms and the outbox dead-letter counter cover the two framework-owned hot paths; everything else (HTTP,…","i":"ParentBased HttpClient true"},{"u":"/docs/adr/041-observability-and-telemetry.html#trade-offs","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Trade-offs","x":"- Custom instrumentation carries a maintenance cost. The Aspire package has no reference to Application or Infrastructure by design, so the meter and activity-source names are…","i":"OutboxProcess ParentBased"},{"u":"/docs/adr/041-observability-and-telemetry.html#related","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox whose dead-letter counter and poll-span filtering this defines), ADR-014 (the CQRS decorator pipeline that emits the RED histograms as a byproduct of its…","i":"AddServiceDefaults"},{"u":"/docs/adr/042-device-capability-abstraction.html","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records"},{"u":"/docs/adr/042-device-capability-abstraction.html#status","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-10, amended 2026-07-17, 2026-07-23 and 2026-08-14)."},{"u":"/docs/adr/042-device-capability-abstraction.html#context","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Context","x":"The consumer apps ship the same Blazor component set through three heads: MAUI Blazor Hybrid (Android/iOS/MacCatalyst/Windows), Blazor Server SSR, and WebAssembly. Native device…","i":"builder.Services.AddCommonMauiTokenStorage ITokenStorageService navigator.clipboard navigator.onLine navigator.share MMCA.Common.UI AddUIShared"},{"u":"/docs/adr/042-device-capability-abstraction.html#decision","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Decision","x":"Add a per-capability contract layer to MMCA.Common.UI and a fifteenth package, MMCA.Common.UI.Maui, carrying the native implementations. - One small interface per capability, no…","i":"IExternalLinkService.InterceptsLinks AddBrowserDeviceCapabilities AddDeviceCapabilityDefaults EnforceUIMauiLayerBoundary IConnectivityStatusService AddMauiDeviceCapabilities ILocalNotificationService UseMauiDeviceCapabilities Directory.Packages.props IPushDeviceTokenProvider IPushRegistrationService MauiBackNavigationBridge"},{"u":"/docs/adr/042-device-capability-abstraction.html#rationale","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Rationale","x":"- A god IDeviceCapabilities interface would force every head to implement everything and turn each new capability into a breaking change; per-capability contracts are open/closed…","i":"IDeviceCapabilities AddUIShared"},{"u":"/docs/adr/042-device-capability-abstraction.html#trade-offs","d":"ADR-042: Device Capability Abstraction (MAUI Blazor Hybrid)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A fifteenth package raises release surface: two runners must both succeed for a whole release. Accepted; the publish-maui job is gated by the same tag and SBOM discipline. -…","i":"AddMauiDeviceCapabilities UseMauiDeviceCapabilities MauiExternalAuthBroker AddUIShared IsAvailable IsSupported false"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#status","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-07-28 (the Android https App Links leg is recorded as shipped, the outstanding Android item is restated as the served certificate fingerprint,…","i":"REPLACE_WITH_PLAY_APP_SIGNING_SHA256_FINGERPRINT WebAuthenticatorCallbackActivity MapAppAssociationEndpoints sha256_cert_fingerprints MauiExternalAuthBroker AppAssociationOptions IDeepLinkDispatcher assetlinks.json MMCA.ADC.UI.Web CompleteAsync MainActivity AutoVerify"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#context","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Context","x":"Three mobile flows all need a URL to leave the web world and land inside the MAUI app: 1. Shared links and QR codes. The share sheet and QR codes carry ordinary https web URLs.…","i":"OAuthControllerBase.CompleteAsync IDeepLinkDispatcher WebAuthenticator assetlinks.json CompleteAsync"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#decision","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Decision","x":"- Custom-scheme returnUrl allowlist in the framework. CompleteAsync consults OAuth:AllowedReturnUrlSchemes (a config array, default empty). When the challenge's stashed returnUrl…","i":"IAuthUIService.ExchangeOAuthCodeAsync WebAuthenticatorCallbackActivity ITokenStorageService IDeepLinkDispatcher IExternalAuthBroker Uri.OriginalString CFBundleURLTypes WebAuthenticator assetlinks.json CompleteAsync AutoVerify returnUrl"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#rationale","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Rationale","x":"- Reusing the single-use-code exchange keeps the token-never-in-URL invariant identical across web and native; the only new surface is WHERE the code lands. - A scheme allowlist…"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#trade-offs","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Trade-offs","x":"- The app-facing hostname is baked into store binaries (intent filters, entitlements). The apps currently ride the Azure Container Apps default domain, which changes if the…","i":"appsettings.json EmbeddedResource PublicWebHost"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-07-28","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-07-28)","x":"Correction pass from an ADR audit. No decision or behavior changed; the Status section had the Android leg backwards and the Decision section attributed the token exchange to the…","i":"IAuthUIService.ExchangeOAuthCodeAsync ITokenStorageService.SetTokensAsync MapAppAssociationEndpoints sha256_cert_fingerprints AndroidCertFingerprints BuildSuccessRedirectUrl IDeepLinkDispatcher WebAuthenticator CompleteAsync IntentFilter MainActivity OnNewIntent"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-08-01","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-08-01)","x":"Status pass from an ADR audit. No decision and no behavior changed; the one item the previous revision left open is closed, and the anchor that revision itself introduced had…","i":"MapAppAssociationEndpoints sha256_cert_fingerprints AndroidCertFingerprints AndroidPackageName assetlinks.json ApplicationId Program.cs d5fd0e9"},{"u":"/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html#revision-2026-08-07","d":"ADR-043: Mobile Deep Links, App Association, and the Native OAuth Callback","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Anchor and precision pass from an ADR audit. No decision and no behavior changed. 1. The two Program.cs anchors moved one line. MMCA.ADC commit 886fa189 (PR 100, merged…","i":"app.MapAppAssociationEndpoints AndroidCertFingerprints AppAssociationOptions AndroidPackageName PublicWebHost GetSection Program.cs new"},{"u":"/docs/adr/044-native-push-delivery.html","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records"},{"u":"/docs/adr/044-native-push-delivery.html#status","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-11). Amends ADR-024. The framework pipeline is implemented and inert by default; each consumer switches it on by provisioning a notification hub with platform…","i":"NativePush"},{"u":"/docs/adr/044-native-push-delivery.html#context","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Context","x":"ADR-024 established two notification channels: a durable per-user UserNotification inbox (the source of truth) and a transient SignalR push behind IPushNotificationSender. Both…","i":"IPushNotificationSender UserNotification"},{"u":"/docs/adr/044-native-push-delivery.html#decision","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Decision","x":"- Azure Notification Hubs as the delivery fan-out. One hub abstracts both platforms behind one API, holds the platform credentials outside our code, and its installation model…","i":"INativePushSender.SendToUsersAsync Notification.PushNotifications MauiPushRegistrationService NullPushDeviceTokenProvider SendPushNotificationHandler AddNativePushNotifications AddNotificationControllers AuthUIService.LogoutAsync IPushDeviceTokenProvider IPushRegistrationService PushRegistrationListener IPushDeviceRegistrar"},{"u":"/docs/adr/044-native-push-delivery.html#consequences","d":"ADR-044: Native Push Delivery (the Third Notification Channel)","k":"Architecture Decision Records","t":"Consequences","x":"- Sends fan out per 20-user chunk and per platform: an audience of N users costs ceil(N/20) 2 hub calls. Acceptable at conference scale; a template-based send can consolidate…","i":"SendPushNotificationHandler ceil"},{"u":"/docs/adr/045-managed-file-storage-and-avatars.html","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records"},{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#status","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-11). Records the BR-116 amendment (ADC): avatar photos are IN scope, powered by two new framework extension points. The framework legs are implemented; each…"},{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#context","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Context","x":"The MAUI capability program (ADR-042) brought MediaPicker/camera within reach, and ADC amended BR-116 to include user avatar photos. That needs binary blob storage (the databases…"},{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#decision","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Decision","x":"- IFileStorageService (Application): upload-by-blob-name returning the public URI, plus idempotent delete. Default is an unconfigured Null implementation whose uploads fail with…","i":"ImageSharpImageProcessor AddAzureBlobFileStorage IFileStorageService IMediaPickerService ConnectionString IImageProcessor configuration ContainerName FileStorage IsSupported ServiceUri InputFile"},{"u":"/docs/adr/045-managed-file-storage-and-avatars.html#consequences","d":"ADR-045: Managed File Storage and User Avatars","k":"Architecture Decision Records","t":"Consequences","x":"- The avatars container is public-read by design: avatar URLs render in tags on anonymous-visible surfaces without SAS plumbing. The random blob suffix prevents enumeration; the…","i":"DefaultAzureCredential img"},{"u":"/docs/adr/046-http-api-versioning.html","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records"},{"u":"/docs/adr/046-http-api-versioning.html#status","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-08-01 (anonymity is granted by each per-service subclass, not by ServiceInfoControllerBase; corrected the ADR-034 cross-reference, which puts…","i":"ServiceInfoControllerBase AddCommonApiVersioning EntityControllerBase DefaultApiVersion Asp.Versioning"},{"u":"/docs/adr/046-http-api-versioning.html#context","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Context","x":"The framework's REST surface is served by controllers hosted in extracted service processes behind a YARP gateway. As those services evolve, a response shape has to be able to…","i":"Asp.Versioning SchemaVersion v1.0"},{"u":"/docs/adr/046-http-api-versioning.html#decision","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Decision","x":"Standardize one header-based API-versioning setup in MMCA.Common.API, adopt it in every service host through a single registration call, and keep it exercised by a shared fitness…","i":"ApiParameterDescription.ParameterDescriptor ApiParameterDescriptorBackfillProvider ServiceInfoVersioningContractTestsBase AssumeDefaultVersionWhenUnspecified ServiceInfoControllerBase SubstituteApiVersionInUrl IApiDescriptionProvider AddCommonApiVersioning Asp.Versioning.OpenApi HeaderApiVersionReader ServiceInfoController ServiceInfoV2Response"},{"u":"/docs/adr/046-http-api-versioning.html#rationale","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Rationale","x":"- Header selection keeps URLs stable. Routing stays version-free, so gateway route maps, client URL builders, and OpenAPI paths do not fork per version; a caller opts into a…","i":"AssumeDefaultVersionWhenUnspecified ServiceInfoControllerBase AddCommonApiVersioning ReportApiVersions ServiceInfo"},{"u":"/docs/adr/046-http-api-versioning.html#trade-offs","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Trade-offs","x":"- The class-level version attributes are not inherited. Each per-service subclass must repeat the [ApiVersion(...)] and routing attributes (the same inheritance caveat ADR-036…","i":"AddCommonApiVersioning MapCommonOpenApi OAuthController ApiVersion"},{"u":"/docs/adr/046-http-api-versioning.html#related","d":"ADR-046: HTTP API Versioning Strategy","k":"Architecture Decision Records","t":"Related","x":"ADR-010 (integration-event schema versioning: the asynchronous, SchemaVersion-carried, consumer-resolved axis this deliberately contrasts with; HTTP versioning here is…","i":"ServiceInfoVersioningContractTestsBase OAuthController ApiController SchemaVersion ApiVersion controller Route"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#status","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15)."},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#context","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Context","x":"Soft-delete is the framework's default deletion model (ADR-005): AuditableBaseEntity.Delete() sets IsDeleted = true and EF global query filters hide the row, but the record…","i":"AuditableBaseEntity.Delete HttpContext.User IsDeleted true"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#decision","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Decision","x":"Add a shared-pipeline middleware, SoftDeletedUserMiddleware (Source/Presentation/MMCA.Common.API/Middleware/SoftDeletedUserMiddleware.cs:31, BR-133), that rejects an…","i":"DeleteUserHandler.OnAfterSoftDeleteAsync SoftDeletedUserCache.MarkDeletedAsync SoftDeletedUserCache.MarkerDuration context.RequestServices.GetService SoftDeletedUserMiddlewareTests AuditableAggregateRootEntity SoftDeletedUserCache.KeyFor UseCommonMiddlewarePipeline ICurrentUserService.UserId ISoftDeletedUserValidator SoftDeletedUserMiddleware SoftDeletedUserValidator"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#rationale","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Rationale","x":"- Bounds the stateless-JWT revocation gap cheaply. Stateless JWT (ADR-004) has no built-in revocation, so a deactivated account would otherwise stay usable for the full remaining…","i":"ISoftDeletedUserValidator SoftDeletedUserValidator MMCA.Common.API TUser User"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#trade-offs","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Trade-offs","x":"- Revocation is bounded, not immediate. A soft-deleted user whose status is cached as not-deleted keeps passing until that cache entry expires (up to 30 seconds), unless the…","i":"SoftDeletedUserCache.MarkDeletedAsync SoftDeletedUserCache.MarkerDuration ISoftDeletedUserValidator DeleteUserHandler"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#related","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (soft-delete is the deletion model whose still-authenticated tokens this middleware revokes; deleting a user is a soft-delete, not a row removal), ADR-004 (the stateless…"},{"u":"/docs/adr/047-soft-deleted-user-session-revocation.html#revision-2026-08-07","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"Re-verified against current source. The decision is unchanged, but three things it described have moved: the validator implementation, the home of the 30-second constant, and the…","i":"SoftDeletedUserCache.MarkerDuration SoftDeletedUserMiddleware SoftDeletedUserValidator TimeSpan.FromSeconds DeleteUserHandler CacheDuration UserId TUser true User"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#status","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-15). Revised 2026-07-21 (corrected the empty-placeholder-folder inventory and the Directory.Build.props and ADC User source citations). Revised 2026-07-28…","i":"Directory.Build.props SponsorIdentifierType UserIdentifierType StronglyTypedIds User"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#context","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Context","x":"Every entity needs an identity type. The framework's base entity is generic over that type: BaseEntity constrains it to notnull and exposes a single required init Id…","i":"UserIdentifierType TIdentifierType IBaseEntity BaseEntity readonly required notnull record struct UserId Value Guid"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#decision","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Decision","x":"Model every identifier as a primitive named through a global-using alias, declared per module, not as a wrapper struct. - Identity is a primitive behind an alias. Each module…","i":"EntityTypeConfigurationSQLServer AuditableAggregateRootEntity AuthenticationServiceBase Directory.Build.props SpeakerIdentifierType AuditableBaseEntity UserIdentifierType LinkedSpeakerId IdentifierType LastModifiedBy GetRepository System.Guid"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#rationale","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Rationale","x":"- Readable signatures at zero runtime cost. GetRepository () reads as intent while the CLR sees a plain int. There is no allocation, boxing, or wrapper indirection per…","i":"UserIdentifierType IEntityDTOMapper System.Text.Json GetRepository JsonConverter IBaseEntity BaseEntity IBaseDTO Shared Guid User int"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#trade-offs","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Trade-offs","x":"- No compile-time protection against swapping same-typed identifiers. An alias is a type synonym, not a distinct type. Because most aliases resolve to int, the compiler will not…","i":"SessionIdentifierType SpeakerIdentifierType UserIdentifierType Shared Guid int"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#related","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Related","x":"ADR-001 (the per-entity DTO mappers are parameterized by this identifier type, IEntityDTOMapper ), ADR-034 (the generic entity controllers and query contract ride on the same…","i":"IEntityDTOMapper TIdentifierType TEntityDTO TEntity Shared"},{"u":"/docs/adr/049-library-configureawait-policy.html","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records"},{"u":"/docs/adr/049-library-configureawait-policy.html#status","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-20; measurements re-anchored 2026-08-07 and 2026-08-14)."},{"u":"/docs/adr/049-library-configureawait-policy.html#context","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common ships as NuGet packages consumed by host applications, not as an application itself. Library code that awaits without ConfigureAwait(false) captures the caller's…","i":"SynchronizationContext MMCA.Common.UI.Maui ConfigureAwait editorconfig VSTHRD111 RCS1090 CA2007 MA0004 false"},{"u":"/docs/adr/049-library-configureawait-policy.html#decision","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Decision","x":"Packaged non-UI framework code awaits with ConfigureAwait(false); UI component packages and application code do not. - Enforcement is a build gate, not a convention. The…","i":"TreatWarningsAsErrors MMCA.Common.UI.Maui MMCA.Common.UI.Web ConfigureAwait MMCA.Common.UI editorconfig VSTHRD111 RCS1090 warning CA2007 MA0004 false"},{"u":"/docs/adr/049-library-configureawait-policy.html#rationale","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Rationale","x":"- Correctness for the one consumer that already has a context. The MAUI head consumes Infrastructure/Application/API packages through DI; a sync-over-async call anywhere in that…","i":"ConfigureAwait GetAwaiter GetResult script batch false fixes place step but"},{"u":"/docs/adr/049-library-configureawait-policy.html#trade-offs","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Visual noise in framework source. Every await in Source/ (except UI packages) carries .ConfigureAwait(false) (324 sites at adoption; 693 gated sites as of the 2026-08-14…","i":"ConfigureAwait editorconfig false"},{"u":"/docs/adr/049-library-configureawait-policy.html#related","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Related","x":"ADR-042 (the MAUI package whose synchronization context motivates the policy), ADR-027 (the same \"machine-boundary hygiene as a build gate\" posture applied to culture-explicit…"},{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-07","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"An audit against the code. The policy did not change; three statements about it did. 1. The exemption covers three packages, not the two the Decision named. The glob is…","i":"CodeAnalysisTreatWarningsAsErrors ServerTokenStorageService TreatWarningsAsErrors MMCA.Common.UI.Maui MMCA.Common.UI.Web ConfigureAwait MMCA.Common.UI analyzers PackageId foreach warning CA2007"},{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-14","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"A re-measurement only. The policy, the gate and the exemption are unchanged; the counts the document quotes were a week old and had moved by roughly 9%. 1. Framework site counts,…","i":"CodeAnalysisTreatWarningsAsErrors ServerTokenStorageService TreatWarningsAsErrors MMCA.Common.UI.Maui MMCA.Common.UI.Web ConfigureAwait MMCA.Common.UI analyzers PackageId warning CA2007 dotnet"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#status","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-21)."},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#context","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Context","x":"Identity issues two credentials on every successful sign-in: a short-lived, stateless JWT access token that every service validates by signature and expiry (ADR-004), and a…","i":"AuthenticationServiceBase TUser"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#decision","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Decision","x":"Mint a stateless JWT access token plus a single, server-stored refresh token that rotates on every use, with a token mismatch triggering revocation. - Access token is stateless;…","i":"TokenService.GetPrincipalFromExpiredToken JwtSettings.AccessTokenExpirationMinutes JwtSettings.RefreshTokenExpirationDays TokenService.GenerateRefreshToken TokenService.RefreshTokenLifetime TokenService.GenerateAccessToken RandomNumberGenerator.GetBytes user.RevokeRefreshToken user.UpdateRefreshToken AuthenticationService RefreshTokenLifetime RefreshTokenExpiry"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#rationale","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Rationale","x":"- Short access token plus refresh keeps the hot path stateless. Every service validates the access token with no store lookup (ADR-004); the short exp bounds the revocation gap,…","i":"exp"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#trade-offs","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Trade-offs","x":"- One refresh token per user means one live session. A new login overwrites the single stored token (AuthenticationServiceBase.cs:298), so signing in on a second device…","i":"JwtSettings.RefreshTokenExpirationDays RefreshTokenExpirationDays RefreshTokenLifetime TimeSpan.Zero TokenService"},{"u":"/docs/adr/050-jwt-refresh-token-rotation.html#related","d":"ADR-050: JWT Access Tokens with a Single Rotating Refresh Token and Reuse Detection","k":"Architecture Decision Records","t":"Related","x":"ADR-004 (the stateless RS256/JWKS access token this refresh flow reissues, and the algorithm pinning GetPrincipalFromExpiredToken relies on), ADR-032 (the password hashing that…","i":"GetPrincipalFromExpiredToken AuthenticationServiceBase TUser"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#status","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-23). Revised 2026-08-14 (SetTokensAsync now writes the refresh token and the access token under one shared guard, so a failed refresh-token write also drops…","i":"SetTokensAsync"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#context","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Context","x":"ADR-022 and ADR-050 describe the two server halves of authentication: the Blazor host's HttpOnly session cookie that survives SSR prerender (ADR-022), and the Identity service's…","i":"HttpContext"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#decision","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Decision","x":"Model the client token lifecycle as two small abstractions (ITokenStorageService for persistence, ITokenRefresher for reacquisition) plus a shared bearer-attaching handler and a…","i":"AddClientAuthSessionCookieSync JwtAuthenticationStateProvider SameOriginProxyTokenRefresher ISessionCookieSync.SyncAsync AddCommonServerTokenStorage AddCommonMauiTokenStorage ServerTokenStorageService mmcaAuthSession.getToken NotifyUserAuthentication AcquireAccessTokenAsync DirectApiTokenRefresher WasmTokenStorageService"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#rationale","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Rationale","x":"- One application surface, three storage stories. Pages, services, and the HTTP pipeline talk to ITokenStorageService and AuthenticationStateProvider only; the head-specific…","i":"AuthenticationStateProvider ITokenStorageService"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#trade-offs","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Trade-offs","x":"- The browser heads depend on the same-origin UI host. SameOriginProxyTokenRefresher only works where the UI host serves the /auth/session/ endpoints; a browser head deployed…","i":"JwtAuthenticationStateProvider SameOriginProxyTokenRefresher MMCA.Common.UI.Maui MMCA.Common.UI.Web MMCA.Common.slnx MMCA.Common.UI AuthorizeView SecureStorage"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#related","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (the Blazor host's HttpOnly session cookie and the /auth/session/ endpoints the browser refresher proxies through), ADR-050 (the single rotating refresh token with reuse…","i":"DirectApiTokenRefresher MauiTokenStorageService"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-08-07","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-08-07)","x":"The MAUI half of ITokenStorageService is no longer app-local. The original Decision left the SecureStorage-backed implementation in each app because it depends on the MAUI…","i":"JwtAuthenticationStateProvider MauiTokenStorageService.cs AddCommonMauiTokenStorage DirectApiTokenRefresher MauiTokenStorageService SecureStorage.Default ITokenStorageService MMCA.Common.UI.Maui auth_refresh_token auth_access_token ClearTokensAsync MMCA.Common.slnx"},{"u":"/docs/adr/051-client-auth-token-lifecycle.html#revision-2026-08-14","d":"ADR-051: Client-Side Authentication Token Lifecycle Across Render Modes","k":"Architecture Decision Records","t":"Revision (2026-08-14)","x":"SetTokensAsync closed a gap the original hoist left open. Point 3 above previously described the method as writing the refresh token first and dropping both tokens only when the…","i":"SetTokensAsync catch try"},{"u":"/docs/adr/052-background-job-execution.html","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records"},{"u":"/docs/adr/052-background-job-execution.html#status","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-24)."},{"u":"/docs/adr/052-background-job-execution.html#context","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Context","x":"Some requests trigger work that cannot run inside the request: an AI scoring pass over an event's sessions takes minutes and issues one paid API call per session, and a…","i":"RunScoringInBackgroundAsync IHostApplicationLifetime eventId"},{"u":"/docs/adr/052-background-job-execution.html#decision","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Decision","x":"In-process background work runs as a bounded queue plus a single-reader hosted drain. Nothing starts an untracked Task from a request. - A bounded Channel per job kind,…","i":"BoundedChannelFullMode.DropOldest LiveChannelPublishProcessor LiveChannelPublishQueue SessionScoringProcessor sp.GetRequiredService IServiceScopeFactory SessionScoringQueue BackgroundService TryAddSingleton stoppingToken ReadAllAsync SingleReader"},{"u":"/docs/adr/052-background-job-execution.html#rationale","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Rationale","x":"- The host lifetime is the point. A BackgroundService is the only in-process shape the host can cancel and await. Everything else in the decision follows from wanting that. - The…","i":"BackgroundService TryEnqueue"},{"u":"/docs/adr/052-background-job-execution.html#trade-offs","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Trade-offs","x":"- In-process only. The queue does not survive a restart and does not span replicas. Accepted because every current job is either ephemeral (a lost broadcast is a missed UI…","i":"DropOldest Wait"},{"u":"/docs/adr/052-background-job-execution.html#related","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox, for work that must be durable and cross-process, and the post-commit deferral this relies on), ADR-008 (the broker, for cross-service work), ADR-014 (the…"},{"u":"/docs/adr/053-dual-registry-package-publishing.html","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#status","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Amended (2026-07-25) to put the pre-decision Context statements in the past tense, to record the MMCA. ID prefix reservation as then-pending, to scope the…","i":"Directory.Build.props MMCA"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#context","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Context","x":"The fifteen MMCA.Common. packages have shipped to GitHub Packages since the first release. That was the right default while the framework had exactly one consumer group (this…","i":"MMCA.Common.API nuget.config local.props MMCA.Common totalHits package dotnet add"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#decision","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Decision","x":"Every release publishes to both registries, from the same tag, in the same workflow run. - release.yml keeps its existing dotnet nuget push to…","i":"github.repository_owner Directory.Build.props PackageProjectUrl PackageReadmeFile Description MMCA.Common PackageIcon PackageTags permissions release.yml README.md ivanball"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#rationale","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Rationale","x":"- The install line has to be true. Documentation that cannot be followed is worse than no documentation, because the reader concludes the project is broken rather than that the…","i":"MMCA"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#trade-offs","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A published version can never be withdrawn. nuget.org allows unlisting, not deletion. A bad release is now permanent public history, which raises the stakes on the release…","i":"release.yml ivanball"},{"u":"/docs/adr/053-dual-registry-package-publishing.html#related","d":"ADR-053: Dual-Registry Package Publishing (nuget.org plus GitHub Packages)","k":"Architecture Decision Records","t":"Related","x":"ADR-016 (lockstep versioning: every package ships at one version, so both registries receive the same fifteen ids per release), ADR-038 (supply-chain provenance: the SBOM hard…"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#status","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Amended (2026-07-28): Store's reconciliation sweep now derives from PeriodicBackgroundService, so the shared-loop and adoption paragraphs are rewritten and…","i":"PeriodicBackgroundService SafeDomainEventHandler TDomainEvent IUnitOfWork maxReplicas"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#context","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Context","x":"Checkout spans a boundary no transaction covers. CheckOutHandler commits the order insert, the cart transition and the atomic conditional stock decrements in one local…","i":"PaymentInitiated CheckOutHandler"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#decision","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Decision","x":"Multi-step workflows are choreographed sagas: each step raises a domain event, and the follow-up or compensating action lives in its own handler. A periodic reconciliation sweep…","i":"OrderPaymentFailedSagaHandler DbUpdateConcurrencyException PaymentReconciliationService OperationCanceledException OrderCancelledSagaHandler PeriodicBackgroundService Order.InventoryRestored SafeDomainEventHandler MarkInventoryRestored IServiceScopeFactory IDomainEventHandler MarkAsPaymentFailed"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#rationale","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Rationale","x":"- No two-phase commit is available, and none is wanted. Transactions are per data source and best-effort sequential (ADR-006), and an external payment provider cannot enlist in a…","i":"Order.InventoryRestored Order.Status SaveChanges Result catch"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#trade-offs","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Trade-offs","x":"- Inconsistency is bounded, not eliminated. Between the cancellation commit and the compensation commit, stock is held against a cancelled order. Between a dropped webhook and…","i":"PaymentInitiated RestoreInventory InventoryItem maxReplicas"},{"u":"/docs/adr/054-saga-compensation-and-reconciliation.html#related","d":"ADR-054: Choreographed Saga Compensation with a Reconciliation Backstop","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox delivery and retry this leans on for compensation redelivery; this record says what the redelivered handler must do), ADR-006 (which accepts \"no…","i":"RowVersion Result"},{"u":"/docs/adr/055-repository-and-specification-contract.html","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/055-repository-and-specification-contract.html#status","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-25). Revised 2026-08-01 (qualified the \"referenced nowhere\" claim about IEntityReader / IEntityQuerier: an ADC doc comment now names IEntityQuerier, though no…","i":"DependencyInjection.cs DependencyInjection EFReadRepository.cs IEntityQueryService SessionsController EFReadRepository IEntityQuerier IRepository.cs IEntityReader stage.ps1"},{"u":"/docs/adr/055-repository-and-specification-contract.html#context","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Context","x":"Every read an application handler performs has to come from somewhere, and the shape of that contract decides whether the module can still be lifted into its own service later…","i":"TIdentifierType IQueryable DbSet"},{"u":"/docs/adr/055-repository-and-specification-contract.html#decision","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Decision","x":"Data access is repository plus specification: interface-segregated read interfaces for the operations, expression-tree specifications for the predicates, and a build-failing…","i":"CrossSourceSpecification.BuildAsync OrdersByCustomerSpecification PublishedEventSpecification IEntityReader.GetByIdAsync TableNoTrackingSingleQuery TableNoTrackingSplitQuery OwnedByUserSpecification EntityQueryService.cs GetAllForLookupAsync IEntityQueryService InlineSpecification EntityQueryService"},{"u":"/docs/adr/055-repository-and-specification-contract.html#rationale","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A narrow interface is the enforcement, not a style preference. A handler that asks for IEntityReader cannot reach TableNoTracking, because the member is not on the interface.…","i":"GetProjectedAsync TableNoTracking IEntityReader IsSatisfiedBy AllowedFiles GetByIdAsync CountAsync IQueryable Criteria"},{"u":"/docs/adr/055-repository-and-specification-contract.html#trade-offs","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- The ISP split is guidance, not yet a wired dependency. Because the only accessors return the composites (IUnitOfWork.cs:19, :29), depending on IEntityReader today means…","i":"Expression.Invoke ISpecification IEntityReader Criteria"},{"u":"/docs/adr/055-repository-and-specification-contract.html#related","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-007 and ADR-008 (the extraction promise the queryable ban exists to protect), ADR-015 (the fitness-function machinery that runs this rule and its per-repo maps), ADR-014 (the…","i":"SpecificationsDoNotNavigateToOtherEntities TIdentifierType"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#status","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-14: re-anchored the host, base-class and AppHost citations to their current lines; scoped the \"only @rendermode attributes\" enumeration to…","i":"InteractiveServer rendermode MudTable"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#context","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Context","x":"Both web applications are Blazor Web Apps: a static server-rendered (SSR) prerender pass produces the first HTML, then an interactive runtime takes over, either a Blazor Server…","i":"InteractiveAuto App.razor Authorize"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#decision","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Decision","x":"Run one render mode for the entire routable component tree, chosen at the application root, default InteractiveAuto, with prerendering left on and the resulting double fetch…","i":"AddInteractiveWebAssemblyComponents AddInteractiveWebAssemblyRenderMode AddInteractiveServerComponents AddInteractiveServerRenderMode RendererInfo.IsInteractive RenderMode.InteractiveAuto PersistentComponentState PrerenderFetchTimeoutMs InteractiveWebAssembly DataGridListPageBase OnParametersSetAsync RegisterOnPersisting"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#rationale","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Rationale","x":"- InteractiveAuto gets both halves without asking page authors to choose. The first visit gets the Server circuit's immediate interactivity while the WASM bundle downloads in the…","i":"InteractiveServer InteractiveAuto CatalogBrowse Authorize"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#trade-offs","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Trade-offs","x":"- Everything shared has to run in both runtimes. The WASM-compatibility layer rule (MMCA.Common.LayerEnforcement.targets:75-88) forbids the shared UI package from touching…","i":"RendererInfo.IsInteractive AddAdditionalAssemblies DataGridListPageBase MMCA.Common.UI.Web OnAfterRenderAsync InteractiveServer InteractiveAuto CatalogBrowse AddUIShared Program.cs Routes"},{"u":"/docs/adr/056-blazor-render-mode-strategy.html#related","d":"ADR-056: Blazor Render-Mode Strategy for the Web Heads","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (reads the HttpOnly session cookie during the SSR prerender pass this decision keeps enabled), ADR-027 (flows one culture through the SSR to Server to WASM sequence this…","i":"InteractiveAuto"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#status","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-01: the diff now fails closed in both repos (the true is gone and MMCA.Store's build-and-test checkout sets fetch-depth: 0), so the…","i":"true"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#context","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Context","x":"ADR-030 decides who applies a migration: every service host runs DatabaseInitStrategy = Migrate and self-applies its pending EF Core migrations at startup as the sole migrator,…","i":"DatabaseInitStrategy containerapp DropColumn migrations adee5058 revision Migrate dotnet sqlcmd copy"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#decision","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Decision","x":"Schema changes follow expand/contract, and a CI step enforces the contract half. - Expand now, contract later, as a written rule. Adding nullable columns, new tables and new…","i":"OutboxMessages InboxMessages pull_request CreateIndex DropColumn Migrations DropIndex DropTable IsDeleted base_ref release added"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#rationale","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Rollback is one-way for schema, so the check belongs where the drop is still cheap. The only moment a destructive migration can be reconsidered for free is the PR that adds it;…","i":"Down"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#trade-offs","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- Three operations, not a model of compatibility. AlterColumn narrowing a type or flipping a column to NOT NULL, DropForeignKey, DropPrimaryKey, DropSchema, RenameColumn and a…","i":"migrationBuilder.Sql DropForeignKey DropPrimaryKey RenameColumn AlterColumn DropSchema diff main true with git"},{"u":"/docs/adr/057-expand-contract-schema-evolution-gate.html#related","d":"ADR-057: Expand/Contract Schema Evolution Enforced as a CI Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-030 (decides that each service self-applies its migrations at startup, which is precisely why a rolled-back revision meets the new schema; this ADR constrains what those…"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#status","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28; revised 2026-08-14)."},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#context","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Context","x":"ADR-015 turned the architecture invariants into build-gating tests, and drew its own boundary explicitly: the fitness suite asserts \"structure / registration, not runtime…"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#decision","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Decision","x":"Ship the runtime conformance suites in the MMCA.Common.Testing package as abstract behavioral bases that each consuming host subclasses, and run every one of them against a host…","i":"ServiceInfoVersioningContractTestsBase SqlServerIntegrationTestFixtureBase MMCA.Common.Testing.Architecture ProductionHostApplicationFactory DecoratorPipelineOrderTestsBase ProblemDetailsContractTestsBase AssertProblemDetailsShapeAsync GracefulShutdownTestsBase AddApplicationDecorators ChangePreferencesCommand OpenApiContractTestsBase SecurityHeadersTestsBase"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#rationale","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Rationale","x":"- Runtime conformance is the half ADR-015 excluded. Structural rules answer \"is the code shaped correctly\"; these suites answer \"does the composed host behave correctly\". A host…","i":"Development Production"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#trade-offs","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Trade-offs","x":"- Opt-in per host, exactly like ADR-015. The framework ships the suites; a host gets the gate only once someone writes the subclass. That is the same audit-the-inventory caveat,…","i":"CorePublicResources MinimumPathCount status title"},{"u":"/docs/adr/058-runtime-conformance-suites-as-a-package.html#related","d":"ADR-058: Runtime Conformance Suites Shipped as a Package","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (the structural / registration fitness layer this complements; its stated non-goal, \"not runtime behavior\", is exactly this ADR's scope, and the two tiers ship as two…","i":"DecoratorPipelineOrderTestsBase ProblemDetailsContractTestsBase"},{"u":"/docs/adr/059-module-contract-and-composition.html","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records"},{"u":"/docs/adr/059-module-contract-and-composition.html#status","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28; revised 2026-08-14)."},{"u":"/docs/adr/059-module-contract-and-composition.html#context","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Context","x":"The framework's headline claim is that an application is built as a modular monolith and later extracted into services without rewriting business logic. ADR-008 states the…","i":"ModuleLoader"},{"u":"/docs/adr/059-module-contract-and-composition.html#decision","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Decision","x":"Make IModule the single composition contract, discover implementations by reflection, register them in topological dependency order, and represent a disabled module by stub…","i":"DisabledSessionBookmarkValidationService AppDomain.CurrentDomain.GetAssemblies DisabledEventLiveValidationService ModuleControllerFeatureProvider DisabledUserSalesExportService DisabledProductVariantService SalesUserDataExportSection ValidateRemoteDependencies InvalidOperationException Activator.CreateInstance AddUserDataExportSection DisabledCustomerService"},{"u":"/docs/adr/059-module-contract-and-composition.html#rationale","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Rationale","x":"- Reflection discovery keeps hosts out of the module registry business. A host calls one method and gets whatever modules its assembly graph contains; adding a module is a…","i":"RequiresDependencies RemoteDependencies appsettings.json Dependencies Modules true"},{"u":"/docs/adr/059-module-contract-and-composition.html#trade-offs","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Trade-offs","x":"- The AppDomain scan is the fragile default and the one everybody uses. The loader's own documentation warns that the AppDomain scan sees only assemblies already loaded, so a…","i":"ModuleConformanceTestsBase ValidateRemoteDependencies Activator.CreateInstance IBookmarkCountService RegisterDisabledStubs RequiresDependencies AddTypedGrpcClient Dependencies ModuleName Complete Register Enabled"},{"u":"/docs/adr/059-module-contract-and-composition.html#related","d":"ADR-059: The IModule Contract and Reflection-Based Module Composition","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (the extraction topology that consumes this model: \"a service is the monolith with one module enabled\" is a statement about ModuleLoader plus the Disabled stubs, cited…","i":"AddApplicationDecorators ModuleLoader"},{"u":"/docs/adr/060-performance-regression-gate.html","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records"},{"u":"/docs/adr/060-performance-regression-gate.html#status","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-07-28). Revised 2026-08-01 (corrected the count in Trade-offs: the single ratio floor names two of the eight benchmarks, so six, not seven, are gated on…","i":"ci.yml"},{"u":"/docs/adr/060-performance-regression-gate.html#context","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Context","x":"Rubric section 12 asks for hot-path efficiency that is measured, not assumed (Website/docs-src/governance/ArchitectureEvaluationCriteria.md:355). MMCA.Common has a…","i":"IsSatisfiedBy"},{"u":"/docs/adr/060-performance-regression-gate.html#decision","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Decision","x":"Measure the hot-path suite on every code PR and verify the results against a committed baseline that carries two rule kinds: absolute allocation ceilings where the measurement is…","i":"ApplyFilters_ThreeMixedOperators IsSatisfiedBy_RecompileEachCall IsSatisfiedBy_CachedCompile allocationCeilingsBytes MMCA.Common.slnx PackageReference System.Text.Json BenchmarkDotNet MemoryDiagnoser fastBenchmark slowBenchmark Performance"},{"u":"/docs/adr/060-performance-regression-gate.html#rationale","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Rationale","x":"- A ratio is a property of the code; an absolute nanosecond count is a property of the runner. Both benchmarks in a floor run in the same process, on the same machine, in the…","i":"MemoryDiagnoser Specification TEntity TId"},{"u":"/docs/adr/060-performance-regression-gate.html#trade-offs","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The Short job cannot see small latency regressions. Three warmup and three iterations (ci.yml:360) give wide confidence intervals: enough for a 1000x floor and for counting…","i":"ApplyFilters release.yml changes main push"},{"u":"/docs/adr/060-performance-regression-gate.html#related","d":"ADR-060: Performance-Regression Gate (Committed Benchmark Baseline in CI)","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (structural fitness functions, which explicitly stop at structure and registration; this is their runtime-cost counterpart), ADR-038 (the other build-gating control set,…"},{"u":"/docs/adr/061-runtime-secret-management.html","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records"},{"u":"/docs/adr/061-runtime-secret-management.html#status","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01; citations re-anchored 2026-08-14)."},{"u":"/docs/adr/061-runtime-secret-management.html#context","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Context","x":"A Container App can hold a credential two ways: as a literal value in the app's own secrets collection, or as a reference to a Key Vault secret that the platform resolves at…","i":"DefaultAzureCredential secrets"},{"u":"/docs/adr/061-runtime-secret-management.html#decision","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Decision","x":"Every production secret lives in Azure Key Vault and reaches the app as a keyVaultUrl secret reference resolved by a user-assigned managed identity. SQL authentication is staged…","i":"azureADOnlyAuthentication USE_MANAGED_IDENTITY_SQL useManagedIdentitySql hasSmtpPassword MMCA.Templates keyVaultUrl claude.yml Directory hasStripe secretRef existing Identity"},{"u":"/docs/adr/061-runtime-secret-management.html#rationale","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Rationale","x":"- A reference has one home; a literal has as many homes as it has consumers. Three vault secrets in each repo are referenced by more than one app: Redis and the broker by all…"},{"u":"/docs/adr/061-runtime-secret-management.html#trade-offs","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Trade-offs","x":"- One identity means vault-wide read for every app that carries it. A Key Vault Secrets User grant is scoped to the vault, so any app running as the shared identity can read…","i":"main.bicep EXTERNAL listKeys PROVIDER secrets CREATE secure unused FROM USER"},{"u":"/docs/adr/061-runtime-secret-management.html#related","d":"ADR-061: Runtime Secret Management via Key Vault References and Managed Identity","k":"Architecture Decision Records","t":"Related","x":"ADR-037 (directs a consumer to keep the field-encryption key in Key Vault but decides no delivery mechanism, and nothing wires that converter today, so no such secret exists in…"},{"u":"/docs/adr/062-slo-alerting-as-code.html","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records"},{"u":"/docs/adr/062-slo-alerting-as-code.html#status","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01)."},{"u":"/docs/adr/062-slo-alerting-as-code.html#context","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Context","x":"ADR-041 standardized what the fleet emits: RED histograms off the CQRS pipeline, an outbox dead-letter counter, correlation ids, exporters, and the cost knobs that keep ingestion…"},{"u":"/docs/adr/062-slo-alerting-as-code.html#decision","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Decision","x":"Declare each consumer's SLO alerts as data in its Bicep template, materialize them as Log Analytics scheduled query rules, and make the alert-to-runbook pairing a build gate…","i":"EveryRunbookAlertSection_MapsToAProvisionedAlert SloAlertSpecs_AreDiscovered_GateIsNotVacuous ObservabilityConventionTestsBaseTests ObservabilityConventionTestsBase legacySloMetricAlertSpecs infra.OPERATIONS.md metricMeasureColumn alertEmailAddress MinimumAlertSpecs infra.main.bicep ResourceAssembly loadTextContent"},{"u":"/docs/adr/062-slo-alerting-as-code.html#rationale","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Alerts as data, not as portal state. One array is reviewable in a PR, diffable across environments, and re-deployable; the rules, the workbook, and the notification channel are…","i":"enabled false"},{"u":"/docs/adr/062-slo-alerting-as-code.html#trade-offs","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- It is a text gate over IaC, not a check against deployed state. The base matches literal anchors and regexes in the template and headings in markdown. It proves the two files…","i":"sloAlertSpecs metricAlerts prefix key"},{"u":"/docs/adr/062-slo-alerting-as-code.html#related","d":"ADR-062: SLO Alerting as Code with an Alert-to-Runbook Build Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-041 (the telemetry this alerts on top of: it defines emission, instrumentation and cost knobs and stops before thresholds, severities and runbooks), ADR-009 (recovery…"},{"u":"/docs/adr/063-accessibility-conformance-gate.html","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#status","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01). Revised 2026-08-14: refreshed the E2ETestBase helper line anchors (explanatory comments were added above ScanGridAsync), the two consumer suite scan counts…","i":"ScanGridAsync E2ETestBase"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#context","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Context","x":"Accessibility was documented before it was enforced. The narrative guide (common-ACCESSIBILITY.md, rubric section 21) named WCAG 2.1 AA as the target for the shared…","i":"MMCA.Common.UI"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#decision","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Decision","x":"Ship WCAG 2.1 AA as a named, versioned test contract in MMCA.Common.Testing.E2E, assert it from the package's own workflow bases, and wire it as a required merge check and a…","i":"AxeOptions.Wcag21AaExceptMudPagerCombobox AssertNoAccessibilityViolationsAsync AccessibilityViolationException MMCA.Common.Testing.E2E ProfileManagementTests AxeOptions.Wcag21Aa PrimaryContrastText WarningContrastText GalleryAxeTestBase ErrorContrastText AxeRunOptions MudTablePager"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#rationale","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Rationale","x":"- A named constant is the contract. Putting the rule set in a shipped, referenced symbol rather than in each repo's test setup means \"what WCAG 2.1 AA means here\" has exactly one…","i":"ProfileManagementTestsBase UserRegistrationTestsBase UserLoginTestsBase"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#trade-offs","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- Best-practice rules are out of scope, deliberately. Findings axe would classify as best practice (and anything WCAG AAA) are not measured at all, so the gate can be green on a…","i":"Wcag21AaExceptMudPagerCombobox AccessibilityTests ScanGridAsync skipped success deploy"},{"u":"/docs/adr/063-accessibility-conformance-gate.html#related","d":"ADR-063: WCAG 2.1 AA Accessibility as a Shipped Test Contract and CI Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-015 (architecture fitness functions: the structural tier this parallels at the browser tier, and the same invariant-over-discipline posture), ADR-058 (runtime conformance…"},{"u":"/docs/adr/064-deploy-recency-gates.html","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records"},{"u":"/docs/adr/064-deploy-recency-gates.html#status","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-01). Revised 2026-08-07: the MMCA.Helpdesk workflow inventory below was corrected (it also carries release-templates.yml, and its ci.yml runs two jobs, not…","i":"ci.yml"},{"u":"/docs/adr/064-deploy-recency-gates.html#context","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Context","x":"A production rollout in both deployed apps waits on a list of jobs in deploy.needs (MMCA.ADC/.github/workflows/deploy.yml:866, MMCA.Store/.github/workflows/deploy.yml:862). Most…","i":"deploy.needs"},{"u":"/docs/adr/064-deploy-recency-gates.html#decision","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Decision","x":"A production deploy is blocked not only on green tests but on proof of recency for out-of-band verification: three gates assert that a real drill, a real load run and a real…","i":"skip_freshness_gates skip_justification github.event_name workflow_dispatch FRESHNESS_DAYS workflow_runs deploy.needs release.yml foundation updated_at cancelled contents"},{"u":"/docs/adr/064-deploy-recency-gates.html#rationale","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Rationale","x":"- A proof with no expiry date is documentation, not a control. ADR-009 already required the drill to be recorded, and recording it was the honest half of the problem; a record…"},{"u":"/docs/adr/064-deploy-recency-gates.html#trade-offs","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Trade-offs","x":"- An unrelated stale proof blocks an unrelated deploy. A one-line hotfix does not ship when the monthly k6 cron did not fire, and the failure surfaces after merge: the gate job…","i":"deploy"},{"u":"/docs/adr/064-deploy-recency-gates.html#related","d":"ADR-064: Deploy Preconditions as Proof-of-Recency Gates","k":"Architecture Decision Records","t":"Related","x":"ADR-009 (states the recovery objectives and requires that a restore be drilled and recorded; this record decides that a deploy is blocked on how recently that drill, and the…"},{"u":"/docs/adr/065-scaffolding-templates.html","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","x":"Status: Accepted (2026-08-02). Revised 2026-08-07: the staged analyzer delta relaxes three rules rather than one; mmca-module prints seven wire-ups rather than five, and a…"},{"u":"/docs/adr/065-scaffolding-templates.html#context","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Context","x":"Build by hand is accurate and complete, and phases 1 through 6 of it are transcription work (common-BUILD-BY-HAND.md:96 through :1049). Its own instruction for the load-bearing…","i":"AddApplicationDecorators Directory.Packages.props Directory.Build.targets Directory.Build.props launchSettings.json IArchitectureMap MMCA.Templates editorconfig nuget.config global.json install WaitFor"},{"u":"/docs/adr/065-scaffolding-templates.html#decision","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Decision","x":"Ship a dotnet new template pack, MMCA.Templates, containing four templates: The template content is the MMCA.Helpdesk reference application itself, staged at pack time.…","i":"IntegrationEventContractTestsBase IntegrationEventContractTests SQLServerMigrationsAssembly WithSQLServerDataSource TreatWarningsAsErrors AddErrorResources appsettings.json IArchitectureMap Contoso.Support RequesterUserId MMCA.Templates MMCA.Helpdesk"},{"u":"/docs/adr/065-scaffolding-templates.html#rationale","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Rationale","x":"Deriving from the seed rather than maintaining a template tree is the whole design. A hand-maintained copy of a 12-project solution drifts within one release, and drift in a…","i":"MMCA.Common.Templates MMCA.Templates MMCA.Helpdesk sourceName Helpdesk install Tickets dotnet Ticket new"},{"u":"/docs/adr/065-scaffolding-templates.html#trade-offs","d":"ADR-065: Scaffolding templates derived from the reference app","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two documented one-time fixups in every generated app, above (one of them covering all three relaxed rules). The alternative to the SA1210 half of the delta was moving every…","i":"IntegrationEventContractTestsBase MMCA.Common copyOnly dotnet SA1210 using Fact new"},{"u":"/docs/adr/066-broker-transport-selection.html","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records"},{"u":"/docs/adr/066-broker-transport-selection.html#status","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-14 (source citations re-anchored; the ADC AppHost comment that used to say no WithBroker() was wired has been corrected in code, so the…","i":"WithBroker"},{"u":"/docs/adr/066-broker-transport-selection.html#context","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Context","x":"ADR-003 decides that integration events leave an aggregate through the outbox and are published by OutboxProcessor via IMessageBus, and it settles the dispatch question…","i":"OutboxProcessor IMessageBus"},{"u":"/docs/adr/066-broker-transport-selection.html#decision","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Decision","x":"Keep one IMessageBus abstraction with a three-value transport selector, choose the value at the deployment edge (never in application code), configure both broker transports…","i":"Bus.Factory.CreateUsingAzureServiceBus ResolveBrokerConnectionString MessageBus__ConnectionString ConnectionStrings__rabbitmq RootManageSharedAccessKey ConfigureBrokerTransport RetryMaxIntervalSeconds RetryMinIntervalSeconds builder.Configuration UseDelayedRedelivery UsingAzureServiceBus cfg.UseMessageRetry"},{"u":"/docs/adr/066-broker-transport-selection.html#rationale","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Rationale","x":"- The transport is a deployment fact, so it lives at the deployment edge. The only difference between a laptop and production is two environment variables set by the AppHost or…","i":"Listen Send"},{"u":"/docs/adr/066-broker-transport-selection.html#trade-offs","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Trade-offs","x":"- Two brokers means two behaviors to keep aligned. Configuration parity is enforced by one code path, but the products still differ (Service Bus supports delayed redelivery…","i":"MessageBus__Provider ConfigureEndpoints WithBroker Manage rabbit"},{"u":"/docs/adr/066-broker-transport-selection.html#related","d":"ADR-066: Broker Transport Selection and Dev/Prod Parity","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox that feeds IMessageBus; this ADR picks the transport underneath it), ADR-016 (the MassTransit v8 pin the emulator tier must work within, which is why the…","i":"IMessageBus Host"},{"u":"/docs/adr/067-ui-module-shell-composition.html","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records"},{"u":"/docs/adr/067-ui-module-shell-composition.html#status","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07)."},{"u":"/docs/adr/067-ui-module-shell-composition.html#context","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Context","x":"ADR-059 decided how a module plugs into the server: an IModule implementation is discovered by reflection, registered in topological order, and a host composes an application out…","i":"MMCA.Common.UI IModule Routes App"},{"u":"/docs/adr/067-ui-module-shell-composition.html#decision","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Decision","x":"Ship the application shell in the framework package and let each module plug into it by implementing IUIModule, resolved from DI as IEnumerable . - The contract is four members,…","i":"AdditionalAssemblies AppBarComponentTypes LayoutComponentTypes AuthorizeRouteView MapRazorComponents DynamicComponent UIModules.Select RedirectToLogin DeviceUIModule RequiredClaim TitleResource AddSingleton"},{"u":"/docs/adr/067-ui-module-shell-composition.html#rationale","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Rationale","x":"- One composition model across both tiers. A module already declares its server-side surface through IModule (ADR-059); declaring its UI surface through IUIModule means \"add a…","i":"AppBarComponentTypes LayoutComponentTypes AuthorizeView Components IUIModule IModule NavMenu"},{"u":"/docs/adr/067-ui-module-shell-composition.html#trade-offs","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Trade-offs","x":"- Assembly is required even when it carries no route. A host-only module that contributes only a layout component still has to return an assembly, which then joins…","i":"AddAdditionalAssemblies AdditionalAssemblies AuthorizeRouteView RequiredClaim MauiUIModule RequiredRole Program.cs IUIModule Assembly NavItems NavMenu page"},{"u":"/docs/adr/067-ui-module-shell-composition.html#related","d":"ADR-067: Shared Blazor Application Shell and IUIModule Composition","k":"Architecture Decision Records","t":"Related","x":"ADR-059 (the server-side IModule contract this mirrors in the presentation layer), ADR-056 (the render-mode strategy for the web heads, which decides how these components render…","i":"TitleResource IModule"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#status","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07)."},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#context","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Context","x":"A domain model has two kinds of small type: the identity of a thing, and a value the thing carries. ADR-048 recorded the identity half: identifiers stay primitives named through…","i":"decimal string"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#decision","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Decision","x":"Model a domain value that carries an invariant as an immutable record value object with a Result-returning factory; keep identifiers primitive (ADR-048). - One abstract record…","i":"PhoneNumberInvariants.EnsurePhoneNumberIsValid ArchitectureRules.DomainFactoriesReturnResult AddressInvariants.EnsureAddressLine1IsValid EmailInvariants.EnsureEmailIsValid NullablePhoneNumberValueConverter NullableEmailValueConverter EmailInvariants.MaxLength PhoneNumberValueConverter DataContractSerializer GetEqualityComponents DateTimeRange.Create ProductVariant.Price"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#rationale","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Rationale","x":"- The invariant belongs to the type, not to every caller. A string email can be validated in one handler and not the next; an Email cannot exist unvalidated, because the only…","i":"NullReferenceException Currency.None Money.Zero OwnsMoney record Result string Email Money"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#trade-offs","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Trade-offs","x":"- The pattern is not uniformly applied. Only three of the seven types have a companion Invariants class; the rest inline their checks. Only Money has a shipped owned-type helper,…","i":"InvalidOperationException DateTimeRange Currency.All PhoneNumber DateRange Money.Add operator Address OwnsOne Create Result string"},{"u":"/docs/adr/068-value-objects-as-validated-primitives.html#related","d":"ADR-068: Value Objects as Validated Domain Primitives","k":"Architecture Decision Records","t":"Related","x":"ADR-048 (the deliberate opposite call for identifiers: primitives behind aliases, wrapper structs rejected, because identifiers cross process boundaries constantly and carry no…","i":"Create Result"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#status","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Updated 2026-08-14: Store's adoption has landed and is live (its own dedicated storage account, gated on dataProtectionStorageReady), and the ADC call-site…","i":"dataProtectionStorageReady AddServiceDefaults"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#context","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Context","x":"ASP.NET Core's DataProtection default keeps the key ring in memory, per process. That is correct for a single-process host and wrong for a scaled-out one: every replica generates…","i":"DefaultAzureCredential maxReplicas"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#decision","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Decision","x":"Add one opt-in registration call, AddCommonDataProtection, that persists the key ring to a single Azure blob so every replica of a host shares one ring…","i":"Azure.Extensions.AspNetCore.DataProtection.Blobs KeyManagementOptions.XmlRepository System.Security.Cryptography.Xml AddCommonKeyVaultConfiguration DataProtection__BlobStorageUri DataProtection__KeyVaultKeyUri grantDataProtectionStorageRole PersistKeysToAzureBlobStorage ProtectKeysWithAzureKeyVault dataProtectionStorageReady AddCommonDataProtection IDataProtectionProvider"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#rationale","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Rationale","x":"- The key ring is the smallest thing that has to be shared. Sticky sessions would paper over the symptom while making a replica restart a mass sign-out, and a shared cache would…"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#trade-offs","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Trade-offs","x":"- The key ring is not encrypted at rest today. Gate 2 is implemented but configured nowhere, so the ring is protected by the container being private and the account grant being…","i":"AddCommonDataProtection AZURE_CLIENT_ID MMCA.ADC"},{"u":"/docs/adr/069-shared-data-protection-key-ring.html#related","d":"ADR-069: Shared DataProtection Key Ring for Scaled-Out Hosts","k":"Architecture Decision Records","t":"Related","x":"ADR-022 (the browser session cookies whose decryption this makes replica-independent, together with the antiforgery tokens the SSR pages mint), ADR-008 (the multi-host topology…","i":"DefaultAzureCredential"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#status","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-07). Revised 2026-08-14 (source citations re-anchored; the consumer-repo facade claim narrowed to production code, with the controller-test exception recorded)."},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#context","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Context","x":"Every host in the workspace reads a dozen or more configuration sections: connection strings, SMTP, JWT key material, outbox tuning, message-bus provider, module enablement,…","i":"IOptions"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#decision","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Decision","x":"Bind every settings section through a validating chain that runs at startup, and expose a settings type through a read-only interface when it must be read above Infrastructure. -…","i":"IConnectionStringSettings IPushNotificationSettings ConnectionStringSettings PushNotificationSettings LoginProtectionSettings ValidateDataAnnotations DependencyInjection.cs LoginProtectionService CacheKeyPrefixOptions AddPushNotifications EntityControllerBase IApplicationSettings"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#rationale","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A boot failure is cheaper than a first-use failure. A host that will not start is caught by the deployment, by a local dotnet run, or by CI. A host that starts and fails on the…","i":"Microsoft.Extensions.Options ValidateDataAnnotations EntityControllerBase IApplicationSettings ApplicationSettings IValidatableObject RepositoryFactory ValidateOnStart JwtSettings IOptions dotnet init"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#trade-offs","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing enforces it. There is no architecture fitness test asserting that a new AddOptions call carries ValidateDataAnnotations().ValidateOnStart(). The uniformity above is…","i":"ValidateDataAnnotations IValidatableObject IValidateOptions IOptionsMonitor ValidateOnStart JwtSettings AddOptions IOptions Value"},{"u":"/docs/adr/070-fail-fast-configuration-contract.html#related","d":"ADR-070: Fail-Fast Configuration Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-025 (startup warm-up and readiness gating: this contract decides what happens before a host reaches that machinery), ADR-031 (feature flags read from configuration, whose…"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#status","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-12). Amended 2026-08-13: the composition-time string trade-off below was resolved in v1.147.0 by a deferred-resolution overload; see the updated trade-off…"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#context","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Context","x":"ADC's badge check-in feature (ADR-072) needs two things that look like one thing: an attendee's device has to show a QR code, and an organizer's device has to read one. They are…","i":"AddDeviceCapabilityDefaults NSCameraUsageDescription MMCA.Common.UI System.Drawing AddUIShared CAMERA"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#decision","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Decision","x":"Split the feature by what it actually depends on: QR display ships as a shared component, barcode scanning ships as an ADR-042 capability whose native half is opt-in per head. -…","i":"AddDeviceCapabilityDefaults DeviceInfo.Current.Platform MauiBarcodeScannerService NullBarcodeScannerService UseMauiDeviceCapabilities Permissions.RequestAsync ZXing.Net.Maui.Controls IBarcodeScannerService QrErrorCorrectionLevel ScanOnMainThreadAsync TaskCompletionSource MMCA.Common.UI.Maui"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#rationale","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Rationale","x":"- Rendering a QR is not a device concern, so making it one would have been ceremony. As a capability it would have needed an interface, a null fallback and a native override for…","i":"UseMauiDeviceCapabilities MMCA.Common.UI PngByteQRCode IsSupported MauiProgram null try"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#trade-offs","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Trade-offs","x":"- The scan page's strings were resolved at composition, not per call (resolved in v1.147.0). As shipped in v1.145.0, cancelText and cameraDescription were captured into the…","i":"UseCommonBarcodeScanner cameraDescription OnParametersSet MMCA.Common.UI IsSupported QrCodeImage cancelText QRCoder string catch false Func"},{"u":"/docs/adr/071-barcode-scanning-and-qr-display.html#related","d":"ADR-071: Device Capability Pattern for Barcode Scanning and QR Display","k":"Architecture Decision Records","t":"Related","x":"ADR-042 (the capability pattern this extends: contract in MMCA.Common.UI, native implementation in MMCA.Common.UI.Maui, override after AddUIShared), ADR-072 (the ADC feature that…","i":"MMCA.Common.UI.Maui MMCA.Common.UI AddUIShared"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#status","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Amended (2026-08-14): ADC shipped two attendee-self-recorded scan surfaces (sponsor booth visits and room self check-in), a third CheckInScope, a sixth…","i":"CheckInScope"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#context","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Context","x":"ADC wanted two conference-day capabilities that turn out to be one mechanism. Organizers want to know who actually attended which session, which the schedule cannot tell them: a…"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#decision","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Decision","x":"AttendeeBadge (MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.Domain/Badges/AttendeeBadge.cs:17-24) is one row per user holding a single Guid Credential, minted on first…","i":"CheckInInvariants.EnsureTargetMatchesScope CheckInSettings.RoomCheckInGraceMinutes EngagementPermissions.CheckInManage CheckInProcessor.FindExistingAsync EngagementFeatures.SponsorVisits EngagementPointsEntryExportItem PointsActivityType.SponsorVisit EngagementFeatures.RoomCheckIn user_engagement_export.proto EngagementCheckInExportItem Engagement.SponsorVisits leaderboard_display_name"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#rationale","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Rationale","x":"- An opaque credential makes the server the only interpreter. A JWT or HMAC badge would verify offline, but the scanning device is online by necessity (it has to write a check-in…","i":"SessionCheckIn EventCheckIn Regenerate"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#trade-offs","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Trade-offs","x":"- The badge credential is a bearer value. Anyone who photographs an attendee's screen can be checked in as that attendee. The mitigations are that a badge scan is organizer-side,…","i":"DuplicateKeyDetection.IsDuplicateKey SetLeaderboardParticipationHandler GetLeaderboardHandler AttendeeCheckedIn Engagement.Points SessionFeedback SessionCheckIn activity_type IFeatureGated PointsAwarder QuestionAsked FeatureGate"},{"u":"/docs/adr/072-qr-badge-check-in-and-points.html#related","d":"ADR-072: QR Badge Check-In and Points Gamification in ADC","k":"Architecture Decision Records","t":"Related","x":"ADR-071 (the framework halves this consumes: the QR component on /my-badge and the scanner capability behind /check-in), ADR-003 (the outbox path AttendeeCheckedIn and the two…","i":"AttendeeCheckedIn EraseDisplayName"},{"u":"/docs/adr/073-multi-tenancy-model.html","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records"},{"u":"/docs/adr/073-multi-tenancy-model.html#status","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). The implementation lands in the MMCA.Common enterprise capability wave release, alongside the scheduler, audit trail, DSAR export, and CSV export work. It…","i":"ApplicationDbContext AddMultiTenancy configuration"},{"u":"/docs/adr/073-multi-tenancy-model.html#context","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Context","x":"MMCA.Common already partitions data along two axes and neither of them is a tenant. ADR-006 partitions by source name (every entity resolves to a DataSourceKey(Engine, Name),…","i":"ApplySoftDeleteFilters SoftDeleteFilterName modelBuilder.Entity OnModelCreating HasQueryFilter DataSourceKey OnConfiguring TenantId clrType Engine filter Where"},{"u":"/docs/adr/073-multi-tenancy-model.html#decision","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Decision","x":"Ship shared-schema tenancy as a second named query filter, with per-tenant database routing as a configuration override on the same source key, both opt-in and both inert until a…","i":"IPhysicalDbContextFactory.Create CosmosDbContext.OnModelCreating TenantSaveChangesInterceptor UseCommonMiddlewarePipeline TenantResolutionMiddleware CrossTenantWriteException DesignTimeDbContextHelper SoftDeletedUserMiddleware ITenantContext.SetTenant CachingCommandDecorator CorrelationIdMiddleware InitializeDatabaseAsync"},{"u":"/docs/adr/073-multi-tenancy-model.html#rationale","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Rationale","x":"- A query filter is the only place the rule cannot be forgotten. Per-handler Where clauses are correct until the tenth handler, and the tenth handler is a data leak rather than a…","i":"IgnoreQueryFilters DataSourceKey ICacheService RequireTenant Where"},{"u":"/docs/adr/073-multi-tenancy-model.html#trade-offs","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Reads are on discipline where writes are on an invariant. A consumer calling EF's own parameterless IgnoreQueryFilters() on a raw Table surface drops the tenant filter along…","i":"ApplicationLayer_DoesNotUseRawQueryableSurfaces DefaultSqlServerDbContextFactory TenantSaveChangesInterceptor IgnoreQueryFilters ICacheService ITenantEntity tenant_id Table"},{"u":"/docs/adr/073-multi-tenancy-model.html#related","d":"ADR-073: Multi-Tenancy (Shared-Schema Query Filter plus DB-per-Tenant Routing)","k":"Architecture Decision Records","t":"Related","x":"ADR-006 (the source-name axis this composes with: an override re-points a DataSourceKey without changing it, and the per-source outbox this record drains once per tenant),…","i":"IgnoreQueryFilters CosmosDbContext TenancySettings DataSourceKey tenantId TenantId string"},{"u":"/docs/adr/074-recurring-job-scheduler.html","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records"},{"u":"/docs/adr/074-recurring-job-scheduler.html#status","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13; revised 2026-08-14). The implementation lands in the MMCA.Common \"enterprise capability wave\" release and is opt-in: a host calls…","i":"AddScheduledJobs configuration"},{"u":"/docs/adr/074-recurring-job-scheduler.html#context","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Context","x":"The framework had two kinds of background work and neither of them is a schedule. OutboxProcessor…","i":"PeriodicBackgroundService OutboxProcessor"},{"u":"/docs/adr/074-recurring-job-scheduler.html#decision","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Decision","x":"A persistent job store plus a single-runner claim lease, reusing the exact idiom the outbox proved. The outbox claims a batch with an ExecuteUpdateAsync that sets LockedUntil and…","i":"DesignTimeDbContextOptions.EnableScheduler DesignTimeDbContextHelper PeriodicBackgroundService Directory.Packages.props EnsurePermissionRegistry ValidateDataAnnotations PollingIntervalSeconds AuditTrailCleanupJob DispatchLagHistogram IServiceScopeFactory ClaimEligibleAsync ConfigureScheduler"},{"u":"/docs/adr/074-recurring-job-scheduler.html#rationale","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Rationale","x":"- The lease is already proven under production load. Multi-replica correctness for recurring work is the hard part, and it was solved once for the outbox: an atomic claim update,…","i":"AddScheduledJobs IUnitOfWork LastRunOn NextRunOn DateTime"},{"u":"/docs/adr/074-recurring-job-scheduler.html#trade-offs","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A polling loop is not a real-time scheduler. Worst-case start lag is one polling interval, 30 seconds at the default, so sub-minute precision is not on offer. A job that must…","i":"LeaseSeconds"},{"u":"/docs/adr/074-recurring-job-scheduler.html#related","d":"ADR-074: Recurring Job Scheduler (Persistent Cron Jobs on the Outbox Claim-Lease Pattern)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox whose claim-lease idiom and smart wait this reuses verbatim, and whose at-least-once posture it inherits along with the idempotency obligation on job bodies),…","i":"SchedulerSettings SchedulerMetrics OutboxMetrics"},{"u":"/docs/adr/075-audit-trail.html","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records"},{"u":"/docs/adr/075-audit-trail.html#status","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13; corrected 2026-08-14: the adoption sweep and the ApplicationDbContext line citations). The implementation lands in the MMCA.Common \"enterprise capability…","i":"ApplicationDbContext IAuditedEntity AddAuditTrail configuration"},{"u":"/docs/adr/075-audit-trail.html#context","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Context","x":"The framework already answers \"who touched this row last\". Every AuditableBaseEntity carries CreatedOn/By and LastModifiedOn/By, stamped by AuditSaveChangesInterceptor on the way…","i":"AuditSaveChangesInterceptor AuditableBaseEntity SaveChangesAsync LastModifiedBy"},{"u":"/docs/adr/075-audit-trail.html#decision","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Decision","x":"AuditTrailSaveChangesInterceptor (Infrastructure Persistence/AuditTrail/) joins the interceptors ApplicationDbContext.OnConfiguring already passes to…","i":"ApplicationDbContext.OnModelCreating ApplicationDbContext.OnConfiguring DomainEventSaveChangesInterceptor AuditTrailSaveChangesInterceptor optionsBuilder.AddInterceptors TenantSaveChangesInterceptor AuditSaveChangesInterceptor DesignTimeDbContextHelper PeriodicBackgroundService PiiRedactor.RedactedToken DiscardAbandonedCapture DependencyInjection.cs"},{"u":"/docs/adr/075-audit-trail.html#rationale","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Rationale","x":"- IAuditableEntity is a statement about a business row, and an audit row is not one. The interface means \"this row stamps who created and last modified it and participates in…","i":"IAuditableEntity LastModifiedBy IScheduledJob OutboxMessage TenantId Pii"},{"u":"/docs/adr/075-audit-trail.html#trade-offs","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Write amplification is real and it is on the caller's latency path. An entity with twenty changed properties writes twenty rows inside the caller's transaction, so an audited…","i":"IAuditTrailReader AddAuditTrail RetentionDays Pii"},{"u":"/docs/adr/075-audit-trail.html#related","d":"ADR-075: Audit Trail (Same-Transaction Field-Level Change History)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the same-transaction write this copies wholesale, including the retry-discard and the Add-only mutation rule), ADR-005 (soft-delete, [Pii] and erasure: why the trail…","i":"AuditTrailSettings RowVersion TenantId Add Pii"},{"u":"/docs/adr/076-data-subject-export.html","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/076-data-subject-export.html#status","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Revised 2026-08-14 (the API-surface section corrected to the shipped mechanism, an abstract DataExportControllerBase a subclass mounts, not an…","i":"ExportUserDataHandlerBase DataExportControllerBase IUserDataExportSection"},{"u":"/docs/adr/076-data-subject-export.html#context","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Context","x":"A data-subject access request is a legal obligation with a clock on it: the person asks for a copy of the personal data held about them, and the operator has a deadline to hand…","i":"DeleteUserHandlerBase UserOwnershipRule IAnonymizable"},{"u":"/docs/adr/076-data-subject-export.html#decision","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Decision","x":"The framework takes the part that is the same in both apps; the app keeps the part that is not. A consumer's export handler becomes a subclass that supplies a role test and a set…","i":"AuthorizationPolicies.RequireAuthenticated EntitiesWithPiiImplementAnonymizable UserOwnershipRule.CheckOwnership AuditableAggregateRootEntity IUserEngagementExportService AddNotificationControllers PrivacyFeatures.DataExport AuthenticationServiceBase ExportUserDataHandlerBase DataExportControllerBase PiiEntitiesAreExportable IUserDataExportSection"},{"u":"/docs/adr/076-data-subject-export.html#rationale","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Rationale","x":"- The two halves of a handler have different owners. The ownership gate, the aggregate load, the fan-out, the per-section catch and the envelope are the same decisions in both…","i":"IUserEngagementExportService IUserSalesExportService ExportUserDataQuery UserOwnershipRule User"},{"u":"/docs/adr/076-data-subject-export.html#trade-offs","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Best-effort degradation can return a quietly incomplete package. Available = false is the only signal, and nothing forces a caller, a UI, or the subject to read it. A section…","i":"DataExportControllerBase UserDataExportDTO UserOwnershipRule CurrentUserId FeatureGate Available false"},{"u":"/docs/adr/076-data-subject-export.html#related","d":"ADR-076: Data-Subject Export (DSAR) as a Framework Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (the erasure half of the same privacy obligation, whose IAnonymizable opt-in and [Pii] guard are this contract's mirror: one erases what the other copies), ADR-033 (the…","i":"PiiEntitiesAreExportable UserOwnershipRule IAnonymizable FeatureGate Result Pii"},{"u":"/docs/adr/077-hybridcache-substrate.html","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","i":"ICacheService"},{"u":"/docs/adr/077-hybridcache-substrate.html#status","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). Amends ADR-026: Tier 1's substrate gains a third implementation beside MemoryCacheService and DistributedCacheService. It is opt-in through…","i":"DistributedCacheService AddCommonHybridCache MemoryCacheService"},{"u":"/docs/adr/077-hybridcache-substrate.html#context","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Context","x":"ADR-026 settled Tier 1 as one abstraction (ICacheService) over two implementations chosen at startup: in-process memory when no real IDistributedCache is present, Redis…","i":"Microsoft.Extensions.Caching.Hybrid ICacheService.IncrementAsync StackExchangeRedisCache IDistributedCache ICacheService HybridCache WRONGTYPE Result INCR"},{"u":"/docs/adr/077-hybridcache-substrate.html#decision","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Decision","x":"Ship HybridCacheService as a third ICacheService implementation, opt-in per host, under a disjoint keyspace. This is the structural rule the design is built around, and…","i":"HybridCacheEntryFlags.DisableUnderlyingData Microsoft.Extensions.Caching.Hybrid CacheOptions.DefaultDuration HybridCache.GetOrCreateAsync HybridCache.RemoveByTagAsync MMCA.Common.Infrastructure Directory.Packages.props DistributedCacheService DisableLocalCacheWrite CachingQueryDecorator DisableLocalCacheRead AddCommonHybridCache"},{"u":"/docs/adr/077-hybridcache-substrate.html#rationale","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Rationale","x":"- The disjoint keyspace is the decision; everything else is implementation. Rather than trusting a second implementation to write a shape compatible with the first, this record…","i":"DisableUnderlyingData LocalCacheExpiration IncrementAsync GetAsync"},{"u":"/docs/adr/077-hybridcache-substrate.html#trade-offs","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Trade-offs","x":"- Invalidation does not reach other replicas' L1 immediately. A remove evicts the L2 entry and the calling replica's L1; every other replica keeps its copy for up to…","i":"AddCommonHybridCache LocalCacheExpiration GetOrCreateAsync IncrementAsync ICacheService RemoveAll"},{"u":"/docs/adr/077-hybridcache-substrate.html#related","d":"ADR-077: HybridCache as an Opt-In ICacheService Substrate (Amends ADR-026)","k":"Architecture Decision Records","t":"Related","x":"ADR-026 (amended by this record: its Tier 1 substrate gains a third implementation, its 30-second default TTL becomes the local-cache bound as well, its prefix-invalidation model…","i":"IncrementAsync GetAsync"},{"u":"/docs/adr/078-csv-export-endpoint.html","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records"},{"u":"/docs/adr/078-csv-export-endpoint.html#status","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-13). The implementation lands in the MMCA.Common \"enterprise capability wave\" release. Unlike the wave's other features this one is NOT opt-in: every controller…","i":"EntityControllerBase"},{"u":"/docs/adr/078-csv-export-endpoint.html#context","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Context","x":"The request is \"export what you filtered\". The generic entity surface of ADR-034 already accepts a full query vocabulary on the paged route…","i":"EntityQueryPipeline.MaxUnboundedResultLimit context.CacheVaryByRules.QueryKeys options.ReturnHttpNotAcceptable PublicEndpointOutputCachePolicy ReturnHttpNotAcceptable QueryFilterModelBinder IAsyncEnumerable OutputFormatter sortDirection sortColumn Accept AddAPI"},{"u":"/docs/adr/078-csv-export-endpoint.html#decision","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Decision","x":"EntityControllerBase gains a virtual [HttpGet(\"export\")] ExportAsync(...) action (Source/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs). It accepts the same…","i":"QueryFieldService.ShapeCollectionData IPhysicalDbContextFactory.Create ApplicationSettings.MaxPageSize IEntityQueryService.GetAllAsync UnhandledResultFailureFilter JsonNamingPolicy.CamelCase OpenApiContractTestsBase MaxUnboundedResultLimit QueryFilterModelBinder IEntityControllerBase EntityControllerBase ApplicationSettings"},{"u":"/docs/adr/078-csv-export-endpoint.html#rationale","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Rationale","x":"- A route is an unambiguous request; an Accept header is a preference. Given a cache policy that ignores Accept and a pipeline configured to never return 406, a client that…","i":"OutputFormatter Accept"},{"u":"/docs/adr/078-csv-export-endpoint.html#trade-offs","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every derivative gains a bulk read whether its owner wanted one or not. The only gate is the controller's existing authorization posture. A resource that was safe to page 20…","i":"GetExportSpecification MaxExportRows ExportAsync MaxPageSize Accept Skip Take"},{"u":"/docs/adr/078-csv-export-endpoint.html#related","d":"ADR-078: CSV Export as a Dedicated Endpoint, Not Content Negotiation","k":"Architecture Decision Records","t":"Related","x":"ADR-034 (the generic entity surface and query contract this extends, and the MaxUnboundedResultLimit ceiling that forced the page loop), ADR-040 (the output-cache policy whose…","i":"MaxUnboundedResultLimit Accept Result"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#status","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#context","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Context","x":"In ASP.NET Core, middleware order is behavior, not style: a rate limiter placed before authentication partitions every request as anonymous, an HTTPS redirect placed in front of…","i":"TenantResolutionMiddleware SoftDeletedUserMiddleware UseAuthentication HttpContext.User"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#decision","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Decision","x":"Ship the edge as one ordered pipeline in the framework, UseCommonMiddlewarePipeline (MMCA.Common/Source/Presentation/MMCA.Common.API/Startup/WebApplicationExtensions.cs:45), and…","i":"UseCommonRequestLocalization UseCommonMiddlewarePipeline TenantResolutionMiddleware SoftDeletedUserMiddleware MapOidcDiscoveryEndpoint app.UseAuthentication UseForwardedHeaders app.UseRateLimiter HttpContext.User UseAuthorization KnownIPNetworks MapJwksEndpoint"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#rationale","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Rationale","x":"- Order is behavior, so it belongs to the framework, not to each host. Four of the adjacencies above fail silently when reversed: the limiter stops limiting, the tenant resolver…","i":"Program.cs"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#trade-offs","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing freezes the order. A workspace-wide search finds no test referencing UseCommonMiddlewarePipeline: the only non-host references are the method itself, a cross-reference…","i":"UseCommonMiddlewarePipeline MapOidcDiscoveryEndpoint UseCommonSecurityHeaders HttpContext.Items KnownIPNetworks MapControllers KnownProxies PreForwarded jwks_uri"},{"u":"/docs/adr/079-shared-http-middleware-pipeline.html#related","d":"ADR-079: One Shared, Ordered HTTP Middleware Pipeline for Every Service Host","k":"Architecture Decision Records","t":"Related","x":"ADR-014 (the in-process sibling: one fixed decorator order for commands and queries), ADR-019 (depends on forwarded headers before the limiter and on the limiter after…"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#status","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#context","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Context","x":"Both production apps deploy to Azure Container Apps from a single deploy.yml job on push to main, and every gate runs before anything rolls out: the deploy job waits on…","i":"deploy.yml foundation deploy main"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#decision","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Decision","x":"Roll out one revision at a time, verify it from outside, and auto-revert the image only when the verification fails. - Single-revision rollout. Every container app runs…","i":"activeRevisionsMode rollback_failed containerapp createdTime Provisioned pipefail revision rollback failure sqlcmd probe Smoke"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#rationale","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Rationale","x":"- ARM success is the wrong success signal. The smoke gate converts \"the control plane accepted the template\" into \"the fleet answers requests\", which is the only claim a deploy…","i":"deploy"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#trade-offs","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Trade-offs","x":"- Schema is never rolled back, so a bad migration is fix-forward only. The image reverts and the database does not, so the previous release resumes against the new schema. This…","i":"rollback_failed Provisioned revision APPS copy"},{"u":"/docs/adr/080-deploy-rollout-revision-rollback.html#related","d":"ADR-080: Production Rollout with Automatic Revision-Only Rollback","k":"Architecture Decision Records","t":"Related","x":"ADR-057 (built on this model: revision-only rollback is why every migration must be backward compatible one release back), ADR-030 (startup migration as sole migrator, the reason…"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#status","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#context","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Context","x":"Both deployed apps run a deliberately small production footprint: every Container App is declared with maxReplicas: 2 and every SQL database with the Basic tier…","i":"maxReplicas Basic"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#decision","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Decision","x":"The cost baseline is asserted by a read-only reusable workflow that both runs weekly and sits in deploy.needs, so an un-reverted scale-up blocks the next production deploy. - One…","i":"properties.template.scale.maxReplicas BASELINE_MAX_REPLICAS AZURE_RESOURCE_GROUP github.event_name workflow_dispatch workflow_call deploy.needs environment release.yml main.bicep production MMCAStore"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#rationale","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Rationale","x":"- Configuration drift is the leading indicator; spend is the lagging one. The budget notification fires at 80% of actual spend, after the money is gone, and names a number rather…","i":"workflow_call deploy.needs maxReplicas deploy.yml sku.tier"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#trade-offs","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- A legitimate scale-up blocks deploys until the baseline is edited. Standing up extra capacity for a real event and then shipping a fix during it requires a pull request against…","i":"BASELINE_MAX_REPLICAS skip_freshness_gates skip_justification workflow_dispatch deploy.needs maxReplicas deploy.yml sku.tier Standard deploy Basic write"},{"u":"/docs/adr/081-cost-baseline-deploy-gate.html#related","d":"ADR-081: The Cost Baseline as a Hard Deploy Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-064 (the sibling deploy-precondition record, which decides the three proof-of-recency gates and enumerates this one only in passing; its break-glass input does not apply…","i":"deploy.needs"},{"u":"/docs/adr/082-two-tier-cors-posture.html","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records"},{"u":"/docs/adr/082-two-tier-cors-posture.html#status","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/082-two-tier-cors-posture.html#context","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Context","x":"Both deployed applications put a YARP gateway in front of per-module service hosts (ADR-008), and the browser and MAUI clients talk to the gateway origin while the services…"},{"u":"/docs/adr/082-two-tier-cors-posture.html#decision","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Decision","x":"Ship two cross-origin policies from the framework: an allow-listed one for service hosts and a deliberately broader one for gateways. - Service hosts register two named policies…","i":"CorsPolicyAllowSpecificOrigins app.Environment.IsDevelopment UseCommonMiddlewarePipeline Cors__AllowedOrigins__0 _allowSpecificOrigins AddCommonGatewayCors CorsPolicyAllowAll UseAuthentication AddDefaultPolicy AllowCredentials IHostEnvironment AllowAnyHeader"},{"u":"/docs/adr/082-two-tier-cors-posture.html#rationale","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Rationale","x":"- A proxy cannot allow-list what it does not own. The gateway has no controllers and no knowledge of which headers the fronted services accept, so a header allow-list there would…","i":"UseCommonMiddlewarePipeline AllowCredentials IHostEnvironment AllowAnyOrigin AddCommonCors UseCors"},{"u":"/docs/adr/082-two-tier-cors-posture.html#trade-offs","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Trade-offs","x":"- The gateway policy is broad on two of three axes. Any header and any method are accepted for an allow-listed origin. The origin list is the only lever there, so a mistake in…","i":"ProductionHostApplicationFactory IHostEnvironment.IsDevelopment configuration.GetSection ValidateOnStart UseEnvironment AddCommonCors UseCors string Get"},{"u":"/docs/adr/082-two-tier-cors-posture.html#related","d":"ADR-082: Two-Tier Cross-Origin Posture: Allow-Listed Service Policies, an Any-Header Gateway Policy","k":"Architecture Decision Records","t":"Related","x":"ADR-079 (the shared middleware pipeline whose fixed order places the environment-selected CORS policy between routing and authentication), ADR-008 (the gateway plus per-module…"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#status","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#context","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Context","x":"ADR-003 decides how a domain event moves: captured into the outbox inside SaveChangesAsync, dispatched in-process after commit, or published to the broker when it is an…","i":"SaveChangesAsync SessionChanged SessionCreated SessionDeleted Changed Created Deleted Session Entity"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#decision","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Decision","x":"Every generic CRUD lifecycle transition of an entity raises one event type for that entity, carrying a DomainEntityState discriminator; handlers filter on State. - One base…","i":"ProductVariantPriceChanged TicketChangedAuditHandler ProductVariantSkuChanged ShoppingCartItemChanged SessionQuestionChanged ShoppingCartCheckedOut ProductVariantRemoved SessionCreatedHandler BaseIntegrationEvent ProductVariantAdded EntityChangedEvent DomainEntityState"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#rationale","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Rationale","x":"- One type per entity is one subscription surface. A subscriber declares interest in the entity, then decides which transitions matter, instead of the container deciding for it…","i":"SessionChanged SessionCreated SessionDeleted OrderPaid"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#trade-offs","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Trade-offs","x":"- Every selective handler pays a filter. A handler that cares about one transition has to open with a State guard and return (SessionCreatedHandler.cs:17-18 is the shape to…","i":"EntityChangedEvent PointsEntryChanged BaseDomainEvent LivePollChanged LivePollStatus Unchanged Added State TId"},{"u":"/docs/adr/083-crud-lifecycle-event-taxonomy.html#related","d":"ADR-083: One CRUD Lifecycle Event per Entity with a State Discriminator","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (how these events are captured and dispatched; this ADR decides only their shape), ADR-010 (schema versioning for the discriminator once it crosses a service boundary),…","i":"MessageId"},{"u":"/docs/adr/084-stripe-webhook-ingress.html","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records"},{"u":"/docs/adr/084-stripe-webhook-ingress.html#status","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-14)."},{"u":"/docs/adr/084-stripe-webhook-ingress.html#context","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Context","x":"Four ADRs already cover how a message crosses a boundary in this workspace. ADR-003 decides how an event leaves a service (outbox, at-least-once). ADR-021 decides how a…","i":"Warning"},{"u":"/docs/adr/084-stripe-webhook-ingress.html#decision","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Decision","x":"Treat third-party webhook ingress as its own contract with two halves: an acceptance-coded endpoint and a self-registering, self-provisioning endpoint registration at startup. -…","i":"StripeWebhookRegistrationService EventUtility.ValidateSignature payment_intent.payment_failed AddModuleSalesInfrastructure SignatureVerificationFailed StripeWebhookSecretProvider checkout.session.completed throwOnApiVersionMismatch checkout.session.expired HttpContext.Request.Body EventUtility.ParseEvent Stripe__WebhookBaseUrl"},{"u":"/docs/adr/084-stripe-webhook-ingress.html#rationale","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Rationale","x":"- The caller's protocol decides the response vocabulary. Stripe reads a status code as \"keep retrying\" or \"stop\", not as \"this succeeded\" or \"this failed\". Mapping every…","i":"Critical Warning"},{"u":"/docs/adr/084-stripe-webhook-ingress.html#trade-offs","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Trade-offs","x":"- A startup service that writes to a live third-party account. Booting a Sales replica creates and deletes webhook endpoints in the real Stripe account…","i":"StripeWebhookRegistrationService PaymentReconciliationService PaymentsController WebhookBaseUrl SecretKey Critical Warning"},{"u":"/docs/adr/084-stripe-webhook-ingress.html#related","d":"ADR-084: Stripe Webhook Ingress (Acceptance-Coded Responses and Startup Self-Registration)","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (outbound at-least-once delivery, the other end of the same family), ADR-021 (broker-side inbound dedup, which never sees a webhook), ADR-017 (client-supplied idempotency…"},{"u":"/docs/onboarding/index.html","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","x":"A teaching guide for an experienced .NET engineer who is new to this codebase. It walks every first-party type, explaining not just what each type is but how it works and why it…","i":"CLAUDE.md dotnet new"},{"u":"/docs/onboarding/index.html#how-the-guide-is-organized-two-axes","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"How the guide is organized, two axes","x":"The guide has two organizing axes that work together. 1. Primary axis, functional grouping. Every type lives in exactly one functional group: the capability or cross-cutting…","i":"SelfHttpWarmupTask GateTestContext MMCA.Common MMCA.ADC Priority"},{"u":"/docs/onboarding/index.html#chapters","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Chapters","x":"---","i":"AuthenticationServiceBase HttpResilienceDefaults AuthenticationService ConferencePermissions ApplicationDbContext IdentityPermissions SQLServerDbContext HealthCheckTags OutboxFinalizer HasPermission ThemeService Contracts"},{"u":"/docs/onboarding/index.html#legend-how-to-read-a-type-section","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Legend, how to read a type section","x":"Every type gets one section using this template: {TypeName} {Assembly} · {namespace} · {file:line} · Level {n} · {kind} - What it is: one or two plain-language sentences. -…","i":"namespace Result Rubric Name"},{"u":"/docs/onboarding/index.html#suggested-reading-paths","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"Suggested reading paths","x":"- Framework-first (recommended). Primer → group-01 → upward. You meet the MMCA.Common foundations before the MMCA.ADC features that build on them; this matches dependency order…","i":"MMCA.Common MMCA.ADC Rubric"},{"u":"/docs/onboarding/index.html#the-companion-projects-context","d":"MMCA.Common + MMCA.ADC, Onboarding Guide","k":"Onboarding Guide","t":"The companion projects (context)","x":"This guide covers MMCA.Common (the framework) and MMCA.ADC (one consumer). MMCA.Store is out of scope. The dependency arrow is why the Common framework groups (1–16) come before…","i":"MMCA.Store"},{"u":"/docs/onboarding/00-dependency-manifest.html","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","x":"Each distinct type node is assigned a Level by longest-path layering over its first-party dependencies (base/interface, generic constraints, field/property/param/return types,…","i":"System.Guid global static using Using int"},{"u":"/docs/onboarding/00-dependency-manifest.html#manifest-by-level-then-assembly","d":"Phase 1: Dependency Manifest & Leveling","k":"Onboarding Guide","t":"Manifest (by level, then assembly)","i":"DefaultEntityConfigurationAssemblyProviderTests GetPublicSessionCategoryItemFilterHandlerTests GetPublicSpeakerCategoryItemFilterHandlerTests AddSessionQuestionAnswerCommandValidatorTests ConferenceCategoryCreateRequestValidatorTests ConferenceCategoryUpdateRequestValidatorTests UserNotificationExportServiceGrpcAdapterTests MarkAllNotificationsReadHandlerTrackingTests SignalRPushNotificationSenderAdditionalTests AddEventQuestionAnswerCommandValidatorTests AddSessionCategoryItemCommandValidatorTests AddSpeakerCategoryItemCommandValidatorTests"},{"u":"/docs/onboarding/00-group-taxonomy.html","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","x":"This is the primary axis of the guide. Every one of the 3,264 distinct first-party type nodes from 00-inventory.md is assigned to exactly one functional group - its primary home:…","i":"MMCA.Common MMCA.ADC Result"},{"u":"/docs/onboarding/00-group-taxonomy.html#design-notes-boundary-decisions-worth-stating-up-front","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"Design notes (boundary decisions worth stating up front)","x":"- Cycles are kept whole. The 13 dependency cycles (SCCs) from the manifest are never split across groups. Notably the ApplicationDbContext AuditSaveChangesInterceptor…","i":"DomainEventSaveChangesInterceptor DataSourceModelCacheKeyFactory AuditSaveChangesInterceptor MMCA.ADC.Notification ApplicationDbContext MMCA.Common.Testing IAnonymizable PiiAttribute Gallery Rubric Fact S30"},{"u":"/docs/onboarding/00-group-taxonomy.html#the-groups-ordered","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"The groups (ordered)","x":"Reconciliation: 1636 production types across 26 groups + 1628 test/testing types in G25 = 3264 (matches the inventory's distinct-node count). No type appears twice; none dropped.…"},{"u":"/docs/onboarding/00-group-taxonomy.html#group-membership","d":"Phase 1b - Functional Group Taxonomy","k":"Onboarding Guide","t":"Group membership","x":"group-01-result-error-handling.md 11 types The Result/Error railway that every operation returns instead of throwing; pagination result shapes. group-02-domain-building-blocks.md…","i":"MMCA.ADC.ServiceBusEmulator.IntegrationTests SessionBookmarkValidationServiceGrpcAdapter DefaultEntityConfigurationAssemblyProvider GetPublicSessionCategoryItemFilterHandler GetPublicSpeakerCategoryItemFilterHandler SessionQuestionPendingCountChangedPayload AddSessionQuestionAnswerCommandValidator ConferenceCategoryCreateRequestValidator ConferenceCategoryUpdateRequestValidator CookieSessionRefreshMiddlewareExtensions DisabledSessionBookmarkValidationService MMCA.ADC.Conference.Infrastructure.Tests"},{"u":"/docs/onboarding/00-inventory.html","d":"Phase 0: Type Inventory","k":"Onboarding Guide","x":"Generated mechanically by a Roslyn syntactic parse of every in-scope .cs file under MMCA.Common/Source, MMCA.Common/Tests, MMCA.ADC/Source, MMCA.ADC/Tests. - Files scanned: 2699…","i":"extension"},{"u":"/docs/onboarding/00-inventory.html#full-inventory","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Full inventory","i":"MMCA.ADC.Conference.Application.Events.Sessionize MMCA.ADC.Conference.Application.Events.Validation MMCA.ADC.Conference.Application.Tests.Events.DTOs MMCA.ADC.Conference.Domain.Questions.DomainEvents MMCA.ADC.Conference.Infrastructure.Tests.Services MMCA.ADC.Conference.IntegrationTests.CrossService MMCA.ADC.Engagement.Application.CheckIns.Services MMCA.ADC.Engagement.Domain.LivePolls.DomainEvents MMCA.ADC.Engagement.Domain.Tests.SessionQuestions MMCA.ADC.Identity.IntegrationTests.Infrastructure MMCA.Common.Application.Interfaces.Infrastructure MMCA.Common.Application.Users.UseCases.DeleteUser"},{"u":"/docs/onboarding/00-inventory.html#extensiont-blocks","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"extension(T) blocks","i":"IDistributedApplicationBuilder IBusRegistrationConfigurator AuthenticationBuilder IEndpointRouteBuilder WebApplicationBuilder IApplicationBuilder ICurrentUserService IReadOnlyCollection currentUserService IServiceCollection OutputCacheOptions IResourceBuilder"},{"u":"/docs/onboarding/00-inventory.html#generated--excluded-artifacts-no-type-sections-written","d":"Phase 0: Type Inventory","k":"Onboarding Guide","t":"Generated / excluded artifacts (no type sections written)","x":"118 files excluded as generated (EF migrations, snapshots, .g.cs, AssemblyInfo)."},{"u":"/docs/onboarding/00-primer.html","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","x":"This chapter teaches the cross-cutting things once, so the per-type chapters can stay focused. Read it before the group chapters (start with group-01). Everything here is either…"},{"u":"/docs/onboarding/00-primer.html#1-the-big-picture","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"1. The big picture","x":"Two codebases are in scope: - MMCA.Common: a framework, published as fifteen NuGet packages to nuget.org (the documented install path) and mirrored to GitHub Packages (ADR-053)…","i":"Testing.Architecture Aspire.Hosting Infrastructure Application MMCA.Common Testing.E2E references Testing.UI MMCA.ADC Testing UI.Maui Aspire"},{"u":"/docs/onboarding/00-primer.html#2-architectural-styles-this-codebase-commits-to","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"2. Architectural styles this codebase commits to","x":"These are the recurring ideas. Each is taught fully at its first concrete appearance in a group chapter; here is the orientation so the vocabulary is familiar. - Domain-Driven…","i":"EntityTypeConfigurationSQLServer PublicEndpointOutputCachePolicy JwtForwardingClientInterceptor JwtSettings.SigningAlgorithm EntityTypeConfigurationBase UseCommonMiddlewarePipeline TenantResolutionMiddleware ExportUserDataHandlerBase ISoftDeletedUserValidator ServiceInfoControllerBase SoftDeletedUserMiddleware AddApplicationDecorators"},{"u":"/docs/onboarding/00-primer.html#3-the-external-stack-bcl--nuget-external-level-0","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"3. The external stack (BCL / NuGet, \"external Level 0\")","x":"These are not first-party and get no per-type sections. Versions are from MMCA.Common/Directory.Packages.props and MMCA.ADC/Directory.Packages.props (Central Package Management,…","i":"Microsoft.Extensions.ServiceDiscovery.Yarp Microsoft.Extensions.Http.Resilience Notification.PushNotifications IEntityTypeConfiguration MMCA.Common.UI global.json IMessageBus SaveChanges TryDecorate DbContext OrderBy vX.Y.Z"},{"u":"/docs/onboarding/00-primer.html#4-c-build-and-code-style-conventions","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"4. C#, build, and code-style conventions","x":"- .NET 10.0, LangVersion: preview: required because the codebase uses C extension types (extension(T) syntax, see below). - Central Package Management (CPM). All NuGet versions…","i":"csharp_style_namespace_declarations MMCA.Common.Testing.Architecture ManagePackageVersionsCentrally Directory.Packages.props DependencyInjection.cs DependencyVersionTests TreatWarningsAsErrors csharp_prefer_braces EntityTypeExtensions packageSourceMapping IServiceCollection IArchitectureMap"},{"u":"/docs/onboarding/00-primer.html#5-the-solution--test-layout","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"5. The solution / test layout","x":"- .slnx: the human solution (XML format). .slnf, a solution filter used in CI to build a subset fast (MMCA.Store.CI.slnf, MMCA.ADC.CI.slnf). - Microsoft Testing Platform, not…","i":"MMCA.Common.UI.E2E.Tests MMCA.Common.UI.Gallery MMCA.Store.CI.slnf MMCA.ADC.CI.slnf csproj slnx"},{"u":"/docs/onboarding/00-primer.html#6-the-34-category-architecture-evaluation-lens","d":"Primer, the concepts, stack, and conventions you need first","k":"Onboarding Guide","t":"6. The 34-category architecture-evaluation lens","x":"This codebase is also scored against a 34-category rubric (Website/docs-src/governance/ArchitectureEvaluationCriteria.md, published at ). This guide weaves the rubric in so you…","i":"Rubric Name"},{"u":"/docs/onboarding/group-01-result-error-handling.html","d":"1. Result & Error Handling","k":"Onboarding Guide","x":"This is the first capability chapter, and it is deliberately first because the pattern it teaches underpins almost every other one in the guide. Before you read a command…","i":"ArgumentOutOfRangeException.ThrowIfNegative ResultJsonConverterFactory.CreateConverter ArgumentNullException.ThrowIfNull DomainInvariantViolationException MMCA.Common.Shared.Serialization ApiControllerBase.HandleFailure MMCA.Common.Shared.Abstractions System.Text.Json.Utf8JsonReader MMCA.Common.Shared.Exceptions ValidationFailureExtensions ResultJsonConverterFactory System.Collections.Generic"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","x":"What this group covers. This is the DDD heart of the framework, the small, dependency-light primitives every business model in MMCA.Common and MMCA.ADC is built from. There are…","i":"EnumerationJsonConverterFactory AuditableAggregateRootEntity IdValueGeneratedAttribute CurrencyJsonConverter PhoneNumberInvariants EntityTypeExtensions EnumerationConverter AuditableBaseEntity MMCA.Common.Domain MMCA.Common.Shared RedactableProperty AddressInvariants"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#the-entity-chain-one-capability-per-rung","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"The entity chain, one capability per rung","x":"Read the chain bottom-up. BaseEntity (MMCA.Common/Source/Core/MMCA.Common.Domain/Entities/BaseEntity.cs:14) is almost nothing: a single required init identifier of the per-entity…","i":"AuditableAggregateRootEntity AuditSaveChangesInterceptor ChangeTracker.Entries AuditableBaseEntity GetChildOrNotFound RemoveDomainEvents ClearDomainEvents IAuditableEntity ValidateSetItems TIdentifierType AddDomainEvent entry.Property"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#two-opt-in-markers-beside-the-chain","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Two opt-in markers beside the chain","x":"Not every cross-cutting capability belongs on the inheritance chain, because not every entity should pay for it. ITenantEntity…","i":"AuditTrailSaveChangesInterceptor TenantSaveChangesInterceptor entity.HasQueryFilter ApplyTenantFilters IAuditableEntity TenantFilterName AddMultiTenancy AuditTrailEntry IAuditedEntity AddAuditTrail configuration ITenantEntity"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#how-a-domain-event-leaves-an-aggregate","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"How a domain event leaves an aggregate","x":"The runtime flow ties this group to the events/outbox group. A command handler loads an aggregate, calls a business method, and that method calls AddDomainEvent(...); the event…","i":"DomainEventSaveChangesInterceptor context.ChangeTracker.Entries RemoveDomainEvents DomainEntityState IIntegrationEvent DeferredDispatch OutboxProcessor AddDomainEvent IAggregateRoot OutboxMessage IDomainEvent Unchanged"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#value-objects-invalid-instances-cannot-exist","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Value objects, invalid instances cannot exist","x":"The second family models concepts with no identity: two Money(10, USD) are equal because their values match, not because they are the same row. ValueObject is the cheapest…","i":"EnsurePreferredCultureIsValid EnsurePreferredThemeIsValid EnsureCollectionIsNotEmpty InvalidOperationException PhoneNumberValueConverter EnsureMoneyIsNotNegative EnsureBytesAreNotEmpty EnsureStringIsNotEmpty CurrencyJsonConverter EnsureStringMaxLength PhoneNumberInvariants EnsureIdIsNotDefault"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#smart-enumerations-a-closed-set-that-can-carry-behavior","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Smart enumerations, a closed set that can carry behavior","x":"Enumeration (MMCA.Common/Source/Core/MMCA.Common.Shared/ValueObjects/Enumeration.cs:71) is the answer to a recurring shape a CLR enum handles badly: a closed set of named members…","i":"ValueObjectsAreImmutableSealedInShared JsonSerializerOptions.Converters EnumerationJsonConverterFactory Enumeration.UnknownValue Enumeration.UnknownName CurrencyJsonConverter EnumerationConverter ReadOnlyCollection FrozenDictionary JsonConverter JsonException TEnumeration"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#governance-markers-metadata-that-other-layers-act-on","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Governance markers, metadata that other layers act on","x":"The last family is tiny attributes and helpers that carry intent the rest of the stack reads reflectively. PiiAttribute…","i":"AuditTrailSaveChangesInterceptor CultureInfo.InvariantCulture IdValueGeneratedAttribute PiiRedactor.RedactedToken EncryptedStringConverter PiiConventionTestsBase ConcurrentDictionary EntityTypeExtensions GetCustomAttribute IsIdValueGenerated MMCA.Common.Domain PiiConventionTests"},{"u":"/docs/onboarding/group-02-domain-building-blocks.html#where-this-group-sits","d":"2. Domain Building Blocks (Entities, Value Objects, Aggregates)","k":"Onboarding Guide","t":"Where this group sits","x":"Everything above is consumed by the layers that follow: every module entity (for example the Conference domain, Engagement, and Identity modules) derives from one of the three…","i":"EnumerationJsonConverterFactory.CreateConverter PhoneNumberInvariants.EnsurePhoneNumberIsValid AddressInvariants.EnsureAddressLine1IsValid MMCA.Common.Domain.Interfaces.IAnonymizable AddressInvariants.AddressLine1MaxLength EntityTypeExtensions.IsIdValueGenerated AddressInvariants.EnsureAddressIsValid EventInvariants.EnsureDateRangeIsValid ValueObjectsAreImmutableSealedInShared EntityTypeBuilderExtensions.OwnsMoney EntitiesWithPiiImplementAnonymizable EmailInvariants.EnsureEmailIsValid"},{"u":"/docs/onboarding/group-03-querying-specifications.html","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","x":"What this group covers. Every read in MMCA.Common and ADC (\"list the published events\", \"get session 42\", \"the speakers in Atlanta, page 3, sorted by name, with only the name and…","i":"Expression IQueryable TEntity OFFSET SELECT ORDER WHERE bool Func name bio"},{"u":"/docs/onboarding/group-03-querying-specifications.html#the-specification-pattern-the-trusted-predicate","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The Specification pattern, the trusted predicate","x":"ISpecification (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/ISpecification.cs:12) exposes two faces of one rule: a Criteria expression tree that EF Core translates to…","i":"SpecificationConventionTestsBase PublishedEventSpecification CrossSourceSpecification OwnedByUserSpecification dependent.ForeignKey Enumerable.Contains InlineSpecification ParameterExpression s.Event.IsPublished Expression.AndAlso Expression.Invoke Expression.Lambda"},{"u":"/docs/onboarding/group-03-querying-specifications.html#dynamic-filtering-one-strategy-per-clr-type","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Dynamic filtering, one Strategy per CLR type","x":"User filters arrive as a Dictionary , property name to operator key plus raw string value, parsed from the query string by QueryFilterModelBinder at the API edge, which caps a…","i":"Filter.Operator.NotSupported QueryParameterizationTests Filter.Property.NotFound Filter.Type.NotSupported datetimefilterstrategy QueryFilterModelBinder ResolveFilterValueType decimalfilterstrategy Filter.Value.Invalid StringFilterStrategy ResolvePropertyInfo boolfilterstrategy"},{"u":"/docs/onboarding/group-03-querying-specifications.html#sorting-sparse-fieldsets-and-paging-arithmetic","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Sorting, sparse fieldsets, and paging arithmetic","x":"QueryFieldService (MMCA.Common/Source/Core/MMCA.Common.Application/Services/QueryFieldService.cs:16) owns the rest of read shaping. ApplySorting (QueryFieldService.cs:135)…","i":"PropertyInfo.GetValue ValidateSortDirection ApplyFieldSelection ShapeCollectionData GetShapedAccessors Expression.Lambda QueryFieldService PagingMath.Clamp PropertyAccessor MaxCacheEntries ExpandoObject ApplySorting"},{"u":"/docs/onboarding/group-03-querying-specifications.html#the-pipeline-two-paths-and-one-contract","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The pipeline, two paths and one contract","x":"IEntityQueryPipeline (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/IEntityQueryPipeline.cs:10) is the execution contract, implemented by the sealed…","i":"inavigationmetadataprovider NavigationMetadataProvider MaxUnboundedResultLimit NavigationPropertyInfo CountUnpaginatedAsync EntityQueryParameters IEntityQueryPipeline INavigationPopulator entityquerypipeline navigationPopulator NavigationMetadata FrozenDictionary"},{"u":"/docs/onboarding/group-03-querying-specifications.html#the-query-service-the-public-face","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The query service, the public face","x":"IEntityQueryService (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/IEntityQueryService.cs:19) and its concrete EntityQueryService…","i":"IEntityQueryPipeline.ExecuteAsync SpeakerEntityQueryService BuildPaginationMetadata MaxUnboundedResultLimit TryGetByIdFastPathAsync DTOToEntityPropertyMap TryGetFastPathIncludes EntityQueryParameters PagedCollectionResult GetAllForLookupAsync INavigationPopulator DTOMapper.MapToDTOs"},{"u":"/docs/onboarding/group-03-querying-specifications.html#end-to-end-one-list-request","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"End to end, one list request","x":"The request reaches a read controller, EntityControllerBase (Group 12), which resolves MaxPageSize per request from IApplicationSettings, falling back to 500 when unset…","i":"PublicSessionStatusSpecification.StatusCriteria EntityQueryPipeline.MaxUnboundedResultLimit Filtering.DynamicQueryConfig.Parameterized MMCA.Common.Application.Services.Filtering QueryFilterService.ResolveFilterValueType System.Linq.Expressions.ExpressionVisitor NavigationMetadataProvider.BuildIncludes CrossSourceSpecification.BuildCriteria MMCA.Common.Application.Services.Query MMCA.Common.Application.Specifications QueryFieldService.ApplyFieldSelection QueryFieldService.ShapeCollectionData"},{"u":"/docs/onboarding/group-04-events-outbox.html","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","x":"What this chapter covers. This group is the codebase's event spine: how an aggregate says \"something happened\", how that fact is persisted so it cannot be lost, and how it…"},{"u":"/docs/onboarding/group-04-events-outbox.html#the-two-kinds-of-event","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The two kinds of event","x":"Everything starts with two marker interfaces in the Domain layer. IDomainEvent is the base contract: a DateOccurred timestamp (when the business action happened, not when it was…","i":"BaseIntegrationEvent EntityChangedEvent DomainEntityState IIntegrationEvent BaseDomainEvent TIdentifierType Infrastructure UserRegistered SchemaVersion Architecture DateOccurred IDomainEvent"},{"u":"/docs/onboarding/group-04-events-outbox.html#raising-and-capturing-where-the-outbox-is-written","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Raising and capturing: where the outbox is written","x":"Aggregates raise events by calling AddDomainEvent() (see AuditableAggregateRootEntity in G02), which simply buffers them on the entity. Nothing is dispatched yet; the events ride…","i":"DomainEventSaveChangesInterceptor OutboxMessage.FromDomainEvent AuditableAggregateRootEntity TIdentifierType AddDomainEvent OutboxMessages OutboxMessage SavingChanges Architecture DbContext Rubric Data"},{"u":"/docs/onboarding/group-04-events-outbox.html#the-routing-split-local-events-dispatch-in-process-integration-events-wait-for-the-bus","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The routing split: local events dispatch in-process, integration events wait for the bus","x":"Here is the detail that most people get wrong, and it is the heart of the design. After the transaction commits (SavedChanges), the interceptor does not treat all captured events…","i":"IIntegrationEventHandler IDomainEventDispatcher SafeDomainEventHandler DomainEventDispatcher someIntegrationEvent IDomainEventHandler TIntegrationEvent DbContextFactory OutboxFinalizer OutboxProcessor AddDomainEvent ExecuteUpdate"},{"u":"/docs/onboarding/group-04-events-outbox.html#the-safety-net-how-the-processor-schedules-itself","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The safety net: how the processor schedules itself","x":"The OutboxProcessor is a BackgroundService and the most intricate type in the group; most of its complexity is about not wasting work. It exists because the steps between commit…","i":"PollingIntervalSeconds ProcessingDelaySeconds BackgroundService OutboxCycleResult ComputeWaitTime OutboxProcessor OutboxSettings ExecuteUpdate IOutboxSignal SemaphoreSlim LeaseSeconds OutboxSignal"},{"u":"/docs/onboarding/group-04-events-outbox.html#failures-dead-letters-and-keeping-the-table-and-telemetry-bounded","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Failures, dead-letters, and keeping the table (and telemetry) bounded","x":"Delivery failures split into two very different outcomes, worth keeping straight. A transient failure (a handler or broker publish throwing) increments the row's RetryCount,…","i":"OutboxPollFilterProcessor outbox.dead_letter.count DeadLetterRetentionDays RetryBackoffBaseSeconds CleanupIntervalHours OutboxCleanupService MMCA.Common.Outbox Observability OutboxMetrics RetentionDays TimeProvider Operability"},{"u":"/docs/onboarding/group-04-events-outbox.html#the-pluggable-transport-in-process-versus-broker","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"The pluggable transport: in-process versus broker","x":"Here is the boundary that makes a module extractable without rewriting its handlers. Application code that wants to publish an integration event depends on IEventBus (or on the…","i":"InProcessMessageBus AddBrokerMessaging IIntegrationEvent InProcessEventBus BrokerMessageBus OutboxFinalizer OutboxProcessor BrokerEventBus Microservices Application IMessageBus IEventBus"},{"u":"/docs/onboarding/group-04-events-outbox.html#consuming-from-the-broker-the-inbox-and-the-generic-consumer","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Consuming from the broker: the inbox and the generic consumer","x":"On the receiving side of a broker hop, application code keeps writing plain IIntegrationEventHandler implementations; there is no MassTransit-specific consumer class to author…","i":"IntegrationEventConsumerExtensions RegisterIntegrationEventConsumer IBusRegistrationConfigurator IIntegrationEventHandler IntegrationEventConsumer AlreadyProcessedAsync MarkProcessedAsync DbUpdateException NoOpInboxStore EfInboxStore InboxMessage AddConsumer"},{"u":"/docs/onboarding/group-04-events-outbox.html#putting-it-together-one-events-life","d":"4. Domain & Integration Events + Outbox Dual-Dispatch","k":"Onboarding Guide","t":"Putting it together, one event's life","x":"To see the whole spine at once, follow a single integration event from a producer service to a consumer service in broker mode. (1) A command mutates an aggregate, which raises…","i":"MMCA.Common.Infrastructure.Persistence.Outbox MMCA.Common.Infrastructure.Persistence.Inbox OutboxProcessor.ProcessPendingMessagesAsync config.RegisterIntegrationEventConsumer ApplicationDbContext.SaveChangesAsync Microsoft.Extensions.Logging.ILogger MMCA.Common.Application.DomainEvents ApplicationDbContext.ConfigureInbox domainEventDispatcher.DispatchAsync MMCA.Common.Infrastructure.Services TryPersistStampsOnCancellationAsync IntegrationEventConsumerExtensions"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","x":"What this group covers. Every write and every read in an MMCA application is a use case: a small, single-purpose object (a command or a query) handed to a handler that does…","i":"TransactionalCommandDecorator FeatureGateCommandDecorator ValidatingCommandDecorator FeatureGateQueryDecorator ProfilingCommandDecorator CachingCommandDecorator LoggingCommandDecorator ProfilingQueryDecorator CachingQueryDecorator LoggingQueryDecorator ResultFailureFactory ICommandWithRequest"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#the-shape-thin-handlers-fat-pipeline","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"The shape: thin handlers, fat pipeline","x":"A handler is deliberately tiny. ICommandHandler (MMCA.Common/Source/Core/MMCA.Common.Application/UseCases/ICommandHandler.cs:9) and IQueryHandler…","i":"cancellationToken CancellationToken ICommandHandler IQueryHandler HandleAsync Patterns TCommand default TResult Design Result Rubric"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#how-the-pipeline-is-assembled-scrutor-registration-versus-execution-order","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"How the pipeline is assembled (Scrutor, registration versus execution order)","x":"The wiring lives in DependencyInjection.cs (MMCA.Common/Source/Core/MMCA.Common.Application/DependencyInjection.cs:21), exposed as extension(IServiceCollection services) members…","i":"ScanModuleApplicationServices ProfilingCommandDecorator AddApplicationDecorators DependencyInjectionTests AddApplicationProfiling ProfilingQueryDecorator DependencyInjection.cs EntityQueryPipeline IServiceCollection ICommandHandler TAssemblyMarker AddApplication"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#why-this-exact-order-and-what-each-layer-guards","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Why this exact order, and what each layer guards","x":"The nesting order is a deliberate cost-and-correctness argument, spelled out in the registration XML-doc (DependencyInjection.cs:72-86): - Feature-gating is outermost so a…","i":"TransactionCommitAmbiguousException ICacheService.RemoveByPrefixAsync IFeatureManager.IsEnabledAsync TransactionalCommandDecorator FeatureGateCommandDecorator OperationCanceledException ValidatingCommandDecorator CqrsMetrics.QueryDuration ExecuteInTransactionAsync FeatureGateQueryDecorator Stopwatch.GetElapsedTime CachingCommandDecorator"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#opt-in-by-marker-interface-pay-only-for-what-you-use","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Opt-in by marker interface, pay only for what you use","x":"The pipeline is registered for every handler, but most decorators are dormant unless the use case asks for them. The switch is a set of tiny marker / role interfaces in…","i":"MMCA.Common.Application.UseCases GetTicketByIdQuery ICacheInvalidating FeatureManagement GetNowNextQuery IQueryCacheable ITransactional CacheDuration IFeatureGated CachePrefix FeatureName OutputCache"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#tenant-scoping-and-the-two-lock-tables","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Tenant scoping and the two lock tables","x":"Both caching decorators are multi-tenant aware, and the reason is a nice illustration of where a cross-cutting concern has to live. ICacheService is a singleton and therefore…","i":"ICacheService.GetOrCreateAsync CachingQueryDecorator KeyedSemaphoreStripe QueryCacheKeyLocks ITenantContext TenantCacheKey CacheKeyLocks ICacheService tenantId TResult TQuery null"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#two-supporting-pieces-and-a-worked-example","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Two supporting pieces, and a worked example","x":"Two small helpers make the short-circuit decorators possible. ResultFailureFactory…","i":"AuditableAggregateRootEntity TypeInitializationException InvalidOperationException DeleteSessionCommand DeleteSpeakerCommand ResultFailureFactory DeleteEntityCommand DeleteEntityHandler ICacheInvalidating MMCA.Common.Cqrs TIdentifierType CachePrefix"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#the-other-application-layer-contracts-in-this-group","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"The other Application-layer contracts in this group","x":"Four contracts sit beside the pipeline rather than inside it. They are declared here, in the Application layer, and implemented in Infrastructure or the composition root, which…","i":"AuditTrailSaveChangesInterceptor InProcessDistributedLock IEntityRequestMapper RedisDistributedLock ICommandWithRequest ScheduledJobRunner cancellationToken IAuditTrailReader AuditTrailReader IAsyncDisposable IDistributedLock TIdentifierType"},{"u":"/docs/onboarding/group-05-cqrs-pipeline.html#where-this-fits-and-the-failure-mode-contract","d":"5. CQRS: Commands, Queries & the Decorator Pipeline","k":"Onboarding Guide","t":"Where this fits, and the failure-mode contract","x":"These contracts sit in the Application layer of Clean Architecture (primer §1), above Domain and below Infrastructure and the API. The API layer (G12) resolves a closed handler…","i":"Microsoft.FeatureManagement.IFeatureManager MMCA.Common.Application.UseCases.Decorators QueryCacheKeyLocks.Locks.AcquireAsync MMCA.Common.Application.Extensions MMCA.Common.Application.Interfaces ICacheService.RemoveByPrefixAsync correlationContext.CorrelationId MMCA.Common.Application.UseCases System.Diagnostics.Metrics.Meter ConferenceCategoryCreateRequest MMCA.Common.Shared.Abstractions ICacheService.GetOrCreateAsync"},{"u":"/docs/onboarding/group-06-validation.html","d":"6. Validation","k":"Onboarding Guide","x":"This chapter covers the small, framework-level validation kit that MMCA.Common.Application ships so that every consuming module validates command input the same way: a set of…","i":"AddressInvariants.AddressLine1MaxLength AddressInvariants.AddressLine2MaxLength ValidationFailureExtensions.ToErrors AddValidatorsFromAssemblyContaining AddressInvariants.CountryMaxLength AddressInvariants.ZipCodeMaxLength MMCA.Common.Application.Extensions MMCA.Common.Application.Validation System.Linq.Expressions.Expression AddressInvariants.StateMaxLength AddressInvariants.CityMaxLength MMCA.Common.Shared.ValueObjects"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html","d":"7. Persistence & EF Core","k":"Onboarding Guide","x":"What this group covers. This is the framework's data-access engine: everything between a domain aggregate and a row in a database. It is the single largest group in the guide…","i":"ApplicationDbContext SQLServerDbContext IWriteRepository SaveChangesAsync CosmosDbContext IReadRepository SqliteDbContext TIdentifierType IRepository UnitOfWork TEntity"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#one-base-context-one-class-per-engine-one-instance-per-database","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"One base context, one class per engine, one instance per database","x":"ApplicationDbContext (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:39) is an abstract primary-constructor class over EF's…","i":"Database.CreateExecutionStrategy DataSourceModelCacheKeyFactory IX_AuditTrailEntries_Entity IX_OutboxMessages_Processed IX_InboxMessages_MessageId IX_ScheduledJobs_NextRunOn PendingModelChangesWarning IX_OutboxMessages_Pending BeginTransactionAsync ChangeTracker.Entries ApplicationDbContext EnableRetryOnFailure"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#savechanges-as-an-interceptor-pipeline","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"SaveChanges as an interceptor pipeline","x":"The base context resolves its interceptors from DI in OnConfiguring (ApplicationDbContext.cs:236-261), and registration order is execution order. The audit interceptor runs…","i":"DomainEventSaveChangesInterceptor AuditSaveChangesInterceptor DiscardAbandonedCapture IDomainEventDispatcher BeginCaptureExclusion ConditionalWeakTable EndCaptureExclusion FlushDeferredAsync GetRequiredService RemoveDomainEvents CurrentSaveUserId IIntegrationEvent"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#the-tenant-boundary-read-filter-plus-write-guard","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"The tenant boundary, read filter plus write guard","x":"Multi-tenancy (ADR-073) is two independent halves that meet in this group. The read half is the named Tenant query filter the base context applies to every non-owned…","i":"TenantSaveChangesInterceptor CrossTenantWriteException InvalidOperationException TenantDataSourceTargets TenantDataSourceTarget ApplicationDbContext IgnoreQueryFilters CurrentTenantId ITenantEntity TenantContext e.TenantId SoftDelete"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#recording-what-changed-the-audit-trail","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Recording what changed, the audit trail","x":"AuditTrailSaveChangesInterceptor (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/AuditTrail/AuditTrailSaveChangesInterceptor.cs:62) is the fourth interceptor and…","i":"AuditTrailSaveChangesInterceptor AuditTrailCleanupJob AuditTrailReader AuditTrailEntry IAuditedEntity AddAuditTrail ExecuteDelete RedactedToken RetentionDays PiiAttribute PropertyName PiiRedactor"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#repositories-and-the-unit-of-work","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Repositories and the unit of work","x":"Handlers do not touch a DbContext directly. They ask a UnitOfWork (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/UnitOfWork.cs:13) for a repository. The…","i":"TransactionCommitAmbiguousException DefaultSqlServerDbContextFactory ApplicationDbContextEFFactory DefaultCosmosDbContextFactory DefaultSqliteDbContextFactory UpdatePropertySetterBuilder EFReadRepositoryDecorator ExecuteInTransactionAsync HasPendingMigrationsAsync IPhysicalDbContextFactory ChangeTracker.HasChanges PhysicalDbContextFactory"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#routing-an-entity-to-its-database","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Routing an entity to its database","x":"The heart of ADR-006 is that every entity resolves to a DataSourceKey (MMCA.Common/Source/Core/MMCA.Common.Application/Interfaces/Infrastructure/DataSourceKey.cs:15), a (Engine,…","i":"IEntityDataSourceRegistry EntityDataSourceRegistry UseDataSourceAttribute NamespaceConventions UseDatabaseAttribute IDataSourceResolver DataSourceResolver DataSourceService DataSourceKey GetModuleName DataSources DataSource"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#two-model-finalizing-conventions","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Two model-finalizing conventions","x":"The base context adds both of its conventions in ConfigureConventions (ApplicationDbContext.cs:282-297), and each exists because a cross-cutting policy above would otherwise…","i":"CrossDataSourceDegradeConvention SoftDeleteUniqueIndexConvention IndexBuilderExtensions ConfigureConventions INavigationPopulator HasSoftDeleteFilter SoftDeleteFilterSql IndexBuilder extension IsDeleted TEntity Build"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#entity-configuration-and-engine-portability","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Entity configuration and engine portability","x":"Concrete entity configurations derive from the engine-aware EntityTypeConfiguration…","i":"DefaultEntityConfigurationAssemblyProvider IEntityConfigurationAssemblyProvider IEntityTypeConfigurationSQLServer NullableEnumerationValueConverter NullablePhoneNumberValueConverter EntityTypeConfigurationSQLServer IEntityTypeConfigurationCosmos IEntityTypeConfigurationSqlite EntityTypeConfigurationCosmos EntityTypeConfigurationSqlite PushNotificationConfiguration UserNotificationConfiguration"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#encryption-seeding-design-time-and-the-shared-helpers","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Encryption, seeding, design time, and the shared helpers","x":"A handful of supporting pieces round out the EF side. EncryptedStringConverter…","i":"PaymentReconciliationService IDesignTimeDbContextFactory DesignTimeDbContextOptions IdentityModuleDbSeederBase DesignTimeDbContextHelper NullDomainEventDispatcher PeriodicBackgroundService EncryptedStringConverter EntityDataSourceRegistry ExplicitAssemblyProvider EFQueryableExecutor DataSourceResolver"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#blobs-images-and-native-push","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Blobs, images, and native push","x":"The group also carries the storage-adjacent infrastructure services that are not EF at all, each behind an Application-layer interface with a null default so a host that has not…","i":"AzureNotificationHubNativePushSender AzureNotificationHubDeviceRegistrar AzureBlobFileStorageService ImageSharpImageProcessor NullPushDeviceRegistrar NullFileStorageService IPushDeviceRegistrar NullNativePushSender IFileStorageService ImageContentSniffer NativePushPayloads INativePushSender"},{"u":"/docs/onboarding/group-07-persistence-ef-core.html#where-this-group-sits","d":"7. Persistence & EF Core","k":"Onboarding Guide","t":"Where this group sits","x":"Persistence is the concrete floor the abstract domain stands on. The entity bases and audit contracts from Group 02 are what the interceptors stamp and the query filters hide;…","i":"MMCA.Common.Application.Interfaces.Infrastructure MMCA.Common.Infrastructure.Persistence.AuditTrail MMCA.Common.Infrastructure.Persistence.DbContexts MMCA.Common.Infrastructure.Persistence.Encryption DomainEventSaveChangesInterceptor.DropDeferred EntityTypeConfiguration.ApplyEngineConventions Microsoft.Extensions.Hosting.BackgroundService AddInfrastructure_RegistersIRepositoryFactory CrossTenantWriteException.ForUnresolvedTenant ModelBuilderExtensions.ApplyAllConfigurations DangerousAcceptAnyServerCertificateValidator RelationalEventId.PendingModelChangesWarning"},{"u":"/docs/onboarding/group-08-auth.html","d":"8. Authentication & Authorization","k":"Onboarding Guide","x":"What this group covers. This is the security spine of the framework: how a caller proves who they are (authentication), how the system decides what they may do (authorization),…","i":"SessionCookieAuthenticationHandler PermissionAuthorizationHandler AuthenticationServiceBase AuthenticationValidators ClaimBasedUserIdProvider AuthorizationExtensions ILoginProtectionService IPasswordChangeableUser LoginProtectionSettings CookieSessionRefresher IAuthenticationService LoginProtectionService"},{"u":"/docs/onboarding/group-08-auth.html#tokens-one-signing-switch-two-validation-worlds","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Tokens: one signing switch, two validation worlds","x":"The framework mints two credentials on every successful login: a short-lived access token (a JWT, 15 minutes by default,…","i":"OidcDiscoveryEndpointExtensions OpenIdConnectMetadataWarmupTask GetPrincipalFromExpiredToken ExecutionAndPublication JwksEndpointExtensions RandomNumberGenerator JwtSigningAlgorithm IValidatableObject additionalClaims SigningAlgorithm PublicationOnly RsaJwksProvider"},{"u":"/docs/onboarding/group-08-auth.html#the-shared-authentication-workflow","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"The shared authentication workflow","x":"Login, registration, refresh, and revocation are not re-implemented per app. They live once in AuthenticationServiceBase…","i":"RefreshTokenRequestValidator AuthenticationServiceBase FindUntrackedByEmailAsync AuthenticationValidators OAuthCodeExchangeRequest AuthenticationResponse CancellationToken.None IAuthenticationService AuthenticationRequest AuthenticationService ChangePasswordRequest LoginRequestValidator"},{"u":"/docs/onboarding/group-08-auth.html#what-the-apps-user-aggregate-must-expose","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"What the app's User aggregate must expose","x":"The shared workflows never see an app's User class. They see four small Domain-layer contracts, each sized to one workflow, which is the [Rubric §1, SOLID] interface-segregation…","i":"GetUserPreferencesHandlerBase ChangePreferencesHandlerBase ChangePasswordHandlerBase ChangePreferencesRequest IPasswordChangeableUser UserPreferencesResponse DeleteUserHandlerBase AuditableBaseEntity RevokeRefreshToken UpdateRefreshToken UpdatePreferences IUserPreferences"},{"u":"/docs/onboarding/group-08-auth.html#passwords-and-brute-force-protection","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Passwords and brute-force protection","x":"Password material is handled by PasswordHasher (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Services/PasswordHasher.cs:12), which hashes with PBKDF2-HMAC-SHA512 at 600,000…","i":"CryptographicOperations.FixedTimeEquals ILoginProtectionService LoginProtectionSettings LoginProtectionService IDistributedCache MaxFailedAttempts MaxLockoutSeconds IPasswordHasher PasswordHasher ICacheService Email Range"},{"u":"/docs/onboarding/group-08-auth.html#reading-identity-from-claims","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Reading identity from claims","x":"Once a request is authenticated, downstream code needs the caller's identity without re-parsing the JWT. CurrentUserService…","i":"CultureInfo.InvariantCulture ClaimBasedUserIdProvider IHttpContextAccessor ICurrentUserService CurrentUserService ClaimsPrincipal IUserIdProvider AuthClaimTypes GetClaimValue Clients.User TokenService IsInRole"},{"u":"/docs/onboarding/group-08-auth.html#authorization-roles-permissions-ownership","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Authorization: roles, permissions, ownership","x":"The framework supports three overlapping authorization styles, wired together by the single AddAuthorizationPolicies() extension in AuthorizationExtensions…","i":"PermissionAuthorizationHandler AllowMissingOwnerAttribute OwnerOrAdminFilterOptions PermissionRegistryBuilder AddAuthorizationPolicies PermissionPolicyProvider AuthorizationExtensions HasPermissionAttribute AuthorizationPolicies PermissionRequirement RequireAuthenticated IPermissionRegistry"},{"u":"/docs/onboarding/group-08-auth.html#session-cookies-keeping-ssr-authenticated","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Session cookies: keeping SSR authenticated","x":"The final cluster solves a Blazor-specific problem: an interactive Blazor app keeps its access token in browser memory, but a cold server-side render (a new tab, an F5, an…","i":"CookieSessionRefreshMiddlewareExtensions SessionCookieAuthenticationExtensions SessionCookieAuthenticationHandler CookieSessionRefreshMiddleware ICookieSessionRefresher CookieSessionRefresher SessionCookieEndpoints KeyedSemaphoreStripe SessionCookieRequest SessionTokenResponse SessionTokenResult CookieTokenReader"},{"u":"/docs/onboarding/group-08-auth.html#privacy-the-data-subject-export-package","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Privacy: the data-subject export package","x":"Three members of this group belong to the privacy surface that sits beside erasure. UserDataExportDTO (MMCA.Common/Source/Core/MMCA.Common.Shared/Privacy/UserDataExportDTO.cs:15)…","i":"ExportUserDataHandlerBase DataExportControllerBase UserDataExportSectionDTO IUserDataExportSection Privacy.DataExport UserDataExportDTO PrivacyFeatures FormatVersion FeatureGate Authorize Available Subject"},{"u":"/docs/onboarding/group-08-auth.html#shared-primitives-and-adjacent-members","d":"8. Authentication & Authorization","k":"Onboarding Guide","t":"Shared primitives and adjacent members","x":"Four group members are general-purpose primitives that landed in this chapter because of how the dependency grouping fell, though one of them is now load-bearing for auth.…","i":"MMCA.Common.Application.Interfaces.Infrastructure AuthorizationExtensions.AddAuthorizationPolicies context.ActionDescriptor.EndpointMetadata.OfType Microsoft.AspNetCore.Http.IHttpContextAccessor JsonWebKeyConverter.ConvertFromRSASecurityKey SessionCookieAuthenticationHandler.SchemeName Microsoft.AspNetCore.SignalR.IUserIdProvider Microsoft.IdentityModel.Tokens.JsonWebKeySet services.AddValidatorsFromAssemblyContaining ArgumentException.ThrowIfNullOrWhiteSpace CookieTokenReader.FreshAccessTokenItemKey ICookieSessionRefresher.GetOrRefreshAsync"},{"u":"/docs/onboarding/group-09-caching.html","d":"9. Caching","k":"Onboarding Guide","x":"What this group covers. Caching in this codebase is small, deliberate, and woven into the CQRS pipeline rather than scattered across handlers. The group is eight types: one port…","i":"Microsoft.Extensions.Caching.Hybrid.HybridCache HybridCacheEntryFlags.DisableUnderlyingData StackExchange.Redis.IConnectionMultiplexer Microsoft.Extensions.Options.IOptions MMCA.Common.Application.Interfaces MMCA.Common.Infrastructure.Caching DistributedCacheServiceRedisTests connectionMultiplexer.GetServers AbsoluteExpirationRelativeToNow PublicEndpointOutputCachePolicy System.Text.Json.JsonSerializer LogPrefixEvictionNoMultiplexer"},{"u":"/docs/onboarding/group-10-notifications.html","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","x":"What this group covers. This is the notification subsystem, the machinery that turns \"an organizer wants to tell every attendee something\" into messages that actually reach…","i":"INotificationRecipientProvider NullPushNotificationSender NullLiveChannelPublisher IPushNotificationSender ILiveChannelPublisher NotificationModule DevicesController INativePushSender UserNotification NotificationHub SmtpEmailSender IEmailSender"},{"u":"/docs/onboarding/group-10-notifications.html#the-layering-and-why-the-pieces-sit-where-they-do","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The layering, and why the pieces sit where they do","x":"The dependency flow of the group mirrors the framework's Clean Architecture story ([Rubric §3, Clean Architecture]). The Domain layer holds the two aggregates, PushNotification…","i":"NullNotificationRecipientProvider Notification.PushNotifications SignalRPushNotificationSender SendPushNotificationRequest SignalRLiveChannelPublisher NullPushNotificationSender PushNotificationInvariants DeviceInstallationRequest NullLiveChannelPublisher NotificationsController PushNotificationCreated PushNotificationStatus"},{"u":"/docs/onboarding/group-10-notifications.html#the-broadcast-send-flow-end-to-end","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The broadcast send flow, end to end","x":"Sending a notification is a command-side vertical slice ([Rubric §5, Vertical Slice], [Rubric §6, CQRS & Event-Driven]). An organizer POSTs to NotificationsController, which is…","i":"NotificationFeatures.PushNotifications AttendeeNotificationRecipientProvider AddNotificationApplicationServices NullNotificationRecipientProvider INotificationRecipientProvider PushNotification.NoRecipients unitOfWork.GetReadRepository SendPushNotificationCommand SendPushNotificationHandler PushNotificationDTOMapper IPushNotificationSender NotificationsController"},{"u":"/docs/onboarding/group-10-notifications.html#the-inbox-side","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The inbox side","x":"Reading and acknowledging notifications is the query/command counterpart, served by InboxController under the same feature gate and [Authorize(RequireAuthenticated)], so any user…","i":"GetUnreadNotificationCountQuery MarkAllNotificationsReadCommand MarkNotificationReadCommand MarkNotificationReadHandler ICurrentUserService.UserId GetMyNotificationsHandler UserNotification.NotFound GetMyNotificationsQuery RequireAuthenticated PushNotification UserNotification InboxController"},{"u":"/docs/onboarding/group-10-notifications.html#the-signalr-transport-and-how-it-survives-extraction","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The SignalR transport, and how it survives extraction","x":"NotificationHub is intentionally thin (MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Hubs/NotificationHub.cs:16-17): it is [Authorize]d, and beyond ASP.NET's built-in…","i":"LiveChannelPublisherGrpcAdapter services__notification__grpc__0 SignalRPushNotificationSender SignalRLiveChannelPublisher NullLiveChannelPublisher LiveChannelGrpcService ILiveChannelPublisher AddPushNotifications RequireAuthorization _grpc.notification MapNotificationHub NotificationHub"},{"u":"/docs/onboarding/group-10-notifications.html#the-module-host-native-device-registration-and-the-privacy-export","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"The module host, native-device registration, and the privacy export","x":"On the ADC side the whole capability is packaged by NotificationModule (MMCA.ADC/Source/Modules/Notification/MMCA.ADC.Notification.API/NotificationModule.cs:15), an IModule that…","i":"UserNotificationExportServiceGrpcAdapter DisabledUserNotificationExportService UserNotificationExportGrpcService IUserNotificationExportService UserNotificationExportItemDTO UserNotificationExportService currentUserService.UserId DeviceInstallationRequest AddNotificationModule IPushDeviceRegistrar RequiresDependencies DependencyInjection"},{"u":"/docs/onboarding/group-10-notifications.html#where-this-group-sits","d":"10. Notifications (Push + In-App Inbox + Email)","k":"Onboarding Guide","t":"Where this group sits","x":"Upstream, this group depends on the domain building blocks of Group 02 (both aggregates derive from AuditableAggregateRootEntity ), the Result pattern of Group 01, the CQRS…","i":"LiveChannelPushService.LiveChannelPushServiceBase MMCA.Common.Application.Interfaces.Infrastructure MMCA.ADC.Notification.Shared.UserNotifications attendeeQueryService.GetAttendeeUserIdsAsync services.AddNotificationApplicationServices MMCA.Common.API.Controllers.Notifications NotificationHub.ReceiveNotificationMethod PushNotificationInvariants.TitleMaxLength Microsoft.Extensions.DependencyInjection PushNotificationInvariants.BodyMaxLength CommonInvariants.EnsureStringIsNotEmpty pushNotificationSender.SendToUsersAsync"},{"u":"/docs/onboarding/group-11-navigation-populators.html","d":"11. Navigation Metadata & Populators (EF-decoupled eager loading)","k":"Onboarding Guide","x":"EF Core gives you .Include() for eager loading, and for a single SQL Server database that is the right tool. But this codebase is a database-per-service modular monolith…","i":"navigationMetadata.UnsupportedIncludes.Count MMCA.Common.Application.Services.Navigation NavigationLoader.LoadChildrenPropertyAsync NavigationMetadataProvider.BuildIncludes NavigationMetadata.UnsupportedIncludes IDataSourceService.HaveIncludeSupport NavigationLoader.LoadFKPropertyAsync INavigationPopulator.PopulateAsync MMCA.Common.Application.Interfaces DeclarativeNavigationPopulator.cs CrossDataSourceDegradeConvention EntityQueryPipeline.ExecuteAsync"},{"u":"/docs/onboarding/group-12-api-hosting-mapping.html","d":"12. API Hosting, Middleware, Idempotency & DTO/Contract Mapping","k":"Onboarding Guide","x":"What this group covers. This is the ASP.NET Core edge of the framework: the layer that turns an HTTP request into a domain call and turns a Result back into an HTTP response.…","i":"Microsoft.Extensions.Localization.LocalizedString Microsoft.AspNetCore.Http.IProblemDetailsService Microsoft.EntityFrameworkCore.DbUpdateException Microsoft.AspNetCore.Http.IHttpContextAccessor Microsoft.IdentityModel.Tokens.JsonWebKeySet IDbContextFactory.HasPendingMigrationsAsync AuthorizationPolicies.RequireAuthenticated Microsoft.AspNetCore.Authentication.Google ArgumentException.ThrowIfNullOrWhiteSpace ApplicationSettings.DatabaseInitStrategy StatusCodes.Status499ClientClosedRequest StatusCodes.Status500InternalServerError"},{"u":"/docs/onboarding/group-13-grpc-contracts.html","d":"13. gRPC & Inter-Service Contracts","k":"Onboarding Guide","x":"What this chapter is about. Once the ADC modules stopped sharing a process and became four separate service hosts (Identity, Conference, Engagement, Notification), the in-process…","i":"Microsoft.AspNetCore.Http.IHttpContextAccessor ArgumentException.ThrowIfNullOrWhiteSpace Microsoft.Extensions.DependencyInjection ErrorHttpMapping.ErrorTypeToStatusCode AddConferenceSessionValidationClient Microsoft.Extensions.Http.Resilience Microsoft.Extensions.Logging.ILogger ResultGrpcExtensions.ThrowIfFailure ResultGrpcExtensions.ToRpcException Grpc.Core.Interceptors.Interceptor ArgumentNullException.ThrowIfNull ISessionBookmarkValidationService"},{"u":"/docs/onboarding/group-14-module-system-composition.html","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","x":"What this chapter covers. This is the wiring layer, the code that turns a pile of layered assemblies into a running host. It answers three questions a new host author asks: how…","i":"ConnectionStringSettings InProcessDistributedLock PushNotificationSettings UseDataSourceAttribute RedisDistributedLock UseDatabaseAttribute ApplicationSettings DataSourcesSettings DependencyInjection FileStorageSettings PersistenceSettings AuditTrailSettings"},{"u":"/docs/onboarding/group-14-module-system-composition.html#the-module-contract-and-the-boundary-it-creates","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The module contract and the boundary it creates","x":"A module is the unit of cohesion above a feature slice: Conference, Engagement, Identity, Notification. Each one implements IModule…","i":"DisabledSessionBookmarkValidationService DisabledEventLiveValidationService GetSessionBookmarkCountHandler IBookmarkCountService RegisterDisabledStubs RequiresDependencies AddConferenceModule applicationSettings ConferenceModule moduleEnabled Dependencies Register"},{"u":"/docs/onboarding/group-14-module-system-composition.html#discovery-and-kahn-ordered-registration","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Discovery and Kahn-ordered registration","x":"ModuleLoader (MMCA.Common/Source/Core/MMCA.Common.Application/Modules/ModuleLoader.cs:15) is the engine. Its DiscoverAndRegister comes in two overloads: the short one…","i":"AppDomain.CurrentDomain.GetAssemblies ModulesSettings.IsModuleEnabled ValidateModuleDependencies ValidateRemoteDependencies Activator.CreateInstance IModuleSeeder.SeedAsync RegisterDisabledStubs RegisterEnabledModule RequiresDependencies DisabledModuleNames DiscoverAndRegister RemoteDependencies"},{"u":"/docs/onboarding/group-14-module-system-composition.html#the-two-composition-roots-and-the-ordering-they-enforce","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The two composition roots and the ordering they enforce","x":"Service registration itself lives in two static DependencyInjection classes, each using a C extension(IServiceCollection services) block (see primer §4 for the extension(T)…","i":"ScanModuleApplicationServices FeatureGateCommandDecorator INavigationMetadataProvider AddNativePushNotifications AddApplicationDecorators InProcessDistributedLock AddApplicationProfiling AddAzureBlobFileStorage CommandRequestValidator LoggingCommandDecorator IConnectionMultiplexer IDomainEventDispatcher"},{"u":"/docs/onboarding/group-14-module-system-composition.html#opt-in-platform-features-are-composed-the-same-way","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Opt-in platform features are composed the same way","x":"Four newer capabilities are registered beside the roots rather than inside them, and they share one discipline: registering a feature is not the same as turning it on.…","i":"AuditTrailSaveChangesInterceptor TenantSaveChangesInterceptor AddUserDataExportSection TenancySettingsValidator IUserDataExportSection MMCA.Common.Scheduler AuditTrailEntryDTO AuditTrailSettings ScheduledJobRunner AddInfrastructure BackgroundService ScheduledJobEntry"},{"u":"/docs/onboarding/group-14-module-system-composition.html#assembly-anchors","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Assembly anchors","x":"Several pieces of machinery need a Type whose Assembly identifies a layer: Scrutor's FromAssemblyOf () scans, FluentValidation's AddValidatorsFromAssemblyContaining (), and…","i":"AddValidatorsFromAssemblyContaining AddInfrastructure AssemblyReference AddApplication ClassReference FromAssemblyOf AssemblyName Assembly static class Type"},{"u":"/docs/onboarding/group-14-module-system-composition.html#configuration-binding-the-settings-family","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Configuration binding, the Settings family","x":"Everything a host operator tunes arrives as a strongly-typed settings object bound from an appsettings.json section, each carrying a static readonly string SectionName so the…","i":"TenantDataSourceOverrideSettings EffectiveExcludedPathPrefixes ScheduledJobOverrideSettings IValidatableObject.Validate IConnectionStringSettings IPushNotificationSettings SQLServerConnectionString ConnectionStringSettings EffectiveResolutionOrder PushNotificationSettings TenancySettingsValidator TenantResolutionStrategy"},{"u":"/docs/onboarding/group-14-module-system-composition.html#the-two-routing-attributes","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"The two routing attributes","x":"Two attributes, both in MMCA.Common.Infrastructure, both Inherited = true so they ride down a configuration class hierarchy, encode where an entity is stored declaratively: the…","i":"MMCA.Common.Infrastructure EntityDataSourceRegistry UseDataSourceAttribute UseDatabaseAttribute DataSourceResolver DbContextFactory DataSource Inherited Domain true"},{"u":"/docs/onboarding/group-14-module-system-composition.html#shared-user-use-case-bases-composition-in-the-other-direction","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"Shared user use-case bases: composition in the other direction","x":"The chapter's last family is composition at the handler level rather than the container level. ADC and Store each own an Identity module, and five of their account use cases had…","i":"UserOwnershipRule.CheckOwnership GetUserPreferencesHandlerBase UserDataExportSectionDefaults ChangePreferencesHandlerBase UserDataExportSectionResult ChangePasswordHandlerBase ExportUserDataHandlerBase ISoftDeletedUserValidator SoftDeletedUserValidator GetUserPreferencesQuery IUserDataExportSection DeleteUserHandlerBase"},{"u":"/docs/onboarding/group-14-module-system-composition.html#end-to-end-one-hosts-boot","d":"14. Module System, Composition & Configuration","k":"Onboarding Guide","t":"End-to-end: one host's boot","x":"Reading MMCA.ADC/Source/Services/MMCA.ADC.Conference.Service/Program.cs top to bottom shows the whole chapter cooperating. The host binds and validates ApplicationSettings and…","i":"MMCA.Common.Application.Interfaces.Infrastructure MMCA.Common.Application.Users.UseCases.DeleteUser UserDataExportSectionDefaults.UnavailableReason TenancySettingsValidator.ConnectionStringFor DefaultEntityConfigurationAssemblyProvider ArgumentException.ThrowIfNullOrWhiteSpace InProcessDistributedLock.TryAcquireAsync Microsoft.Extensions.DependencyInjection ScheduledJobRunner.ResolveCronExpression UserDataExportSectionResult.Unavailable UserUseCaseLog.ExportSectionUnavailable DbContextFactory.ResolveTenantOverride"},{"u":"/docs/onboarding/group-15-common-ui-framework.html","d":"15. Common UI Framework (MudBlazor components, theme, base pages)","k":"Onboarding Guide","x":"What this chapter covers. MMCA.Common.UI is the Blazor presentation package, and it is one of the two layers (with Grpc) allowed to reference Shared only (see primer §1). It…","i":"Microsoft.AspNetCore.Components.NavigationManager Microsoft.Extensions.Configuration.IConfiguration Microsoft.AspNetCore.Builder.IApplicationBuilder Microsoft.AspNetCore.Components.DynamicComponent MauiBackNavigationBridge.HandleBackPressedAsync Microsoft.AspNetCore.WebUtilities.QueryHelpers WasmTokenStorageService.GetAccessTokenAsync HttpResilienceDefaults.TotalRequestTimeout ArgumentException.ThrowIfNullOrWhiteSpace CultureInfo.DefaultThreadCurrentUICulture ITokenStorageService.GetAccessTokenAsync Microsoft.Extensions.DependencyInjection"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","x":"This chapter covers the hosting boundary: how the distributed MMCA system is composed and run, locally as a single dotnet run, and in Azure Container Apps (ACA) as a set of…","i":"MMCA.Common.Aspire.Hosting AddServiceDefaults MMCA.Common.Aspire MMCA.Common.Shared MMCA.ADC.AppHost Aspire.Hosting dotnet run"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-orchestrator-declaring-the-resource-graph","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The orchestrator: declaring the resource graph","x":"When you run dotnet run --project Source/Hosting/MMCA.ADC.AppHost, Aspire executes Program.cs top to bottom, building a resource model rather than starting anything immediately.…","i":"LoginProtection__MaxRegistrationsPerIpPerHour ConnectionStrings__SQLServerConnectionString Authentication__JwtBearer__Authority WithE2eRegistrationThrottleLift E2E_LIFT_REGISTRATION_THROTTLE E2eRegistrationsPerIpPerHour __SQLServerConnectionString MMCA.Common.Aspire.Hosting DefaultBrokerResourceName E2E_JWT_PRIVATE_KEY_PEM WithSQLServerDataSource E2E_JWT_PUBLIC_KEY_PEM"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#startup-ordering-and-the-grpc-deadlock-avoidance-trick","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Startup ordering and the gRPC deadlock-avoidance trick","x":"Aspire's WaitFor builds a startup dependency graph from the resource model: a service does not start until the resources it waits on report healthy (/health/ready for projects,…","i":"ISessionBookmarkValidationService IBookmarkCountService AddTypedGrpcClient WaitFor"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#the-service-baseline-addservicedefaults","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The service baseline: AddServiceDefaults()","x":"Every running host calls one method first in Program.cs: AddServiceDefaults() from the single framework-grade Extensions in MMCA.Common.Aspire (Level 2,…","i":"EnableMultipleHttp2Connections ConfigureHttpClientDefaults AddDefaultHealthChecks ConfigureOpenTelemetry AddServiceDiscovery AddServiceDefaults AddWarmupReadiness MMCA.Common.Aspire SocketsHttpHandler HttpClient Program.cs TBuilder"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#one-source-of-truth-for-outbound-http-httpresiliencedefaults","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"One source of truth for outbound HTTP: HttpResilienceDefaults","x":"The numbers that pipeline uses are not literals in the Aspire package. They live in HttpResilienceDefaults (Level 0,…","i":"MMCA.Common.Grpc Continuity properties Resilience including Business Concerns lifetime sampling attempt initial request"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#listeners-and-probes-one-kestrel-profile-per-host-shape","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Listeners and probes: one Kestrel profile per host shape","x":"Before a service can answer a health probe it has to be listening on a protocol the prober speaks, and that is not free when the same port serves inbound gRPC.…","i":"redeclareCleartextEndpoint ASPNETCORE_HTTP_PORTS HttpProtocols.Http2 MapDefaultEndpoints BuildListenerPlan HTTP_1_1_REQUIRED Http1AndHttp2 Deployment Protocols deployed profiles httpGet"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#health-checks-liveness-readiness-and-the-optional-tag","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Health checks: liveness, readiness, and the \"optional\" tag","x":"MapDefaultEndpoints() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:329) exposes the three-probe surface the platform reads: /health (every check, for humans and…","i":"AddInfrastructureHealthChecks AddDefaultHealthChecks MapDefaultEndpoints requireSqlServer Observability Operability Deployment optional Optional DevOps Rubric Ready"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#telemetry-what-gets-exported-and-what-it-costs","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Telemetry: what gets exported, and what it costs","x":"ConfigureOpenTelemetry() (MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs:121) wires logging with formatted messages and scopes (:123-127), metrics, and tracing. It…","i":"APPLICATIONINSIGHTS_CONNECTION_STRING ActivityTraceFlags.Recorded OTEL_EXPORTER_OTLP_ENDPOINT AddOpenTelemetryExporters IsInstrumentationDisabled TraceIdRatioBasedSampler MMCA.Common.Idempotency ConfigureOpenTelemetry TryGetTraceSampleRatio MMCA.Common.Scheduler MMCA.Common.Outbox ParentBasedSampler"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#warm-up-defeating-aca-cold-start","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Warm-up: defeating ACA cold-start","x":"The warm-up subsystem exists for one concrete failure mode: the \"first request fails, second succeeds\" pattern on a CPU-throttled idle ACA replica, where lazy initialization…","i":"RequireSuccessStatusCode HealthCheckTags.Ready WebApplicationFactory Interlocked.Exchange RequestVersionPolicy AddServiceDefaults AddWarmupReadiness ApplicationStarted IHttpClientFactory BackgroundService ResolveWarmupPort WithJwksDiscovery"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#configuration-secrets-the-vault-as-one-more-configuration-source","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Configuration secrets: the vault as one more configuration source","x":"Connection strings, RSA signing keys and OAuth client secrets have to reach the process somehow, and KeyVaultConfigurationExtensions (Level 0,…","i":"AddCommonDataProtection DefaultAzureCredential builder.Configuration ConfigurationManager AddServiceDefaults IConfiguration Deployment Security answer DevOps Rubric the"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#security-headers-cors-and-the-shared-key-ring-at-the-host-edge","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"Security headers, CORS, and the shared key ring at the host edge","x":"The last boundary in this group hardens the request and response edge. The shared response-header middleware is defined entirely in…","i":"AddCommonSecurityHeaders UseCommonSecurityHeaders AddCommonDataProtection DefaultAzureCredential AddCommonGatewayCors AddCommonBlazorCsp PermissionsPolicy MMCA.Common.API TryAddSingleton ReferrerPolicy AddCommonCors FrameOptions"},{"u":"/docs/onboarding/group-16-aspire-orchestration.html#how-it-all-fits-at-runtime","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"How it all fits at runtime","x":"Putting the pieces in sequence: the AppHost declares the graph and injects per-service env vars (WithSQLServerDataSource, WithBroker, WithJwksDiscovery, the two E2E helpers, and…","i":"Azure.Extensions.AspNetCore.Configuration.Secrets identityService.WithE2eRegistrationThrottleLift LoginProtection__MaxRegistrationsPerIpPerHour ConnectionStrings__SQLServerConnectionString database.Resource.ConnectionStringExpression ResilienceCircuitBreakerFaultInjectionTests WarmupReadinessHealthCheck.CheckHealthAsync HttpKeepAlivePingPolicy.WithActiveRequests cancellationToken.IsCancellationRequested ConnectionStrings__CosmosConnectionString ConnectionStrings__SqliteConnectionString Microsoft.AspNetCore.Server.Kestrel.Core"},{"u":"/docs/onboarding/group-17-conference-domain.html","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","x":"What this chapter covers. This is the heart of the Atlanta Developers Conference application, the Conference bounded context, the largest and richest domain in MMCA.ADC. It…","i":"AuditableAggregateRootEntity MMCA.ADC.Conference.Shared IdValueGeneratedAttribute INavigationPopulator EntityChangedEvent DomainEntityState TIdentifierType IAuditedEntity IModule TEntity Design Result"},{"u":"/docs/onboarding/group-17-conference-domain.html#two-packages-one-bounded-context","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Two packages, one bounded context","x":"The Conference context spans two of the module's projects, and the split is deliberate Clean Architecture ([Rubric §3, Clean Architecture]). MMCA.ADC.Conference.Domain holds the…","i":"ISessionBookmarkValidationService IEventLiveValidationService MMCA.ADC.Conference.Domain MMCA.ADC.Conference.Shared SessionFeedbackSubmitted SpeakerUnlinkedFromUser EventFeedbackSubmitted SpeakerLinkedToUser MMCA.Common.Domain AssemblyReference ClassReference Architecture"},{"u":"/docs/onboarding/group-17-conference-domain.html#seven-aggregates-and-their-ownership-boundaries","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Seven aggregates and their ownership boundaries","x":"An aggregate is a root entity plus the children it exclusively owns; invariants are enforced inside the boundary, and references across aggregates are by ID, never by object…","i":"AuditableAggregateRootEntity RecordSessionizeRefresh SessionQuestionAnswer SpeakerQuestionAnswer EventQuestionAnswer SessionCategoryItem SpeakerCategoryItem IDENTITY_INSERT TIdentifierType IAuditedEntity QuestionEntity QuestionSource"},{"u":"/docs/onboarding/group-17-conference-domain.html#the-aggregate-shape-taught-once","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"The aggregate shape, taught once","x":"Open any of the roots and you will see the same skeleton; this repetition is the point, and it is what makes the per-type sections that follow read quickly. The shape, using…","i":"Session.SessionQuestionAnswers Event.EventQuestionAnswers IReadOnlyCollection RestoreEventSpeaker isIdValueGenerated _rooms.AsReadOnly Result.Combine Architecture IsCollection base.Delete Performance RestoreRoom"},{"u":"/docs/onboarding/group-17-conference-domain.html#invariants-business-rules-as-testable-units","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Invariants, business rules as testable units","x":"Each aggregate has a co-located static invariant class, EventInvariants (MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Domain/Events/EventInvariants.cs:10),…","i":"System.Net.Mail.MailAddress CategoryInvariants QuestionInvariants SessionInvariants SpeakerInvariants SponsorInvariants CommonInvariants EventInvariants SessionStatuses Result.Combine Decline_Queue Accept_Queue"},{"u":"/docs/onboarding/group-17-conference-domain.html#domain-events-and-the-outbox-spine","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Domain events and the outbox spine","x":"Every state-changing method raises a domain event through the inherited AddDomainEvent(...). The events come in two shapes. The aggregate-level ones, EventChanged,…","i":"SessionCategoryItemChanged SpeakerCategoryItemChanged SessionSpeakerChanged PreviousLinkedUserId CategoryItemChanged EventSpeakerChanged EntityChangedEvent DomainEntityState SaveChangesAsync CategoryChanged QuestionChanged TIdentifierType"},{"u":"/docs/onboarding/group-17-conference-domain.html#the-cross-aggregate-cascade-a-pure-domain-service","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"The cross-aggregate cascade: a pure domain service","x":"One business rule cannot live inside a single aggregate: deleting an Event must also delete every Session belonging to it (BR-127) and every Sponsor sold against it, but sessions…","i":"IEventCascadeDeletionDomainService EventCascadeDeletionDomainService EventId Session Sponsor Design Rubric Event List"},{"u":"/docs/onboarding/group-17-conference-domain.html#read-models-and-the-ai-decision-support-feature","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Read models and the AI decision-support feature","x":"The largest cluster in Conference.Shared is the DTO layer, the wire contracts that decouple the API from the domain entities ([Rubric §9, API & Contract Design]; ADR-001 chose…","i":"RefreshFromSessionizeResultDTO RefreshFromSessionizeCommand SessionSelectionDashboardDTO ScoreEventSessionsResultDTO CategoryGroupDistribution Conference.Infrastructure CategoryItemDistribution SessionQuestionAnswerDTO SpeakerQuestionAnswerDTO SpeakerSessionOverlapDTO CategoryDistributionDTO ConcurrencyTokenRequest"},{"u":"/docs/onboarding/group-17-conference-domain.html#authorization-vocabulary-and-current-event-selection","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Authorization vocabulary and current-event selection","x":"Two more Shared helpers deserve a mention because they encode policy the whole module relies on. ConferencePermissions…","i":"TimeZoneInfo.ConvertTimeToUtc ConferenceReadAudience ConferencePermissions CurrentEventDefaults CurrentEventSelector ContentManagement ContentEditor HasPermission Organizer RoleNames StartDate EventDTO"},{"u":"/docs/onboarding/group-17-conference-domain.html#crossing-the-module-boundary-contracts-stubs-and-integration-events","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"Crossing the module boundary: contracts, stubs, and integration events","x":"Conference does not live alone. Three kinds of connection point join it to other modules, and all live in Conference.Shared so neither side reaches into the other's domain…","i":"DisabledSessionBookmarkValidationService DisabledEventLiveValidationService ISessionBookmarkValidationService IEventLiveValidationService QuestionModerationDefault SessionFeedbackSubmitted SpeakerUnlinkedFromUser Conference.Application EventFeedbackSubmitted BaseIntegrationEvent User.LinkedSpeakerId SpeakerLinkedToUser"},{"u":"/docs/onboarding/group-17-conference-domain.html#end-to-end-one-organizer-action","d":"17. ADC Conference - Domain Model & Module Contracts","k":"Onboarding Guide","t":"End-to-end: one organizer action","x":"To see the chapter cooperate, follow an organizer renaming a room on an event. The application handler loads the Event aggregate (with its Rooms hydrated by the navigation…","i":"CategoryInvariants.EnsureCategoryItemNameIsUnique IEventLiveValidationService.GetEventLiveInfoAsync MMCA.ADC.Conference.Domain.Questions.DomainEvents MMCA.ADC.Conference.Domain.Sessions.DomainEvents MMCA.ADC.Conference.Domain.Speakers.DomainEvents MMCA.ADC.Conference.Domain.Sponsors.DomainEvents IUserSpeakerLinkService.ClearLinkedSpeakerAsync MMCA.ADC.Conference.Domain.Events.DomainEvents SessionInvariants.EnsureAnswerValueIsValid SpeakerInvariants.EnsureAnswerValueIsValid CurrentEventSelector.SelectCurrentOrNext DisabledSessionBookmarkValidationService"},{"u":"/docs/onboarding/group-18-conference-application.html","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","x":"What this chapter covers. This is the application layer of the Conference module, the largest single application assembly in the codebase (this group covers 251 distinct types).…","i":"MMCA.Common.Application ics"},{"u":"/docs/onboarding/group-18-conference-application.html#the-vertical-slice-anatomy-of-a-use-case","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"The vertical-slice anatomy of a use case","x":"Open any feature folder under Sessions/UseCases/, Events/UseCases/, Speakers/UseCases/, Sponsors/UseCases/, Categories/UseCases/, or Questions/UseCases/ and you will find the…","i":"ValidateRoomAssignmentAsync BuildOverlapPredicate EventQuestionAnswers UnprocessableEntity EventSpeakers int.MinValue HandleAsync s.StartsAt DbContext s.EndsAt startsAt Session"},{"u":"/docs/onboarding/group-18-conference-application.html#manual-mapping-validation-rule-fragments-and-authorization-specifications","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Manual mapping, validation rule fragments, and authorization specifications","x":"Three sibling families recur across every aggregate. DTO mappers (SessionDTOMapper, EventDTOMapper, SpeakerDTOMapper, SponsorDTOMapper, RoomDTOMapper, CategoryItemDTOMapper, and…","i":"TimeZoneInfo.FindSystemTimeZoneById s.Event.IsPublished AbstractValidator GetProjectedAsync GetReadRepository Session.EventId SessionSpeaker e.IsPublished EventSpeaker Expression IsEligible StartDate"},{"u":"/docs/onboarding/group-18-conference-application.html#query-services-navigation-populators-and-the-composition-root","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Query services, navigation populators, and the composition root","x":"Read paths do not get bespoke handlers for the common cases; they go through the framework's generic IEntityQueryService , which supplies filtering, sorting, paging, and field…","i":"ScanModuleApplicationServices IServiceCollection ClassReference extension FirstName FullName LastName Question Sponsor"},{"u":"/docs/onboarding/group-18-conference-application.html#event-driven-reactions-domain-and-integration-handlers","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Event-driven reactions: domain and integration handlers","x":"The application layer is also where the module reacts to events. Domain event handlers implement IDomainEventHandler and run in-process after the aggregate's SaveChangesAsync.…","i":"EnsureNotServiceSession SpeakerUnlinkedFromUser EnsureStatusIsEligible User.LinkedSpeakerId SpeakerLinkedToUser GetLiveWindowUtc SaveChangesAsync SessionChanged UserRegistered LogAndRethrow IEventBus Deleted"},{"u":"/docs/onboarding/group-18-conference-application.html#attendee-facing-read-models-calendar-export-and-nownext","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Attendee-facing read models: calendar export and Now/Next","x":"A small cluster of queries serves the public schedule surfaces without going through the generic query service, because their output is not a DTO list. ExportEventCalendarHandler…","i":"CalendarExportMapper.IsExportable DateTimeOffset.UtcNow GetNowNextHandler GetLiveWindowUtc Error.NotFound IsExportable TimeProvider DTSTAMP Result string ics"},{"u":"/docs/onboarding/group-18-conference-application.html#the-sessionize-import-strategy-pattern-orchestration","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"The Sessionize import: Strategy-pattern orchestration","x":"The single most involved use case is importing a conference agenda from Sessionize.com. Sessionize returns one JSON payload covering five interdependent entity families…","i":"ThrowIfCancellationRequested TimeoutRejectedException BrokenCircuitException NotSupportedException RequestIdentityInsert HttpRequestException SaveChangesAsync JsonException TimeProvider Create Update catch"},{"u":"/docs/onboarding/group-18-conference-application.html#decision-support-ai-scoring-and-content-analytics","d":"18. ADC Conference - Application & Use Cases","k":"Onboarding Guide","t":"Decision support: AI scoring and content analytics","x":"The last cluster is session-selection decision support, analytics that help organizers triage proposals. GetSessionSelectionDashboardHandler is a composite query: it validates…","i":"MMCA.ADC.Conference.Application.Events.Sessionize MMCA.ADC.Conference.Application.Events.Validation SessionRoomScheduling.ValidateRoomAssignmentAsync currentUserService.IsPrivilegedConferenceReader eventCascadeDeletionDomainService.CascadeDelete IUserSpeakerLinkService.ClearLinkedSpeakerAsync MMCA.ADC.Conference.Application.Categories.DTOs MMCA.ADC.Engagement.Shared.UserSessionBookmarks PublicSessionStatusSpecification.StatusCriteria SessionSimilarityCalculator.CalculateSimilarity cancellationToken.ThrowIfCancellationRequested EventInvariants.OrganizerContactEmailMaxLength"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","x":"What this chapter covers. This is the adapter layer of the Conference module, the place where the engine-agnostic domain meets concrete technology. Three concerns live here: (1)…","i":"SessionScoringQueue ISessionizeService IAiScoringService Architecture DbContext Rubric Clean DbSet"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#engine-agnostic-entities-engine-chosen-by-the-config-base-class","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Engine-agnostic entities, engine chosen by the config base class","x":"The most important idea in this chapter is one the entities themselves never express: what storage engine each entity uses is decided here, not in the domain. A Conference domain…","i":"EntityTypeConfigurationSQLServer EntityDataSourceRegistry EntityTypeConfiguration DataSource.SQLServer SessionConfiguration SpeakerConfiguration SponsorConfiguration EventConfiguration TIdentifierType UseDataSource Session Speaker"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#each-config-inherits-the-cross-cutting-behavior-then-adds-entity-specifics","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Each config inherits the cross-cutting behavior, then adds entity specifics","x":"Every configuration's Configure method begins with base.Configure(builder) (for example SessionConfiguration.cs:18) and then adds its own mappings. That one base call is where…","i":"SessionQuestionAnswerConfiguration SpeakerQuestionAnswerConfiguration EventQuestionAnswerConfiguration SessionCategoryItemConfiguration SessionInvariants.TitleMaxLength SpeakerCategoryItemConfiguration ConferenceCategoryConfiguration SoftDeleteUniqueIndexConvention SponsorInvariants.NameMaxLength EventInvariants.NameMaxLength EventQuestionAnswer.EventId NullableEmailValueConverter"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#dbsets-the-context-shape-and-how-the-configurations-are-actually-found","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"DbSets, the context shape, and how the configurations are actually found","x":"ModuleApplicationDbContext (MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/ModuleApplicationDbContext.cs:19) is the Conference module's abstract DbContext. It does one…","i":"IEntityTypeConfigurationSQLServer MMCA.ADC.Conference.Service ModuleApplicationDbContext SessionQuestionAnswers SpeakerQuestionAnswer ApplicationDbContext EventQuestionAnswers SessionCategoryItems SpeakerCategoryItems dbo.OutboxMessages SQLServerDbContext SaveChangesAsync"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#seeding-two-real-events-always-sample-data-only-in-dev-and-ci","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Seeding: two real events always, sample data only in dev and CI","x":"ConferenceModuleDbSeeder (MMCA.ADC.Conference.Infrastructure/Persistence/DbContexts/Seeding/ConferenceModuleDbSeeder.cs:24) derives from the framework's DbSeeder and runs after…","i":"ConferenceModuleDbSeeder ConferenceModuleSeeder ManualIdRangeStart QuestionInvariants includeSampleData SessionInvariants ExistsAsync DbSeeder sf1nopko z1ecmzux Migrate"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#the-sessionize-adapter","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"The Sessionize adapter","x":"SessionizeService (MMCA.ADC.Conference.Infrastructure/Services/SessionizeService.cs:10) is a deliberately thin HTTP client: the whole class is one method. Given a Sessionize…","i":"EnsureSuccessStatusCode DependencyInjection SessionizeResponse SessionizeService HttpClient GetAsync code"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#the-anthropic-ai-scoring-adapter","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"The Anthropic AI scoring adapter","x":"AnthropicScoringService (MMCA.ADC.Conference.Infrastructure/Services/AnthropicScoringService.cs:16) is the richer of the two adapters: it scores one session proposal against a…","i":"CultureInfo.InvariantCulture OperationCanceledException AnthropicScoringService AnthropicContentBlock SessionScoringResult AnthropicResponse IAiScoringService AnthropicMessage AnthropicRequest JsonPropertyName AiScoreResponse LoggerMessage"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#scoring-runs-on-a-hosted-drain-guarded-across-replicas","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"Scoring runs on a hosted drain, guarded across replicas","x":"SessionScoringProcessor (MMCA.ADC.Conference.Infrastructure/Services/SessionScoringProcessor.cs:49) is the piece that makes a multi-minute paid AI pass safe to trigger from an…","i":"MMCA.ADC.Conference.Scoring scoring.run.failed.terminal ScoreEventSessionsCommand SessionScoringProcessor queue.MarkCompleted SessionScoringQueue BackgroundService CreateAsyncScope IDistributedLock TryAcquireAsync conferenceApp MarkCompleted"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#di-wiring-and-a-deliberate-resilience-override","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"DI wiring and a deliberate resilience override","x":"DependencyInjection (MMCA.ADC.Conference.Infrastructure/DependencyInjection.cs:11) is a single extension(IServiceCollection) block (the codebase's standard DI-registration idiom,…","i":"AddModuleConferenceInfrastructure RemoveAllResilienceHandlers StandardResilienceHandler DependencyInjection HttpClient.Timeout IServiceCollection extension"},{"u":"/docs/onboarding/group-19-conference-infrastructure.html#how-it-fits-together-at-runtime","d":"19. ADC Conference - Infrastructure & Persistence","k":"Onboarding Guide","t":"How it fits together at runtime","x":"Three flows tie the chapter together. Persistence flow: a Conference command handler mutates an aggregate and the unit of work saves; that resolves the concrete…","i":"Microsoft.EntityFrameworkCore.Metadata.Builders System.Text.Json.Serialization.JsonPropertyName CategoryInvariants.CategoryItemNameMaxLength MMCA.ADC.Conference.Infrastructure.Services AnthropicScoringService.ScoreSessionAsync AnthropicScoringService.ParseSingleScore Microsoft.Extensions.DependencyInjection MMCA.ADC.Migrations.SqlServer.Conference ApplyConfigurationsForEntitiesInContext SessionInvariants.AnswerValueMaxLength SpeakerInvariants.AnswerValueMaxLength QuestionInvariants.ManualIdRangeStart"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","x":"This chapter is the edge of the Conference bounded context, the layer that turns the rich Conference domain (G17) and its CQRS slices (G18) into a running HTTP + gRPC surface,…","i":"MMCA.ADC.Conference.Contracts MMCA.ADC.Conference.Service MMCA.ADC.Conference.API ConferenceModuleSeeder ConferenceModule Microservices Readiness Contract Vertical IModule Design Rubric"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-controller-hierarchy-almost-everything-is-inherited","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The controller hierarchy, almost everything is inherited","x":"The Conference API exposes sixteen controllers, and the striking thing about them is how little code each carries. They split into three structural families, all built on the…","i":"sessionquestionanswerscontroller conferencecategoriescontroller ConferenceCategoriesController eventquestionanswerscontroller sessioncategoryitemscontroller speakercategoryitemscontroller SessionSelectionController sessionspeakerscontroller categoryitemscontroller eventspeakerscontroller PagedCollectionResult ServiceInfoController"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#authorization-at-the-edge-three-shapes-not-one","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Authorization at the edge, three shapes not one","x":"Authorization is capability-based by default but not uniform, and the differences are the interesting part. Most write-bearing controllers carry a class-level…","i":"AuthorizationPolicies.RequireAuthenticated ConferencePermissions.SpeakersManage SessionQuestionAnswersController EventQuestionAnswersController CurrentUserServiceExtensions IsPrivilegedConferenceReader AddModuleConferenceAPI ConferenceReadAudience HasPermissionAttribute SessionSelectionManage ConferencePermissions ICurrentUserService"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-request-records-the-inbound-write-shapes","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The request records, the inbound write shapes","x":"Several controllers declare small record class request types alongside themselves, co-located in the same file: AddRoomRequest/UpdateRoomRequest…","i":"updatesessionquestionanswerrequest updateeventquestionanswerrequest addsessionquestionanswerrequest addeventquestionanswerrequest addsessioncategoryitemrequest addspeakercategoryitemrequest updatecategoryitemrequest addsessionspeakerrequest addcategoryitemrequest addeventspeakerrequest SessionCreateRequest SessionsController"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#where-the-generic-shape-gives-way-filtering-warnings-and-calendars","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Where the generic shape gives way: filtering, warnings, and calendars","x":"SessionsController is the best illustration of how a controller earns its overrides. Every read action is [AllowAnonymous] and [OutputCache(PolicyName = \"SessionsCache\")]…","i":"BuildPublicSessionSpecificationAsync BuildPagedSessionSpecificationAsync GetSessionsBySpeakerFilterQuery GetPublicSessionFilterQuery GetPublicSponsorFilterQuery PublishedEventSpecification ExportSessionCalendarQuery EvictSessionsCacheAsync UpdateSponsorCommand HasDateRangeWarning IdempotentAttribute IOutputCacheFeature"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#two-more-deviations-versioning-and-decision-support","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Two more deviations, versioning and decision support","x":"ServiceInfoController exists to prove the API-versioning machinery works beyond a single version ([Rubric §9, API & Contract Design]). It is a one-member shell over Common's…","i":"ConferencePermissions.SessionSelectionManage SessionScoringEnqueueResult SessionSelectionController ServiceInfoControllerBase SessionScoringProcessor ServiceInfoController ISessionScoringQueue minimumSimilarity ConferenceCache AllowAnonymous AlreadyPending HandleFailure"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-module-entry-point-and-seeder-how-conference-plugs-in","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The module entry point and seeder, how Conference plugs in","x":"ConferenceModule is the Conference implementation of IModule. It is tiny by design: Register(...) calls the DependencyInjection extension's AddConferenceModule(...)…","i":"DisabledSessionBookmarkValidationService DisabledEventLiveValidationService ISessionBookmarkValidationService IEventLiveValidationService ConferenceErrorResources ConferenceModuleDbSeeder ConferenceModuleSeeder AuthorizationPolicies RegisterDisabledStubs RequireAuthenticated AddConferenceModule DependencyInjection"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-grpc-edge-conference-as-both-server-and-client","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The gRPC edge, Conference as both server and client","x":"When Conference is extracted into its own process, two of its in-process collaborations must cross a network boundary, and both are handled by the G13 transport boundary (Result…","i":"SessionBookmarkValidationServiceGrpcAdapter AddConferenceEventLiveValidationClient eventlivevalidationservicegrpcadapter AddConferenceSessionValidationClient ISessionBookmarkValidationService AddEngagementBookmarkCountClient ModuleLoader.DiscoverAndRegister eventlivevalidationgrpcservice GrpcResultExceptionInterceptor MMCA.ADC.Conference.Contracts MMCA.ADC.Conference.Service SessionBookmarksGrpcService"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-service-host-kestrel-first-and-why","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The service host: Kestrel first, and why","x":"The MMCA.ADC.Conference.Service Program.cs boots only the Conference module (Modules:Conference:Enabled=true). Kestrel is configured before anything else, and the whole of it is…","i":"builder.ConfigureEndpointsWithHealthProbe MMCA.ADC.Conference.Scoring MMCA.ADC.Conference.Service KestrelEndpointExtensions HttpProtocols.Http2 MapDefaultEndpoints HTTP_1_1_REQUIRED Http1AndHttp2 Program.cs UseSerilog httpGet GOAWAY"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#output-caching-and-warm-up-the-two-performance-extension-points","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"Output caching and warm-up, the two performance extension points","x":"Output caching is where this host carries the most bespoke configuration (Program.cs:191-255). The base policy is deny-by-default NoCache (Program.cs:198), so only explicitly…","i":"ConferenceReadAudience.PrivilegedRoles PublicEndpointOutputCachePolicy SelfHttpOutputCacheWarmupTask DbUpdateConcurrencyException SessionSelectionController ConferenceErrorResources AddPublicEndpointPolicy SelfHttpWarmupTaskBase ConferencePublicCache BookmarkCountsCache AddErrorResources Event.Name.Empty"},{"u":"/docs/onboarding/group-20-conference-api-grpc.html#the-runtime-picture-one-host-two-transports","d":"20. ADC Conference - API, gRPC Contracts & Service Host","k":"Onboarding Guide","t":"The runtime picture, one host, two transports","x":"After module discovery (Program.cs:313-319) the host wires the Engagement gRPC client (Program.cs:329), the broker (AddBrokerMessaging registering the UserRegistered…","i":"MMCA.Common.Application.Interfaces.Infrastructure currentUserService.IsPrivilegedConferenceReader ConferencePermissions.SessionSelectionManage AuthorizationPolicies.RequireAuthenticated ConferenceReadAudience.PrivilegedRoles.Any builder.ConfigureEndpointsWithHealthProbe DisabledSessionBookmarkValidationService MMCA.ADC.Conference.Shared.Authorization ConferencePermissions.ContentManagement GetPublicSessionCategoryItemFilterQuery GetPublicSpeakerCategoryItemFilterQuery AddConferenceEventLiveValidationClient"},{"u":"/docs/onboarding/group-21-conference-ui.html","d":"21. ADC Conference - UI","k":"Onboarding Guide","x":"What this chapter covers. This is the consumer half of the \"write-once UI, render everywhere\" story (primer §2): the Blazor pages and per-page HTTP services that turn the…","i":"MMCA.ADC.Conference.UI Architecture Responsive Component Design Rubric"},{"u":"/docs/onboarding/group-21-conference-ui.html#the-layering-inside-the-ui-a-page-never-touches-httpclient","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The layering inside the UI: a page never touches HttpClient","x":"Each page is a .razor + .razor.cs code-behind pair that depends only on a UI service interface, never on HttpClient and never on the API's internals. The eight CRUD-shaped…","i":"IConferenceCategoryUIService RefreshFromSessionizeAsync ConferenceCategoryService EnsureSuccessStatusCode ICategoryItemUIService ServiceExceptionHelper PagedCollectionResult SponsorIdentifierType CategoryItemService IQuestionUIService EntityServiceBase ISessionUIService"},{"u":"/docs/onboarding/group-21-conference-ui.html#the-list-pages-derive-from-datagridlistpagebasetdto-get-everything-for-free","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The list pages: derive from DataGridListPageBase, get everything for free","x":"Ten list screens, the organizer EventList, SessionList, SpeakerList, ConferenceCategoryList, QuestionList, RoomList, SponsorList, and the public PublicEventList,…","i":"MobileInfiniteScrollList ConferenceCategoryList DataGridListPageBase PublicSessionList PublicSpeakerList PublicSponsorList FetchMobilePage ListPageActions PublicEventList LoadServerData RestoreFilters GetPagedAsync"},{"u":"/docs/onboarding/group-21-conference-ui.html#container-and-presentational-split","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Container and presentational split","x":"The behaviour-heavy screens do not keep everything in one code-behind: the page stays the container (data fetching, filter and paging state, service calls) and hands rendering to…","i":"SessionSelectionSpeakerOverlap PublicSessionListFilterBar SpeakerCategoryItemsPanel SessionSelectionAiScores SessionSelectionDisplay PublicSessionListView PublicSessionList Architecture ReloadAsync Changed Testing Rubric"},{"u":"/docs/onboarding/group-21-conference-ui.html#child-and-join-entities-a-thin-postdelete-base","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Child-and-join entities: a thin POST/DELETE base","x":"Sessions, speakers, and events own join relationships (a speaker added to a session, a category item to a speaker) that the generic CRUD base cannot model, because the write…","i":"ISessionCategoryItemUIService ISpeakerCategoryItemUIService SessionCategoryItemService SpeakerCategoryItemService ISessionSpeakerUIService ChildEntityServiceBase IEventSpeakerUIService SessionSpeakerService EventSpeakerService MMCA.Common.UI DeleteAsync Validation"},{"u":"/docs/onboarding/group-21-conference-ui.html#display-enrichment-lookups-the-getall-vs-getbyid-populator-gap-worked-around-in-the-ui","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Display-enrichment lookups: the GetAll-vs-GetById populator gap, worked around in the UI","x":"Because the API's list endpoints do not always populate every cross-entity navigation, several pages need a cheap id-to-name map to render speaker names beside a session or an…","i":"ICategoryItemLookupService CategoryItemLookupService ISpeakerLookupService SpeakerLookupService SponsorshipPacketUrl IEventLookupService EventLookupService PublicSessionList CategoryItemInfo SessionSpeakers SpeakerInfo Dictionary"},{"u":"/docs/onboarding/group-21-conference-ui.html#three-feature-areas-that-go-beyond-crud","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Three feature areas that go beyond CRUD","x":"First, the speaker self-service dashboard: SpeakerDashboard is gated on the speakerid JWT claim (read from the cascaded authentication state and parsed as a Guid,…","i":"IOrganizerSessionFeedbackUIService IOrganizerEventFeedbackUIService OrganizerSessionFeedbackService OrganizerEventFeedbackService ISpeakerDashboardUIService AuthenticatedServiceBase OrganizerSessionFeedback SpeakerDashboardService OrganizerEventFeedback ServiceExceptionHelper IPublicLinkBuilder SpeakerDashboard"},{"u":"/docs/onboarding/group-21-conference-ui.html#session-selection-decision-support-the-asynchronous-edge","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Session-selection decision support, the asynchronous edge","x":"The most behaviour-rich page is the organizer-only SessionSelectionDashboard, which renders category distribution, speaker overlap, locality breakdown, and AI content-similarity…","i":"SessionSelectionFilterOptions ScoreEventSessionsResultDTO ISessionSelectionUIService ScoreEventSessionsCommand SessionSelectionDashboard SessionSelectionService CurrentEventSelector ScorePollTracker ScorePollSignal SessionsScored Resilience inherited"},{"u":"/docs/onboarding/group-21-conference-ui.html#public-versus-authenticated-rendering-and-the-device-capability-path","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Public versus authenticated rendering, and the device-capability path","x":"A recurring [Rubric §11, Security] pattern: the same conference entity is exposed through two page families. The public family (PublicEventList/PublicEventDetail,…","i":"IServiceProvider.GetService IConnectivityStatusService ISessionBookmarkUIService ConferenceReadAudience IHapticFeedbackService CurrentEventDefaults PublicSessionDetail PublicSpeakerDetail IScreenshotService CachedSessionPage PublicEventDetail PublicSessionList"},{"u":"/docs/onboarding/group-21-conference-ui.html#sponsors-a-feature-area-in-miniature","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Sponsors, a feature area in miniature","x":"The sponsor surface is worth reading as a compact tour of every pattern above, because it is the newest and touches all of them. Organizers manage the roster through SponsorList…","i":"ADCSponsorCollectionResult DataGridListPageBase EventLookupService PublicSponsorList ADCSponsorInfo Enum.GetValues SponsorCreate SponsorDetail SponsorList SponsorTier SponsorDTO ADCHome"},{"u":"/docs/onboarding/group-21-conference-ui.html#the-landing-page","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"The landing page","x":"ADCHome is the conference front door, shared by the web and MAUI heads; both serve the editorial images from their own site root today, so neither overrides the ImageBasePath…","i":"CurrentEventSelector ADCCollectionResult ConferenceTrackInfo KeynoteSpeakerInfo ImageBasePath ADCEventInfo Performance EventPhase Rendering ADCHome Rubric Timer"},{"u":"/docs/onboarding/group-21-conference-ui.html#routes-and-navigation","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"Routes and navigation","x":"All paths are centralized in ConferenceRoutePaths, a static catalogue of literal routes and id-parameterized builder methods (EventDetails(id), PublicSessionDetails(id),…","i":"ConferenceRoutePaths.EventDetails NavigationManager.NavigateTo NavigationPublicLinkBuilder EventFeedbackOrganizer ConferenceRoutePaths Internationalization PublicSessionDetails IPublicLinkBuilder IStringLocalizer SponsorVisitLink RoomCheckInLink SponsorDetails"},{"u":"/docs/onboarding/group-21-conference-ui.html#how-it-all-plugs-into-the-shell","d":"21. ADC Conference - UI","k":"Onboarding Guide","t":"How it all plugs into the shell","x":"Two registration types wire the area in. ConferenceUIModule implements Common's IUIModule (the front-end counterpart of the IModule back-end contract): it declares the module's…","i":"MMCA.ADC.Conference.UI.Pages.ConferenceCategory ConferenceRoutePaths.SessionSelectionDashboard MMCA.ADC.Conference.UI.Pages.SessionSelection ListPageActions.DeleteWithConfirmationAsync ArgumentException.ThrowIfNullOrWhiteSpace CurrentEventDefaults.SelectCurrentOrNext CurrentEventSelector.SelectCurrentOrNext DashboardService.GetSpeakerSessionsAsync ListPageActions.ReloadActiveLayoutAsync MMCA.ADC.Conference.UI.Pages.Feedback MMCA.ADC.Conference.UI.Pages.Question MMCA.ADC.Conference.UI.Pages.Session"},{"u":"/docs/onboarding/group-22-engagement-module.html","d":"22. ADC Engagement Module (Session Bookmarks)","k":"Onboarding Guide","x":"What this group covers. Engagement is the ADC bounded context that holds everything an attendee does at the conference beyond browsing the catalog. Four capability families live…","i":"MMCA.ADC.Engagement.Application.CheckIns.Services BookmarkCountService.BookmarkCountServiceClient MMCA.ADC.Engagement.Application.Points.Services MMCA.ADC.Engagement.Domain.UserSessionBookmarks MMCA.ADC.Engagement.Shared.UserSessionBookmarks MMCA.ADC.Engagement.Domain.Points.DomainEvents BookmarkCountService.BookmarkCountServiceBase MMCA.ADC.Engagement.Application.CheckIns.DTOs assemblyProvider.GetConfigurationAssemblies AuthorizationPolicies.RequireAuthenticated CheckInsController.GetAttendanceStatsAsync SessionFeedbackSubmittedPointsHandlerTests"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","x":"What this chapter covers. This is the conference-day layer of the Engagement bounded context: the features that only matter while an event is actually happening in the room.…","i":"SessionQuestion PresenterView HappeningNow SessionLive LivePoll"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-two-aggregates-and-their-invariants","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The two aggregates and their invariants","x":"Both aggregates are sealed AuditableAggregateRootEntity subclasses that follow the framework's factory-plus-Result discipline (primer §2). LivePoll…","i":"AuditableAggregateRootEntity SessionQuestion.Create SessionQuestionChanged SessionQuestionUpvote ToggleUpvoteHandler LivePollInvariants DomainEntityState LiveWindowEndUtc BaseDomainEvent CanAcceptUpvote CastVoteHandler LivePollChanged"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-write-path-and-where-the-realtime-broadcast-actually-happens","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The write path, and where the realtime broadcast actually happens","x":"Each operation is a vertical slice under Application/{LivePollsSessionQuestions}/UseCases/{Op}/, and every command handler shares the first two beats: mutate the aggregate…","i":"SessionQuestionUpvoteChangedHandler ILiveChannelPublishQueue.Enqueue SessionQuestionUpvoteChanged LiveChannelPublishWorkItem LivePollVoteChangedHandler ILiveChannelPublishQueue ModerateQuestionHandler SessionQuestionChannel CreateLivePollHandler LivePollClosedPayload SubmitQuestionHandler CloseLivePollHandler"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html#one-websocket-one-publisher-port-and-a-cross-service-ingress","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"One WebSocket, one publisher port, and a cross-service ingress","x":"The transport itself is framework-owned (ADR-039, Group 10). The single NotificationHub carries both durable notifications and channel events on one connection, and the…","i":"LiveChannelPublisherGrpcAdapter LiveChannelPublishProcessor SignalRLiveChannelPublisher RendererInfo.IsInteractive NullLiveChannelPublisher IPushNotificationSender LiveChannelGrpcService NotificationHubService ILiveChannelPublisher OnAfterRenderAsync OnInitializedAsync LeaveChannelAsync"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html#the-read-path-and-how-the-ui-reacts","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"The read path and how the UI reacts","x":"Reads do not go through the generic entity-query machinery; the live views need shaped projections. LivePollResultsBuilder…","i":"LivePollNavigationPopulator SessionLiveModerationPanel SessionQuestionViewBuilder SessionLiveQuestionPanel LivePollResultsBuilder ISessionLiveUIService LivePollConfiguration CurrentEventSelector SessionLivePollPanel SessionLiveUIService GetOpenPollsHandler LivePollDTOMapper"},{"u":"/docs/onboarding/group-23-engagement-live-layer.html#authorization-feature-gating-and-the-cross-service-dependency-on-conference","d":"23. ADC Engagement Live Layer (Real-Time Polls & Session Q&A)","k":"Onboarding Guide","t":"Authorization, feature gating, and the cross-service dependency on Conference","x":"Both controllers, LivePollsController (MMCA.ADC.Engagement.API/Controllers/LivePollsController.cs:42) and SessionQuestionsController…","i":"MMCA.ADC.Engagement.Domain.LivePolls.DomainEvents MMCA.ADC.Engagement.Application.LivePolls.DTOs SessionQuestionChannel.QuestionUpvoteChanged MMCA.ADC.Engagement.Domain.SessionQuestions MMCA.ADC.Engagement.Shared.SessionQuestions AuthorizationPolicies.RequireAuthenticated LivePollInvariants.EnsureOptionTextIsValid PushNotificationSettings.ChannelKeyPattern MMCA.ADC.Engagement.UI.Pages.HappeningNow SessionQuestionPendingCountChangedPayload SessionQuestionUpvote.QuestionId.Required CurrentEventSelector.SelectCurrentOrNext"},{"u":"/docs/onboarding/group-24-identity-module.html","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","x":"What this chapter covers. This is the Identity bounded context of MMCA.ADC, the module that owns who a person is across every ADC surface: web, WebAssembly, and MAUI. It is a…","i":"GetUserPreferencesHandlerBase AuditableAggregateRootEntity AuthenticationServiceBase ChangePasswordHandlerBase ExportUserDataHandlerBase HasPermissionAttribute DeleteUserHandlerBase BaseIntegrationEvent SoftDeletedUserCache TIdentifierType IAnonymizable PiiAttribute"},{"u":"/docs/onboarding/group-24-identity-module.html#projects-one-bounded-context","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Projects, one bounded context","x":"The module is split along the standard Clean Architecture layering ([Rubric §3, Clean Architecture]), each project pinned by a trivial AssemblyReference / ClassReference anchor…","i":"MMCA.ADC.Identity.Infrastructure MMCA.ADC.Identity.Application ScanModuleApplicationServices MaxRegistrationsPerIpPerHour MMCA.ADC.Identity.Contracts ModuleApplicationDbContext MMCA.ADC.Identity.Service MMCA.ADC.Identity.Domain MMCA.ADC.Identity.Shared SoftDeletedUserValidator IdentityErrorResources IdentityModuleDbSeeder"},{"u":"/docs/onboarding/group-24-identity-module.html#the-user-aggregate-credentials-profile-and-cross-context-links-in-one-root","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The User aggregate: credentials, profile, and cross-context links in one root","x":"User (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Domain/Users/User.cs:33) is the only aggregate root in the module, and it carries more responsibility than most: it is…","i":"RegisterRequestValidator IPasswordChangeableUser DeviceFieldMaxLength UserPasswordChanged FirstNameMaxLength RefreshTokenExpiry RevokeRefreshToken UpdateRefreshToken LastNameMaxLength UpdatePreferences UserConfiguration CommonInvariants"},{"u":"/docs/onboarding/group-24-identity-module.html#authentication-a-thin-subclass-over-the-shared-engine","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Authentication: a thin subclass over the shared engine","x":"The login / registration / refresh / revocation workflow is not re-implemented here. It lives in AuthenticationServiceBase (G08), which owns the validate-first flow, the lockout…","i":"HttpContextExternalLoginEmailVerifier UnitOfWork.ExecuteInTransactionAsync CreateChangePreferencesCommand Auth.ExternalEmailNotVerified IdentityPermissions.UsersRead UserAccountAuthControllerBase CreateChangePasswordCommand IExternalLoginEmailVerifier AuthenticationServiceBase GetUserPreferencesHandler TChangePreferencesCommand ChangePreferencesCommand"},{"u":"/docs/onboarding/group-24-identity-module.html#the-privacy-pair-export-and-erasure","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The privacy pair: export and erasure","x":"Two use cases make this module the codebase's clearest [Rubric §30, Compliance / Privacy / Data Governance] story, and both are now thin ADC specializations of a G14 base. The…","i":"UserDataExportEngagementSectionDTO NotificationUserDataExportSection EngagementUserDataExportSection IUserNotificationExportService IUserEngagementExportService BuildSubjectSnapshotAsync ExportUserDataHandlerBase UserDataExportSectionDTO UserDataExportSubjectDTO IUserDataExportSection OnAfterSoftDeleteAsync DeleteUserHandlerBase"},{"u":"/docs/onboarding/group-24-identity-module.html#avatars-the-third-mutating-slice","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Avatars: the third mutating slice","x":"The avatar trio is a small but complete example of a file-handling slice ([Rubric §11, Security] at the content boundary, ADR-045). UsersController caps the multipart upload at 2…","i":"RemoveUserAvatarHandler Avatar.InvalidUpload GetUserAvatarHandler SetUserAvatarHandler IFileStorageService ImageContentSniffer RequestSizeLimit IImageProcessor UsersController MaxAvatarBytes"},{"u":"/docs/onboarding/group-24-identity-module.html#persistence-seeding-and-the-disabled-stub","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Persistence, seeding, and the disabled stub","x":"ModuleApplicationDbContext (ModuleApplicationDbContext.cs:15) is the abstract, engine-agnostic context declaring the single Users set (:22); the concrete per-engine class…","i":"EntityTypeConfigurationSQLServer DisabledAttendeeQueryService IdentityModuleDbSeederBase ModuleApplicationDbContext IdentityModuleDbSeeder RegisterDisabledStubs ApplicationDbContext IdentityModuleSeeder EmailValueConverter dbo.OutboxMessages SQLServerDbContext UserConfiguration"},{"u":"/docs/onboarding/group-24-identity-module.html#crossing-the-service-boundary-grpc-and-integration-events","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"Crossing the service boundary: gRPC and integration events","x":"Identity talks to its peers two ways, and both live in Shared and Contracts so neither side reaches into the other's domain ([Rubric §7, Microservices Readiness]). Synchronously,…","i":"ConfigureEndpointsWithHealthProbe ModuleLoader.DiscoverAndRegister AttendeeQueryServiceGrpcAdapter SpeakerUnlinkedFromUserHandler SpeakerLinkedToUserHandler AddIdentityAttendeeClient KestrelEndpointExtensions RequireSuccessStatusCode SpeakerUnlinkedFromUser SelfHttpWarmupTaskBase AuthenticationService IAttendeeQueryService"},{"u":"/docs/onboarding/group-24-identity-module.html#the-ui-edge","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"The UI edge","x":"The Blazor surface is registered as an IdentityUIModule (MMCA.ADC.Identity.UI/IdentityUIModule.cs:13), an IUIModule descriptor that contributes two NavItems as resource keys, \"My…","i":"AuthenticatedServiceBase MobileInfiniteScrollList RetryPolicy.ExecuteAsync MMCA.Common.Testing.E2E DataGridListPageBase DependencyInjection IMediaPickerService IdentityRoutePaths IdentityUIModule ListPageActions IUserUIService UserListDTO"},{"u":"/docs/onboarding/group-24-identity-module.html#end-to-end-one-registration","d":"24. ADC Identity Module (Users, Profiles, GDPR Export/Erasure)","k":"Onboarding Guide","t":"End-to-end: one registration","x":"To see the chapter cooperate, follow a new attendee signing up. AuthController receives the register POST, captures the client IP for BR-213 rate limiting (AuthController.cs:57),…","i":"MMCA.ADC.Identity.Shared.Users.IntegrationEvents AttendeeQueryService.AttendeeQueryServiceClient System.Diagnostics.CodeAnalysis.SuppressMessage MMCA.ADC.Identity.Application.Users.Validation AttendeeQueryService.AttendeeQueryServiceBase AuthStateProvider.GetAuthenticationStateAsync LoginProtection__MaxRegistrationsPerIpPerHour ServiceCollectionDescriptorExtensions.Replace ListPageActions.DeleteWithConfirmationAsync MMCA.ADC.Identity.Domain.Users.DomainEvents ExternalAuthExtensions.ExternalLoginScheme System.Collections.Frozen.FrozenDictionary"},{"u":"/docs/onboarding/group-25-adc-host-composition.html","d":"25. ADC Application Host, UI Shell & Cross-Module Composition","k":"Onboarding Guide","x":"What this chapter covers. Every ADC module described so far, Conference, Engagement, Identity, Notification, is consumed somewhere. This chapter is that somewhere: the client…","i":"Microsoft.Extensions.Configuration.IConfiguration ArgumentException.ThrowIfNullOrWhiteSpace NowNextWidgetProvider.FetchSnapshotAsync MMCA.Common.UI.Components.Capabilities IPlatformApplication.Current.Services UIModuleConfiguration.IsModuleEnabled RemoteCertificateValidationCallback SessionCookieAuthenticationHandler EngagementRoutePaths.HappeningNow NowNextWidgetProvider.BuildViews System.Resources.ResourceManager WebAuthenticatorCallbackActivity"},{"u":"/docs/onboarding/group-26-device-capability-layer.html","d":"26. Device Capability Abstraction Layer (Native Contracts, MAUI, Browser & Fallback Adapters)","k":"Onboarding Guide","x":"What this group covers. A single Blazor UI codebase in MMCA.Common.UI renders on three very different heads: Blazor Server (server-side render plus interactive Server circuits),…","i":"MauiBackNavigationBridge.HandleBackPressedAsync MMCA.Common.UI.Services.Capabilities.Fallbacks MMCA.Common.UI.Services.Capabilities.Browser builder.Services.AddMauiDeviceCapabilities WebAuthenticator.Default.AuthenticateAsync ArgumentException.ThrowIfNullOrWhiteSpace Battery.Default.EnergySaverStatusChanged CommunityToolkit.Maui.Media.SpeechToText Connectivity.Current.ConnectivityChanged CultureInfo.DefaultThreadCurrentCulture ILocalNotificationService.ScheduleAsync IPushDeviceTokenProvider.GetTokenAsync"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","x":"What this group covers. Everything the codebase uses to prove itself: the four reusable test-support packages that ship out of MMCA.Common/Source/Hosting (MMCA.Common.Testing,…","i":"ServiceInfoVersioningContractTestsBase SqlServerIntegrationTestFixtureBase MMCA.Common.Testing.Architecture ProductionHostApplicationFactory AccessibilityViolationException DecoratorPipelineOrderTestsBase FeatureManagementTestExtensions ProblemDetailsContractTestsBase CapturingHttpMessageHandler CrossEntityNavigationFinder RouteAuthorizationTestsBase BunitInteractionExtensions"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#integration-tests-a-real-host-a-throwaway-database-a-per-test-reset","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Integration tests: a real host, a throwaway database, a per-test reset","x":"The integration tier boots the actual application, not a mock of it. The abstraction at its center is IIntegrationTestFixture (MMCA.Common.Testing/IIntegrationTestFixture.cs:8):…","i":"SqlServerIntegrationTestFixtureBase ProductionHostApplicationFactory FeatureManagementTestExtensions appsettings.Development.json SqlBaseEnvironmentVariable ConfigureTestFeatureFlags IEntityDataSourceRegistry CrossServiceFixtureBase IIntegrationTestFixture CrossServiceDataSource __EFMigrationsHistory WebApplicationFactory"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#architecture-fitness-functions-rules-that-gate-the-build","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Architecture fitness functions: rules that gate the build","x":"The layering and DDD conventions this codebase commits to are not left to code review, they are executed as tests. The reusable rule library lives in…","i":"ConstructorDependencyCountTestsBase IntegrationEventContractTestsBase MMCA.Common.Testing.Architecture ObservabilityConventionTestsBase AggregateRootsHaveResultFactory MicroserviceExtractionTestsBase RawQueryableConventionTestsBase ArchitectureRules.Entities.cs AggregateConventionTestsBase CrossEntityNavigationFinder DomainExposesAggregateRoots DomainFactoriesReturnResult"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#component-tests-real-mudblazor-faked-edges","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Component tests: real MudBlazor, faked edges","x":"The bUnit tier renders a single Blazor component in-process with its real dependencies but stubbed network and auth. BunitComponentTestBase…","i":"IsAuthenticatedAuthorizationService AuthenticationStateProvider CapturingHttpMessageHandler BunitInteractionExtensions StubTokenStorageService BunitComponentTestBase FreshApiClientFactory MarkupSnapshotResult UiHttpServiceHarness AuthenticationState HttpMessageHandler IRenderedComponent"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#end-to-end-tests-a-real-browser-accessibility-and-performance-as-gates","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"End-to-end tests: a real browser, accessibility and performance as gates","x":"The E2E tier drives a real browser through Playwright. PlaywrightFixture (MMCA.Common.Testing.E2E/Infrastructure/PlaywrightFixture.cs:6) is an xUnit collection fixture (its…","i":"AssertNoAccessibilityViolationsAsync AccessibilityViolationException Wcag21AaExceptMudPagerCombobox ProfileManagementTestsBase GotoAndWaitForBlazorAsync UserRegistrationTestsBase UserPreferencesTestsBase ClickAndWaitForUrlAsync window.Blazor._internal AuthorizationTestsBase WaitForAuthResultAsync AuthenticatedUserPath"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#the-gallery-harness","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"The Gallery harness","x":"Component and E2E coverage of MMCA.Common's own UI needs a page to render, but the framework is not a runnable app. MMCA.Common.UI.Gallery is a deliberately backend-less Blazor…","i":"GalleryAuthenticationStateProvider GalleryFakeAuthenticationHandler StubNotificationInboxUIService StubPushNotificationUIService MMCA.Common.UI.E2E.Tests NullTokenStorageService MMCA.Common.UI.Gallery MapRazorComponents NullTokenRefresher NoOpAuthUIService MMCA.Common.slnx GalleryUIModule"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#contract-pipeline-and-benchmark-bases","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Contract, pipeline, and benchmark bases","x":"The last family pins guarantees that live in the composition of the stack rather than in any one type, and it is the subject of ADR-058: these suites ship in MMCA.Common.Testing…","i":"Application_ShouldNotDependOn_EntityFrameworkCore Controllers_ShouldNotDependOn_EntityFrameworkCore DataSubject_DeclaresPii_SoTheContractIsNotVacuous Module_ShouldDeclare_ExpectedRequiresDependencies PiiRedactor_LeaksNoClearTextPii_ToLogsOrTelemetry CultureSwitch_ToSpanish_ShouldLocalizeAndPersist EveryRunbookAlertSection_MapsToAProvisionedAlert MobileViewport_CultureAndTheme_ShouldBeReachable ModuleShared_ShouldNotDependOn_OwnInternalLayers OpenApiDocument_DescribesEveryCorePublicResource Register_WithMismatchedPasswords_ShouldShowError RegisterPage_ShouldHaveNoAccessibilityViolations"},{"u":"/docs/onboarding/group-27-testing-infrastructure.html#per-project-test-rollup","d":"27. Testing & Quality Infrastructure","k":"Onboarding Guide","t":"Per-project test rollup","x":"This guide treats tests as grouped, not sectioned per [Fact] (the logged exception in the charter): the reusable test bases, the shared architecture-fitness library and its…","i":"MMCA.ADC.ServiceBusEmulator.IntegrationTests CachingDecoratorConstructorSelectionTests MMCA.ADC.Conference.Infrastructure.Tests MMCA.ADC.Engagement.Infrastructure.Tests MMCA.ADC.Notification.Application.Tests EntityServiceBaseIdempotencyRetryTests MMCA.ADC.CrossService.IntegrationTests MMCA.ADC.Identity.Infrastructure.Tests MMCA.ADC.Notification.IntegrationTests MMCA.Common.Infrastructure.Redis.Tests UserNotificationExportGrpcServiceTests ApplicationDbContextTenantFilterTests"},{"u":"/docs/onboarding/devops-aspire.html","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","x":"This chapter teaches how the MMCA.ADC system goes from a single dotnet run on your workstation to a running stack of six .NET processes plus four containers: databases, a broker,…","i":"MMCA.Common.Aspire ServiceDefaults WithReference dotnet run"},{"u":"/docs/onboarding/devops-aspire.html#the-one-command-local-run","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The one-command local run","x":"That command brings up everything the application needs locally: four SQL Server databases, Redis, RabbitMQ with management UI, a MailDev SMTP interceptor, four extracted…"},{"u":"/docs/onboarding/devops-aspire.html#mmcaadcapphost-the-orchestration-project","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.ADC.AppHost, the orchestration project","x":"Source file: MMCA.ADC/Source/Hosting/MMCA.ADC.AppHost/Program.cs Extension helpers: MMCA.Common.Aspire.Hosting/Extensions.cs (AddMessageBroker, WithBroker, WithJwksDiscovery,…","i":"identityService.WithE2eRegistrationThrottleLift LoginProtection__MaxRegistrationsPerIpPerHour ConnectionStrings__SQLServerConnectionString ConnectionStrings__CosmosConnectionString ConnectionStrings__SqliteConnectionString Authentication__JwtBearer__Authority identityService.WithEnvironment services__notification__grpc__0 WithE2eRegistrationThrottleLift E2E_LIFT_REGISTRATION_THROTTLE GrpcResultExceptionInterceptor JwtForwardingClientInterceptor"},{"u":"/docs/onboarding/devops-aspire.html#where-service-defaults-come-from-mmcacommonaspire-not-a-local-project","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Where service defaults come from, MMCA.Common.Aspire, not a local project","x":"There is no MMCA.ADC.ServiceDefaults project. The conventional Aspire \"ServiceDefaults\" shared project that scaffolding generates has been deleted; each service host (and the UI)…","i":"AddCommonKeyVaultConfiguration scoring.run.failed.terminal MMCA.ADC.ServiceDefaults AddCommonDataProtection DefaultAzureCredential builder.Configuration AuditTrailCleanupJob ConfigurationManager MapDefaultEndpoints AddServiceDefaults MMCA.Common.Aspire ScheduledJobRunner"},{"u":"/docs/onboarding/devops-aspire.html#mmcacommonaspire-the-framework-service-defaults-package","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.Common.Aspire, the framework service-defaults package","x":"Source: MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.cs Telemetry: MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Telemetry/OutboxPollFilterProcessor.cs Security:…","i":"APPLICATIONINSIGHTS_CONNECTION_STRING OpenIdConnectMetadataWarmupTask EnableMultipleHttp2Connections AddInfrastructureHealthChecks Services.AddServiceDiscovery Telemetry__TracesSampleRatio ActivityTraceFlags.Recorded ConfigureHttpClientDefaults OTEL_EXPORTER_OTLP_ENDPOINT PooledConnectionIdleTimeout MMCA.Common.Infrastructure WarmupReadinessHealthCheck"},{"u":"/docs/onboarding/devops-aspire.html#mmcacommonaspirehosting-the-apphost-extensions-package","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"MMCA.Common.Aspire.Hosting, the AppHost extensions package","x":"Source: MMCA.Common/Source/Hosting/MMCA.Common.Aspire.Hosting/Extensions.cs This package lives in a separate assembly from MMCA.Common.Aspire so running services do not pull in…","i":"identityService.WithE2eRegistrationThrottleLift LoginProtection__MaxRegistrationsPerIpPerHour Authentication__JwtBearer__Authority WithE2eRegistrationThrottleLift E2E_LIFT_REGISTRATION_THROTTLE E2eRegistrationsPerIpPerHour builder.AddMessageBroker E2E_JWT_PRIVATE_KEY_PEM WithSQLServerDataSource E2E_JWT_PUBLIC_KEY_PEM Jwks__RsaPublicKeyPem Jwt__RsaPrivateKeyPem"},{"u":"/docs/onboarding/devops-aspire.html#the-six-dockerfiles","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The six Dockerfiles","x":"All six Dockerfiles share the same multi-stage structure (base → build → publish → final) and the same base images. None build the AppHost, it is a local-only orchestration…","i":"MMCA.ADC.Notification.Service.dll MMCA.ADC.Conference.Service.dll MMCA.ADC.Engagement.Service.dll GlobalUsings.IdentifierType.cs MMCA.ADC.Identity.Service.dll MMCA.ADC.UI.Web.Client Directory.Build.props TreatWarningsAsErrors MMCA.ADC.Gateway.dll MMCA.ADC.UI.Web.dll MMCA.Common.Aspire MMCA.ADC.UI.Web"},{"u":"/docs/onboarding/devops-aspire.html#local-to-cloud-parity","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Local-to-cloud parity","x":"The AppHost topology maps directly to the Azure infrastructure provisioned by infra/main.bicep. The table below cross-references the local resource with its Azure equivalent: The…","i":"ConnectionStrings__SQLServerMigrationsAssembly APPLICATIONINSIGHTS_CONNECTION_STRING __SQLServerMigrationsAssembly OTEL_EXPORTER_OTLP_ENDPOINT ConnectionStrings__redis WithSQLServerDataSource Outbox__DatabaseName AddBrokerMessaging MessageBusProvider ADC_Notification AzureServiceBus ADC_Conference"},{"u":"/docs/onboarding/devops-aspire.html#the-yarp-gateways-role","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"The YARP Gateway's role","x":"The gateway (Source/Hosts/MMCA.ADC.Gateway) is a pure YARP reverse proxy. It has no DbContext, no ModuleLoader, no REST controllers, and no broker connection. Its Program.cs is…","i":"HttpResilienceDefaults.TotalRequestTimeout notificationRestConfig HttpVersion.Version20 RequestVersionOrLower RequestVersionExact restActivityTimeout ActivityTimeout Http1AndHttp2 VersionPolicy ForwardHttp2 MapForwarder ModuleLoader"},{"u":"/docs/onboarding/devops-aspire.html#startup-ordering-summary","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Startup ordering summary","x":"The health-based WaitFor chain imposes this ordering. Note that three of the four services wait on Identity without any explicit WaitFor in the AppHost: WithJwksDiscovery adds it…","i":"WithJwksDiscovery WithReference WaitFor"},{"u":"/docs/onboarding/devops-aspire.html#not-determinable-from-source","d":"Aspire Orchestration and Containers","k":"Onboarding Guide","t":"Not determinable from source","x":"- The specific integration events that flow over the broker (e.g., UserRegistered, SpeakerLinkedToUser) are cited from AppHost inline comments (Program.cs:46-51, 130-136), not…","i":"SpeakerLinkedToUser UserRegistered CLAUDE.md"},{"u":"/docs/onboarding/devops-cicd.html","d":"CI/CD and Operations","k":"Onboarding Guide","x":"This chapter walks the GitHub Actions workflows that govern MMCA, from the framework's continuous integration and lockstep NuGet release in MMCA.Common, through the ADC…","i":"MMCA.Common"},{"u":"/docs/onboarding/devops-cicd.html#mmcacommon-ciyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.Common, ci.yml","x":"File: MMCA.Common/.github/workflows/ci.yml The continuous-integration workflow for the MMCA.Common framework. Because the fifteen packages are consumed by every downstream…","i":"MMCA.Common.Infrastructure.Redis.Tests RestorePackagesWithLockFile Deque.AxeCore.Playwright Directory.Packages.props PLAYWRIGHT_BROWSERS_PATH DistributedCacheService MMCA.Common.Testing.E2E MMCA.Common.UI.Gallery Directory.Build.props TreatWarningsAsErrors Infrastructure.Tests MMCA.Common.UI.Tests"},{"u":"/docs/onboarding/devops-cicd.html#mmcacommon-releaseyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.Common, release.yml","x":"File: MMCA.Common/.github/workflows/release.yml The lockstep NuGet release workflow. When a maintainer pushes a vX.Y.Z git tag, this workflow deterministically derives the…","i":"Directory.Packages.props github.repository_owner DependencyVersionTests Testing.Architecture MMCA.Common.UI.Maui MMCA.Common.slnx GITHUB_REF_NAME Aspire.Hosting Infrastructure GITHUB_TOKEN Application release.yml"},{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-deployyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, deploy.yml","x":"File: MMCA.ADC/.github/workflows/deploy.yml The primary CI/CD pipeline for the Atlanta Developers Conference application. It runs on every push to main, on every pull request…","i":"needs.foundation.outputs.acrLoginServer coverage.integration.cobertura.xml MMCA.ADC.Integration.slnf Directory.Packages.props USE_MANAGED_IDENTITY_SQL JWT_RSA_PRIVATE_KEY_PEM MMCA.ADC.Services.Tests __EFMigrationsHistory Directory.Build.props SQL_LOCATION_OVERRIDE WebApplicationFactory skip_freshness_gates"},{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-e2eyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, e2e.yml","x":"File: MMCA.ADC/.github/workflows/e2e.yml The full-stack Playwright E2E test workflow. It brings up the complete Aspire stack (SQL Server + Redis + RabbitMQ + four services +…","i":"PLAYWRIGHT_BROWSERS_PATH MMCA.Common.Testing.E2E github.event.schedule WEB_VITALS_OUTPUT_DIR PlaywrightFixture workflow_dispatch matrix.browser WebVitalsTests workflow_call E2E_BASE_URL GITHUB_TOKEN E2E_BROWSER"},{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-cost-guardyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, cost-guard.yml","x":"File: MMCA.ADC/.github/workflows/cost-guard.yml A read-only FinOps check that confirms the production Azure footprint is at its cost baseline. It detects a specific operational…","i":"project_adc_2026_actual_load.md BASELINE_MAX_REPLICAS workflow_dispatch workflow_call deploy.yml production"},{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-load-testyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, load-test.yml","x":"File: MMCA.ADC/.github/workflows/load-test.yml A k6 load test targeting the output-cached Conference read endpoints through the production Gateway. It establishes a repeatable…","i":"project_adc_2026_actual_load.md workflow_dispatch inputs.peak_vus production base_url BASE_URL peak_vus PEAK_VUS"},{"u":"/docs/onboarding/devops-cicd.html#mmcaadc-cutover-per-service-dbsyml","d":"CI/CD and Operations","k":"Onboarding Guide","t":"MMCA.ADC, cutover-per-service-dbs.yml","x":"File: MMCA.ADC/.github/workflows/cutover-per-service-dbs.yml A one-time, manually-triggered workflow that migrated the four empty per-service databases (ADCIdentity,…","i":"inputs.freeze_traffic ADC_Notification OutboxProcessor ADC_Conference ADC_Engagement freeze_traffic OutboxMessages ADC_Identity containerapp GITHUB_TOKEN SqlBulkCopy deploy.yml"},{"u":"/docs/onboarding/devops-cicd.html#cross-workflow-summary","d":"CI/CD and Operations","k":"Onboarding Guide","t":"Cross-workflow summary","x":"(dr-drill.yml is the ADR-009 §29 restore drill: it PITR-restores a copy of a chosen database, times the restore for the RTO record, verifies it comes back Online, then deletes…","i":"workflow_call deploy.needs deploy.yml federated because e2e.yml subject deploy scoped false slnx the"},{"u":"/docs/onboarding/devops-cicd.html#rubric-category-index-for-this-chapter","d":"CI/CD and Operations","k":"Onboarding Guide","t":"Rubric category index for this chapter","i":"WebVitalsTests deploy.needs environment release.yml deploy.yml foundation production coverage cutover e2e.yml ci.yml deploy"},{"u":"/docs/onboarding/devops-iac.html","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","x":"This chapter teaches the Azure Infrastructure-as-Code layer for the MMCA.ADC application: what resources are provisioned, why they are shaped the way they are, how secrets reach…","i":"azure.yaml deploy.yml"},{"u":"/docs/onboarding/devops-iac.html#how-the-pieces-fit-together","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"How the pieces fit together","x":"Before diving into individual files, here is the end-to-end picture: Phases 1 and 2 are their own jobs (deploy.yml:747, deploy.yml:795) rather than steps inside deploy, so they…","i":"AZURE_RESOURCE_GROUP resourceGroup foundation main.bicep AtlDevCon deploy"},{"u":"/docs/onboarding/devops-iac.html#azureyaml-the-azd-project-definition","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"azure.yaml, the azd project definition","x":"File: MMCA.ADC/azure.yaml azure.yaml is the Azure Developer CLI (azd) manifest for the project. It declares six deployable services and points azd at the Bicep infrastructure…","i":"Directory.Packages.props foundation.bicep containerapp notification azure.yaml conference engagement main.bicep identity language provider context"},{"u":"/docs/onboarding/devops-iac.html#infrafoundationbicep-long-lived-shared-infrastructure","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"infra/foundation.bicep, long-lived shared infrastructure","x":"File: MMCA.ADC/infra/foundation.bicep Foundation is deployed first (CI/CD chapter: deploy.yml:773-779) on every run. It provisions three resources: the Azure Container Registry,…","i":"reference_log_analytics_sku_limits.md needs.foundation.outputs.acrName workspaceCapping.dailyQuotaGb appLogsConfiguration adminUserEnabled logAnalyticsName environmentName acrLoginServer resourceGroup resourceToken timerTriggers acrPurgeTask"},{"u":"/docs/onboarding/devops-iac.html#deployment-parameters-assembled-at-deploy-time-not-committed","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Deployment parameters, assembled at deploy time, not committed","x":"There is no infra/main.parameters.json file in the repository, the infra/ directory holds only foundation.bicep, main.bicep, DISASTER-RECOVERY.md, OPERATIONS.md,…","i":"USE_MANAGED_IDENTITY_SQL useManagedIdentitySql deploymentParameters SQL_ADMIN_PASSWORD alertEmailAddress foundation.bicep logAnalyticsName sqlAdminPassword environmentName Microsoft.Sql OPERATIONS.md hasAnthropic"},{"u":"/docs/onboarding/devops-iac.html#inframainbicep-the-full-application-infrastructure","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"infra/main.bicep, the full application infrastructure","x":"File: MMCA.ADC/infra/main.bicep main.bicep declares every application-layer Azure resource: Application Insights, the SLO scheduled query rules and their action group, two…","i":"ApplicationSettings__DatabaseInitStrategy Logging__OpenTelemetry__LogLevel__Default APPLICATIONINSIGHTS_CONNECTION_STRING AddHttpForwarderWithServiceDiscovery Authentication__JwtBearer__Authority project_outbox_cost_optimization.md Telemetry__DisableHttpClientMetrics project_adc_no_broker_in_azure.md Scheduler__PollingIntervalSeconds ObservabilityConventionTestsBase Telemetry__DisableRuntimeMetrics DataProtection__ApplicationName"},{"u":"/docs/onboarding/devops-iac.html#deployment-model-summary","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Deployment model summary","x":"The complete credential chain: No static credential exists at any link in this chain. The GitHub secrets AZURECLIENTID, AZURETENANTID, AZURESUBSCRIPTIONID are the OIDC…","i":"AZURE_SUBSCRIPTION_ID SQL_ADMIN_PASSWORD AZURE_CLIENT_ID AZURE_TENANT_ID secure"},{"u":"/docs/onboarding/devops-iac.html#rubric-category-cross-reference","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Rubric category cross-reference","x":"---","i":"useManagedIdentitySql OTEL_SERVICE_NAME adminUserEnabled KeyVault__Uri dailyQuotaGb minReplicas commonTags secrets secure false grpc"},{"u":"/docs/onboarding/devops-iac.html#not-determinable-from-source","d":"Infrastructure as Code, ADC Azure Deployment","k":"Onboarding Guide","t":"Not determinable from source","x":"- The exact AcrPull and Key Vault Secrets User role-assignment commands used in the out-of- band bootstrap are referenced in comments (main.bicep:915-919, main.bicep:933-936) but…","i":"USE_MANAGED_IDENTITY_SQL AZURE_RESOURCE_GROUP SQL_AAD_ADMIN_LOGIN AZURE_SQL_LOCATION SQL_AAD_ADMIN_OID deploymentMode deploy.yml main.bicep AcrPull Secrets westus2 false"},{"u":"/docs/onboarding/devops-runbooks.html","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","x":"This chapter covers every operational script and runbook in MMCA.ADC: the one-time Azure bootstrap, the database-per-service cutover story (how the legacy AtlDevCon monolith DB…","i":"MMCA.Store AtlDevCon MMCAStore MMCA.ADC ib_rg"},{"u":"/docs/onboarding/devops-runbooks.html#azure-setupsh-one-time-azure-bootstrap","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"azure-setup.sh, One-time Azure bootstrap","x":"File: MMCA.ADC/scripts/azure-setup.sh What it is. A bash script that creates every Azure identity and OIDC credential the GitHub Actions deploy pipeline needs. It is idempotent:…","i":"feedback_azure_cli_role_bug.md JWT_RSA_PRIVATE_KEY_PEM JWT_RSA_PUBLIC_KEY_PEM AZURE_SUBSCRIPTION_ID create_or_replace_fic AZURE_RESOURCE_GROUP MissingSubscription SQL_ADMIN_PASSWORD AZURE_CLIENT_ID AZURE_TENANT_ID Technologies assign_role"},{"u":"/docs/onboarding/devops-runbooks.html#the-database-per-service-cutover-story","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"The database-per-service cutover story","x":"Before the cutover scripts make sense, the story behind them does. Before ADR-006. All four modules (Identity, Conference, Engagement, Notification) pointed at a single shared…","i":"DataSources__Identity__SQLServerConnectionString CrossDataSourceDegradeConvention project_outbox_race_shared_db.md AtlDevCon.dbo.OutboxMessages inputs.freeze_traffic dbo.OutboxMessages workflow_dispatch ADC_Notification OutboxProcessor ADC_Conference ADC_Engagement freeze_traffic"},{"u":"/docs/onboarding/devops-runbooks.html#copy-atldevcon-to-per-service-dbsazureps1-azure-data-copy","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"copy-atldevcon-to-per-service-dbs.azure.ps1, Azure data copy","x":"File: MMCA.ADC/scripts/copy-atldevcon-to-per-service-dbs.azure.ps1 What it is. A PowerShell script that streams rows from AtlDevCon into the four per-service Azure SQL databases…","i":"Microsoft.Data.SqlClient AtlDevCon.schema.Table QUOTED_IDENTIFIER OutboxMessages KeepIdentity is_computed SqlBulkCopy sys.columns CHECKIDENT rowversion RowVersion AtlDevCon"},{"u":"/docs/onboarding/devops-runbooks.html#migrate-atldevcon-to-per-service-dbsps1-local-data-copy-wrapper","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"migrate-atldevcon-to-per-service-dbs.ps1, local data copy wrapper","x":"File: MMCA.ADC/scripts/migrate-atldevcon-to-per-service-dbs.ps1 What it is. A thin PowerShell wrapper that invokes the companion SQL script via sqlcmd against the local Aspire…","i":"QUOTED_IDENTIFIER AtlDevCon localhost sqlcmd error exit sql"},{"u":"/docs/onboarding/devops-runbooks.html#migrate-atldevcon-to-per-service-dbssql-local-sql-copy-script","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"migrate-atldevcon-to-per-service-dbs.sql, local SQL copy script","x":"File: MMCA.ADC/scripts/migrate-atldevcon-to-per-service-dbs.sql What it is. The T-SQL script that performs the actual per-row copy from AtlDevCon into the four per-service…","i":"AtlDevCon.sys.columns sys.identity_columns IDENTITY_INSERT OutboxMessages CHECKIDENT SchemaName XACT_ABORT AtlDevCon TableName timestamp TargetDb EXISTS"},{"u":"/docs/onboarding/devops-runbooks.html#infradisaster-recoverymd-dr-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/DISASTER-RECOVERY.md, DR runbook","x":"File: MMCA.ADC/infra/DISASTER-RECOVERY.md (175 lines; not the Store file of the same name) What it is. The authoritative disaster-recovery runbook for the ADC production…","i":"publicNetworkAccess scheduledQueryRules serviceDatabaseLtr workflow_dispatch ADC_Notification ADC_Conference ADC_Engagement resourceToken sloAlertSpecs ADC_Identity containerapp keyVaultUrl"},{"u":"/docs/onboarding/devops-runbooks.html#dr-drillyml-and-dr-restore-drillps1-the-adr-009-restore-drill","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"dr-drill.yml and dr-restore-drill.ps1, the ADR-009 restore drill","x":"Files: MMCA.ADC/.github/workflows/dr-drill.yml, MMCA.ADC/scripts/dr-restore-drill.ps1 What it is. The automation behind the drill requirement above: the workflow picks a target…","i":"workflow_dispatch SourceDatabase ADC_Identity deploy.needs deploy.yml AtlDevCon finally restore Online status exit show"},{"u":"/docs/onboarding/devops-runbooks.html#infraoperationsmd-day-2-alert-triage-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/OPERATIONS.md, day-2 alert triage runbook","x":"File: MMCA.ADC/infra/OPERATIONS.md What it is. The alert-to-action companion to the provisioned observability: what to do when each SLO alert fires, how to read the SLO workbook,…","i":"MMCA.Common.Testing.Architecture ObservabilityConventionTestsBase legacySloMetricAlertSpecs infra.OPERATIONS.md MinimumAlertSpecs infra.main.bicep OPERATIONS.md sloAlertSpecs ALERT_EMAIL AppTraces sloAlerts resource"},{"u":"/docs/onboarding/devops-runbooks.html#infrasql-managed-identitymd-staged-passwordless-sql-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/SQL-MANAGED-IDENTITY.md, staged passwordless-SQL runbook","x":"File: MMCA.ADC/infra/SQL-MANAGED-IDENTITY.md What it is. The runbook for moving the four service apps from SQL-login (password) auth to Entra managed-identity auth against their…","i":"vars.USE_MANAGED_IDENTITY_SQL USE_MANAGED_IDENTITY_SQL SQL_AAD_ADMIN_LOGIN SQL_AAD_ADMIN_OID Directory db_owner EXTERNAL Identity PROVIDER Managed Active CREATE"},{"u":"/docs/onboarding/devops-runbooks.html#infrapost-cutover-atldevcon-downgrademd-archive-downgrade-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"infra/POST-CUTOVER-atldevcon-downgrade.md, archive downgrade runbook","x":"File: MMCA.ADC/infra/POST-CUTOVER-atldevcon-downgrade.md What it is. A step-by-step runbook for the third and final commit of the database-per-service rollout: downgrading…","i":"maxSizeBytes ProcessedOn deploy.yml main.bicep AtlDevCon capacity against bacpac update query name NULL"},{"u":"/docs/onboarding/devops-runbooks.html#play-store-captureps1-android-screenshot-capture","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"play-store-capture.ps1, Android screenshot capture","x":"File: MMCA.ADC/scripts/play-store-capture.ps1 What it is. A PowerShell 7 script that captures a screenshot from an attached Android device or emulator via adb screencap and saves…","i":"screencap Files shell PATH slug adb png x86"},{"u":"/docs/onboarding/devops-runbooks.html#play-store-composeps1-play-store-screenshot-compositor","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"play-store-compose.ps1, Play Store screenshot compositor","x":"File: MMCA.ADC/scripts/play-store-compose.ps1 What it is. A PowerShell 7 script that reads raw captures from store-assets/play-store/raw/, wraps each into a 1080×1920 branded…","i":"System.Drawing.Common LinearGradientBrush brandTealDark brandCyan brandTeal imageMaxH imageMaxW slug png"},{"u":"/docs/onboarding/devops-runbooks.html#docsmobilereleaserunbookmd-store-submission-runbook","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Docs/MobileReleaseRunbook.md, store-submission runbook","x":"File: MMCA.ADC/Docs/MobileReleaseRunbook.md What it is. The manual, credential-holding steps around a store submission that code and CI cannot perform, each tagged with when it…","i":"ADC_ANDROID_SIGNING_PASSWORD FileStorage.UploadFailed sha256_cert_fingerprints AndroidSigningStorePass com.ivanball.atldevcon grantAvatarStorageRole AndroidSigningKeyPass deployNotificationHub TargetPlatformVersion InternalServerError Entitlements.plist ivanball.AtlDevCon"},{"u":"/docs/onboarding/devops-runbooks.html#the-database-per-service-cutover-in-full-context","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"The database-per-service cutover in full context","x":"The five database-related artifacts above form a single coherent story, and the resilience artifacts extend it past the cutover: The AtlDevCon database is the thread that runs…","i":"CrossDataSourceDegradeConvention OPERATIONS.md deploy.yml main.bicep AtlDevCon delete NEVER sql"},{"u":"/docs/onboarding/devops-runbooks.html#rubric-tag-summary","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Rubric tag summary","x":"---","i":"OPERATIONS.md"},{"u":"/docs/onboarding/devops-runbooks.html#not-determinable-from-source","d":"Operational Scripts & Runbooks","k":"Onboarding Guide","t":"Not determinable from source","x":"- ALERTEMAIL variable: DISASTER-RECOVERY.md:55-57 and OPERATIONS.md:8-11 both route alert notifications through the alertEmailAddress action-group receiver fed by the ALERTEMAIL…","i":"alertEmailAddress ALERT_EMAIL"},{"u":"/docs/onboarding/devops-testing.html","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","x":"Chapter scope note. The tier chapters (tier-00 through the sweep) document every type in the production codebase one by one. Test types are the logged exception: this chapter…","i":"Fact"},{"u":"/docs/onboarding/devops-testing.html#1-solution-composition-and-the-test-runner","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"1. Solution composition and the test runner","x":"The two deployed apps use the same two-file pattern; MMCA.Common and MMCA.Helpdesk ship a .slnx only, because their solutions are already fast enough not to need a CI subset:…","i":"MMCA.ADC.ServiceBusEmulator.IntegrationTests MMCA.ADC.CrossService.IntegrationTests MMCA.ADC.Architecture.Tests MMCA.Store.Integration.slnf MMCA.ADC.Integration.slnf MMCA.ADC.IntegrationTests DistributedCacheService MMCA.ADC.Services.Tests MMCA.ADC.Gateway.Tests MMCA.ADC.WebAPI.Tests MMCA.Common.API.Tests WebApplicationFactory"},{"u":"/docs/onboarding/devops-testing.html#2-test-project-layout","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"2. Test project layout","x":"The inventory below is drawn from 00-inventory.md:23-117 (test-assembly counts) and the solution files above. Counts are distinct types per project as reported by the Roslyn…","i":"MMCA.ADC.ServiceBusEmulator.IntegrationTests CurrentEventNotificationScopeProviderTests MMCA.ADC.Conference.Infrastructure.Tests MMCA.ADC.Engagement.Infrastructure.Tests MMCA.ADC.Notification.Application.Tests MMCA.ADC.CrossService.IntegrationTests MMCA.ADC.Identity.Infrastructure.Tests MMCA.ADC.Notification.IntegrationTests MMCA.Common.Infrastructure.Redis.Tests NotificationUserDataExportSectionTests MMCA.ADC.Conference.Application.Tests MMCA.ADC.Engagement.Application.Tests"},{"u":"/docs/onboarding/devops-testing.html#3-shipped-testing-infrastructure-packages","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"3. Shipped testing-infrastructure packages","x":"MMCA.Common ships four of its fifteen packages as testing infrastructure that downstream apps consume as NuGet references rather than writing their own harness…","i":"AxeOptions.Wcag21AaExceptMudPagerCombobox WebApplicationFactory.ConfigureServices ServiceInfoVersioningContractTestsBase AssertNoAccessibilityViolationsAsync IsAuthenticatedAuthorizationService SqlServerIntegrationTestFixtureBase MutableAuthenticationStateProvider PageExtensions.FillAndVerifyAsync MMCA.Common.Testing.Architecture ProductionHostApplicationFactory AccessibilityViolationException DecoratorPipelineOrderTestsBase"},{"u":"/docs/onboarding/devops-testing.html#4-architecture-fitness-tests-executable-governance","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"4. Architecture fitness tests, executable governance","x":"[Rubric §34, Architecture Governance & Documentation]: §34 assesses whether architectural decisions are documented, enforced, and kept honest over time; fitness functions are the…","i":"AggregateRoots_ShouldHave_NoPublicConstructors SpecificationsDoNotNavigateToOtherEntities ArchitectureRules.PinnedPackageMajorBelow LayerMap_ModulesDeclareEveryExpectedLayer MassTransit_MustNotExceed_MajorVersion8 CoreLayers_ShouldNotDependOn_Transport ImageSharp_MustNotExceed_MajorVersion3 ObservabilityConventionTestsBaseTests Infrastructure_ShouldNotDependOn_Api ConstructorDependencyCountTestsBase DomainFactories_ShouldReturn_Result FakeDependentModuleConformanceTests"},{"u":"/docs/onboarding/devops-testing.html#5-integration-and-e2e-strategy","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"5. Integration and E2E strategy","x":"The four integration test projects (Identity, Conference, Engagement, Notification) each boot their service in-process with WebApplicationFactory . The lifecycle is not written…","i":"MMCA.Store.ServiceBusEmulator.IntegrationTests MMCA.ADC.ServiceBusEmulator.IntegrationTests MMCA.Store.CrossService.IntegrationTests MMCA.ADC.CrossService.IntegrationTests MMCA.Common.Infrastructure.Redis.Tests AssertNoAccessibilityViolationsAsync IntegrationTestBase.InitializeAsync SqlServerIntegrationTestFixtureBase MMCA.Common.Infrastructure.Tests IdentityIntegrationTestFixture appsettings.Development.json DatabaseInitStrategy.Migrate"},{"u":"/docs/onboarding/devops-testing.html#6-worked-examples","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"6. Worked examples","x":"Three examples tie the infrastructure above to real test code. The per-repo class is a bare subclass; the facts, the package lists and the parsing live once in the shared base:…","i":"IdentityIntegrationTestFixture.DisposeAsync ImageSharp_MustNotExceed_MajorVersion3 IntegrationTestBase.InitializeAsync MutableAuthenticationStateProvider IntegrationTestBase.DisposeAsync IdentityIntegrationTestFixture AuthenticationStateProvider GetAuthenticationStateAsync IdentityIntegrationTestBase Fixture.ResetDatabaseAsync Directory.Packages.props AuthenticateAsAttendee"},{"u":"/docs/onboarding/devops-testing.html#7-the-tiers-and-the-gates-that-run-them","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"7. The tiers and the gates that run them","x":"A test tier only means something once you know what it blocks. This is the map. MMCA.Common's ui-e2e job (MMCA.Common/.github/workflows/ci.yml:228) builds the out-of-slnx gallery…","i":"Integration.slnf MemoryDiagnoser E2E_BROWSER browsers chromium coverage CI.slnf e2e.yml firefox skipped success deploy"},{"u":"/docs/onboarding/devops-testing.html#quick-reference-rubric-categories-touched-in-this-chapter","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"Quick reference: rubric categories touched in this chapter","x":"---"},{"u":"/docs/onboarding/devops-testing.html#cross-links","d":"Testing Architecture & Solution Composition","k":"Onboarding Guide","t":"Cross-links","x":"- Primer: 00-primer.md5-the-solution--test-layout , solution files, MTP runner, slnx-excluded UI projects - Primer:…","i":"MMCA.ADC.Integration.slnf IIntegrationTestFixture"},{"u":"/docs/onboarding/99-coverage-audit.html","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","x":"This audit reconciles the written guide against the mechanically-extracted inventory, logs every deliberate exception, verifies the grouping/ordering rules, proves all 34 rubric…","i":"classify.ps1 verify.ps1 plan.ps1"},{"u":"/docs/onboarding/99-coverage-audit.html#1-coverage-reconciliation","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"1. Coverage reconciliation","x":"Cross-check result: verify.ps1 confirms 0 of the 1,804 individually-sectioned types are missing from their group chapter, every one appears as a heading or in a sibling-family…","i":"SpecificationsDoNotNavigateToOtherEntities MMCA.Common.Infrastructure.Redis.Tests DatabaseInitializationExtensionsTests HttpContextExternalLoginEmailVerifier MMCA.ADC.Conference.Application.Tests MMCA.ADC.Engagement.Application.Tests ObservabilityConventionTestsBaseTests OwnSessionQuestionAnswerSpecification AnonymousAuthenticationStateProvider AzureNotificationHubNativePushSender EntitiesWithPiiImplementAnonymizable FrameworkVersionConsistencyTestsBase"},{"u":"/docs/onboarding/99-coverage-audit.html#2-exceptions-log-every-deliberate-omission-with-reason","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"2. Exceptions log (every deliberate omission, with reason)","x":"EF Core migrations (/Migrations/, .Migrations.SqlServer), ModelSnapshot, .Designer.cs, .g.cs, GlobalUsings.g.cs, and AssemblyInfo.cs are excluded by rule (Tools/invtool…","i":"ObservabilityConventionTestsBase ProductionHostApplicationFactory RouteAuthorizationTestsBase ModuleConformanceTestsBase DependencyInjectionAssert GracefulShutdownTestsBase MMCA.Common.Benchmarks Migrations.SqlServer Testing.Architecture MMCA.Common.Testing GlobalUsings.g.cs AssemblyInfo.cs"},{"u":"/docs/onboarding/99-coverage-audit.html#3-grouping--ordering-verification","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"3. Grouping & ordering verification","x":"- Every type in exactly one group. classify.ps1 assigns all 3,264 nodes via name-level overrides (for the grab-bag MMCA.Common.Interfaces/Services namespaces) + ordered…","i":"MidSaveContextCreatingDbContext OutboxRoutingTestDbContext ReentrantSaveInterceptor FailingSaveInterceptor INavigationPopulator ResultGrpcExtensions EntityQueryService SelfHttpWarmupTask ApiControllerBase DeferredDispatch ErrorHttpMapping _typemap.tsv"},{"u":"/docs/onboarding/99-coverage-audit.html#4-rubric-coverage-matrix","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"4. Rubric coverage matrix","x":"Every one of the 34 categories is explained at least once against real code. \"First explained in\" is the earliest group chapter (by order) that tags it; many recur and several…","i":"ThemeService verify.ps1 token"},{"u":"/docs/onboarding/99-coverage-audit.html#5-open-questions--not-determinable-from-source","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"5. Open questions / not determinable from source","x":"1. IDbSeeder host invocation (group-07). The seeding contract and implementations are in MMCA.Common, but the IHostedService/startup invoker that actually runs seeding at boot…","i":"MMCA.ADC.Identity.Contracts.DependencyInjection ModuleApplicationDbContext CrossSourceSpecification ReadRepositoryExtensions EntityTypeConfiguration DependencyInjection DbContexts.Factory ChangePassword ExportUserData IHostedService EnsureCreated IUnitOfWork"},{"u":"/docs/onboarding/99-coverage-audit.html#6-how-to-regenerate-this-audit","d":"Phase 4, Coverage Audit","k":"Onboarding Guide","t":"6. How to regenerate this audit","x":"Then copy the refreshed out/00-inventory.md and out/00-dependency-manifest.md into Docs/Onboarding/ (the 00-group-taxonomy.md is written there directly by classify.ps1).","i":"classify.ps1"},{"u":"/docs/onboarding/CONCEPT-MAPS.html","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","x":"Mermaid diagrams distilled from the Onboarding guide (primer, group taxonomy, dependency manifest, and the 27 group chapters). Each diagram captures a relationship between the…"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#1-system-context-two-codebases--the-15-packages","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"1. System context, two codebases + the 15 packages","x":"MMCA.Common is a framework published as fifteen NuGet packages in lockstep, to nuget.org and GitHub Packages from one tag (ADR-053); MMCA.ADC and MMCA.Store consume them. The…","i":"MMCA.Common.slnx MMCA.Common MMCA.Store MMCA.ADC UI.Maui"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#2-clean-architecture-the-layered-dependency-rule","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"2. Clean Architecture, the layered dependency rule","x":"Source dependencies point inward toward the Domain; each layer references only layers below it. Deliberate exceptions: UI and Grpc depend on Shared only (UI for Blazor WASM…","i":"ProjectReference UI.Maui Aspire Blazor bridge depend Shared above host only sits and"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#3-the-27-functional-groups-dependency--build-order","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"3. The 27 functional groups, dependency / build order","x":"The primary axis of the guide: every type lives in exactly one of 27 chapter groups, ordered roughly topologically. Foundational, widely-depended-on concerns first (Result →…"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#4-core-framework-patterns-how-the-building-blocks-compose","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"4. Core framework patterns, how the building blocks compose","x":"The pattern-level view of the same backbone: the ideas the primer commits to and how they feed each other. Result is the pervasive currency; DDD blocks produce domain events;…","i":"Result"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#5-request-lifecycle-the-cqrs-decorator-pipeline-adr-014","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"5. Request lifecycle, the CQRS decorator pipeline (ADR-014)","x":"Handlers are thin (one method); every cross-cutting concern is a decorator wrapping the next. Scrutor TryDecorate composes them in reverse registration order (last registered =…","i":"AddApplicationDecorators TryDecorate"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#6-event-driven-integration-outbox-dual-dispatch-adr-003--010--021","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"6. Event-driven integration, outbox dual-dispatch (ADR-003 / 010 / 021)","x":"Domain events are captured into an OutboxMessage row in the same transaction as the data (no dual-write bug). The two event kinds then part ways: local domain events are…","i":"IIntegrationEventPublisher IEventBus.PublishAsync OutboxProcessor OutboxMessage SchemaVersion IMessageBus MessageId"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#7-modular-monolith--extractable-services-adr-006--007--008--012","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"7. Modular monolith → extractable services (ADR-006 / 007 / 008 / 012)","x":"Modules implement IModule and are discovered + Kahn-ordered by ModuleLoader (ADR-059). The same module code runs as a single monolith host or as N service processes behind a YARP…","i":"MMCA.ADC.WebAPI ModuleLoader IMessageBus IModule"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#8-persistence-database-per-service--polyglot-engines-adr-006--018--030","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"8. Persistence, database-per-service + polyglot engines (ADR-006 / 018 / 030)","x":"One concrete SQLServerDbContext over the abstract ApplicationDbContext, one instance per database. Each entity is engine-agnostic; a single [UseDataSource(engine)] attribute on…","i":"ApplicationDbContext SQLServerDbContext UseDataSource engine"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#9-authentication--authorization-stack","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"9. Authentication & Authorization stack","x":"The auth concern (G08) spans token validation, session cookies, federated sign-in, password hashing, brute-force protection, refresh-token rotation and revocation, and a layered…"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#10-notifications-three-channels-behind-one-send-pipeline-adr-024--044","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"10. Notifications, three channels behind one send pipeline (ADR-024 / 044)","x":"One use case (SendPushNotificationHandler) writes a durable per-user inbox, fires a transient SignalR push, and then an OS-level native push that reaches a backgrounded or killed…","i":"SendPushNotificationHandler MMCA.ADC.Notification SmtpEmailSender IEmailSender"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#11-ui-write-once-render-everywhere--i18n--theming","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"11. UI, write-once render everywhere + i18n + theming","x":"A page is authored once as a Razor component in a per-module UI library; both the Blazor web host (Server + WASM) and the .NET MAUI host reference the same libraries, so it…","i":"IStringLocalizer InteractiveAuto MMCA.Common.UI ThemeService rendermode"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#12-adc-business-modules-bounded-contexts-end-to-end","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"12. ADC business modules, bounded contexts end-to-end","x":"Each ADC module is a vertical slice through all layers. Conference is large enough to split across five chapters (G17-G21); Engagement takes two (G22 session bookmarks, G23 the…"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#13-the-adrs-grouped-by-theme","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"13. The ADRs, grouped by theme","x":"Every accepted ADR in Website/docs-src/adr/, clustered by the concern it governs. That directory's README.md is the canonical index and owns the count and range; this map only…","i":"README.md"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#14-the-34-category-evaluation-rubric","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"14. The 34-category evaluation rubric","x":"The lens the guide tags code against ([Rubric §N]). Scored on two axes: Maturity (0-4, process) and Implementation (0-10, substance). Three parts. ---","i":"Rubric"},{"u":"/docs/onboarding/CONCEPT-MAPS.html#15-how-the-axes-fit-together-reading-map","d":"MMCA Concept & Pattern Maps","k":"Onboarding Guide","t":"15. How the axes fit together (reading map)","x":"The guide is organized on two axes at once. This ties the diagrams above back to the guide's navigation. --- - Group-to-group arrows in §3 show the dominant \"builds on\" direction…","i":"ApplicationDbContext"},{"u":"/docs/governance/index.html","d":"Architecture Governance","k":"Architecture Governance","x":"The governance artifacts behind the MMCA platform: the shared 34-category evaluation rubric, and each repo's evidence-based scorecard plus its remediation backlog. Every score…"},{"u":"/docs/governance/index.html#the-rubric","d":"Architecture Governance","k":"Architecture Governance","t":"The rubric","x":"- Architecture Evaluation Criteria: the 34-category rubric (Maturity 0-4 and Implementation 0-10 per category) that all three application repos are scored against."},{"u":"/docs/governance/index.html#how-these-are-maintained","d":"Architecture Governance","k":"Architecture Governance","t":"How these are maintained","x":"Scores are re-verified from source on a cadence: each category is scored by reading the current code, config, and CI (never rolled forward), and any change lands with the…"},{"u":"/docs/governance/adc-ArchitectureScorecard.html","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.ADC's architecture scores (replaces the former single-axis snapshot; see git history).…","i":"dotnet_analyzer_diagnostic.severity SessionSelectionDashboard.razor.cs ArchitectureEvaluationCriteria.md MMCA.ADC.Notification.Application MMCA.Common.Testing.Architecture UIArchitectureConventionTests.cs StateManagementConventionTests SQLitePCLRaw.bundle_e_sqlite3 UIArchitectureConventionTests ObservabilityConventionTests PseudoLocalizationTests RemediationBacklog.md"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#executive-summary","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Executive summary","x":"MMCA.ADC is a mature, evidence-driven .NET 10.0 (LangVersion preview) DDD/Clean Architecture system for the Atlanta Developers Conference, deliberately extracted from a modular…","i":"MMCA.ADC.CrossService.IntegrationTests MMCA.ADC.Notification.IntegrationTests SessionIncludeChildrenRegressionTests UIArchitectureConventionTestsBase FrameworkVersionConsistencyTests LocalizedTextConventionTestsBase MMCA.Common.Testing.Architecture ErrorMessages.ValidationError LocalizedTextConventionTests ObservabilityConventionTests SpecificationConventionTests BlazorCspPolicyProvider.cs"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#scorecard","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted column = Maturity·weight / Implementation·weight per row. Axis-gap findings: the former §21 gap (excellent-but-not-enforced, impl 7 over maturity 2) closed on 2026-07-02…","i":"MmcaCultureBootstrap.SetBrowserCultureAsync ResilienceCircuitBreakerFaultInjectionTests MMCA.ADC.CrossService.IntegrationTests dotnet_analyzer_diagnostic.severity FrameworkVersionConsistencyTests.cs StateManagementConventionTestsBase MMCA.ADC.Notification.Application UIArchitectureConventionTestsBase MMCA.Common.Testing.Architecture ConstructorDependencyCountTests LocalizedTextConventionTests.cs ObservabilityConventionTests.cs"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#indices","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Indices","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 311 ÷ 320 = 97.2% (down from 97.8%, 313/320: the twenty-second-cycle full re-score (2026-07-21, pin v1.121.0) corrected §22…"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Architecture intent is executable, not prose: 29 architecture-test classes / 91 methods gate CI (§34 Governance, mat 4 / impl 9, and §3 Clean Architecture, mat 4 / impl 9):…","i":"SessionIncludeChildrenRegressionTests MMCA.Common.Testing.Architecture SpecificationConventionTests.cs AddSessionCookieAuthentication StateManagementConventionTests MicroserviceExtractionTests AddCommonSecurityHeaders ArchitecturalAnalysis.md LayerDependencyTests AddCommonBlazorCsp DataResidencyTests DomainPurityTests"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#top-5-risks","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"Expected-delta note (updated 2026-08-01): several entries below record an expected lift of \"impl 9→10\". Under the 2026-08-01 recalibration those are attainable, not aspirational:…","i":"publicNetworkAccess packages.lock.json MMCA.ADC.CI.slnf deploy.needs maxReplicas MMCA.ADC.UI CI.slnf"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How this repo's quality relates to the other consumers (where the framework's quality propagates, where a repo diverges or under-uses it, and where a consumer does better than…"},{"u":"/docs/governance/adc-RemediationBacklog.html","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md (single-axis 0-4, baseline 75%, 241/320, dated 2026-06-08). Current authoritative two-axis scores (twenty-sixth-cycle full re-score,…","i":"MMCA.ADC.CrossService.IntegrationTests SqlServerIntegrationTestFixtureBase SessionSelectionDashboard.razor.cs MMCA.ADC.Notification.Application FrameworkVersionConsistencyTests StateManagementConventionTests UIArchitectureConventionTests IntegrationTestReworkPlan.md LocalizedTextConventionTests ObservabilityConventionTests TranslationCompletenessTests MMCA.ADC.Integration.slnf"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-6-highest-leverage","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔴 Priority 6: highest leverage","x":"Token handling uses two rubric-named anti-patterns, with no CSP defense-in-depth. Status (2026-06-27): cookie-only refresh + in-memory access (auth-path BFF), OAuth…","i":"ResilienceCircuitBreakerFaultInjectionTests DisconnectedCircuitRetentionPeriod ManagementRouteAuthorizationTests GatewaySecurityHeadersMiddleware E2E_LIFT_REGISTRATION_THROTTLE OAuthController.CompleteAsync OAuthController.ExchangeAsync SameOriginProxyTokenRefresher MMCA.ADC.Conference.UI.Tests AuthenticationStateProvider EventDetailPage.StatusChip InvalidOperationException"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-4","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟠 Priority 4","x":"The (4−score)×weight formula puts this at 4, but the High flag is a contractual/regulatory exposure that contradicts a shipped, publicly-served policy: treat it as do-soon. -…","i":"user_notification_export.proto LocalizedTextConventionTests TranslationCompletenessTests user_engagement_export.proto ExportUserDataHandlerTests ErasureAndPiiLoggingTests DeleteUserHandlerTests ErrorMessages.Success SessionQuestionAnswer User.PreferredCulture UserRegisteredHandler EventQuestionAnswer"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-3-score-3-weight-3-one-rung-from-a-4","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟡 Priority 3: score 3, weight 3 (one rung from a 4)","x":"- ~~(High) The 258-test Testcontainers integration tier references the deleted MMCA.ADC.WebAPI host, won't build, and is excluded.~~ RESOLVED: reworked as per-service…","i":"Update_WithStaleRowVersion_ReturnsConflict MMCA.ADC.CrossService.IntegrationTests SessionSelectionDashboard.razor.cs StateManagementConventionTestsBase ManagementRouteAuthorizationTests UIArchitectureConventionTestsBase InProcessEventBus.PublishAsync SessionSelectionSpeakerOverlap StateManagementConventionTests UIArchitectureConventionTests DbUpdateConcurrencyException PublicSessionList.razor.cs"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-2-score-3-weight-2-polish--hardening","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Priority 2: score 3, weight 2 (polish / hardening)","x":"- ~~(Medium) No OpenAPI served by any running service, yet CLAUDE.md still advertises 4 doc UIs (/swagger, /nswag-swagger, /api-docs, /scalar/v1).~~ - [x] Serve OpenAPI per…","i":"Microsoft.AspNetCore.Authorization.Authorize AuthorizationPolicies.RequireOrganizer MMCA.ADC.Conference.IntegrationTests ManagementRouteAuthorizationTests FrameworkVersionConsistencyTests IdentityRouteAuthorizationTests IntegrationEventContractTests MMCA.ADC.Migrations.SqlServer Microsoft.AspNetCore.OpenApi ObservabilityConventionTests MicroserviceExtractionTests Validation.CorrectFollowing"},{"u":"/docs/governance/adc-RemediationBacklog.html#implementation-band-implementation--8-ranked-by-implpriority","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔵 Implementation band (implementation <= 8, ranked by implPriority)","x":"Added 2026-07-28 when the ledger gained its second ranked axis. Until then implementation gaps appeared only as unranked sub-bullets inside maturity items, so they were never…","i":"SessionSelectionDashboard.razor.cs MMCA.ADC.Notification.Application AdcArchitectureMap.DefineLayers ConferenceCategoryDetail.razor HappeningNow.razor.cs TreatWarningsAsErrors DeviceSettings.razor SponsorCreate.razor SponsorDetail.razor System.Private.Uri workflow_dispatch UI.Web.Client"},{"u":"/docs/governance/adc-RemediationBacklog.html#resolved-2026-07-25-performance-program-2","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Resolved 2026-07-25 (performance program 2)","x":"Second evidence-led performance pass over Common/ADC/Store. ADC's share shipped as three PRs plus the v1.127.0 framework sweep. - [x] Output cache shared across replicas.…","i":"Session.SessionQuestionAnswers Event.EventQuestionAnswers SessionQuestionViewBuilder CategoryItemLookupService SessionScoringProcessor SpeakerDashboardService SessionQuestionAnswers EventQuestionAnswers SessionCategoryItems SpeakerCategoryItems GetOpenPollsHandler PublicSessionDetail"},{"u":"/docs/governance/adc-RemediationBacklog.html#deliberate--accepted-recorded-decisions-not-scheduled-work","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate / accepted (recorded decisions, not scheduled work)","x":"Conscious, recorded choices, not pending work (the former TECHDEBT.md accepted-risk section): - Single-region deployment (no multi-region failover): accepted in…","i":"SessionRoomScheduling.ValidateRoomAssignmentAsync MMCA.ADC.Notification.Application ConstructorDependencyCountTests LocalizedTextConventionTests TranslationCompletenessTests ArchitecturalAnalysis.md PseudoLocalizationTests AuthenticationService BrandColorTokenTests DeviceSettings.razor skip_freshness_gates SliceCohesionTests"},{"u":"/docs/governance/adc-RemediationBacklog.html#already-at-level-4-protect-dont-regress","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"✅ Already at level 4: protect, don't regress","x":"1 SOLID · 2 Design Patterns · 3 Clean Architecture · 4 Domain-Driven Design · 5 Vertical Slice Architecture · 6 CQRS & Event-Driven · 7 Microservices Readiness · 8 Data…","i":"AnthropicScoringService.ScoreSessionAsync MMCA.ADC.CrossService.IntegrationTests GetSessionSelectionDashboardHandler SessionSelectionDashboard.razor.cs GetSpeakerSessionOverlapHandler GetCategoryDistributionHandler Session.AddSessionCategoryItem Session.CategoryItem.Duplicate Speaker.AddSpeakerCategoryItem Speaker.CategoryItem.Duplicate ObservabilityConventionTests OperationCanceledException"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html","d":"Architecture Evaluation Criteria","k":"Architecture Governance","x":"A structured rubric for evaluating the architecture of an enterprise application. Each category defines what is being assessed, concrete criteria to check, red flags that signal…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#how-to-use-this-rubric","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"How to Use This Rubric","x":"Score each category 0–4. Use the same scale everywhere so totals are comparable. Alongside the maturity level, rate how well each category is actually implemented on a finer 0–10…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#1-solid-principles","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"1. SOLID Principles","x":"Intent: Object/module-level design discipline that keeps code flexible and decoupled. Criteria - SRP: each class/handler has one reason to change; no \"god\" services orchestrating…","i":"NotSupportedException switch new"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#2-design-patterns","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"2. Design Patterns","x":"Intent: Appropriate, idiomatic use of patterns, solving real problems, not pattern theater. Criteria - Creational (Factory methods on entities, Builder, Options) used where…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#3-clean-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"3. Clean Architecture","x":"Intent: Dependencies point inward; business rules are independent of frameworks, UI, and data stores. Criteria - Dependency rule enforced: Domain → (nothing); Application →…","i":"JsonProperty DbContext Table"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#4-domain-driven-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"4. Domain-Driven Design","x":"Intent: The model reflects the business; boundaries follow capability boundaries, not technical layers. Criteria - Ubiquitous language: type/method names match business terms…","i":"decimal Result string Guid"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#5-vertical-slice-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"5. Vertical Slice Architecture","x":"Intent: Code is organized by feature/capability, so a change touches one cohesive slice. Criteria - Features grouped by use case (command/query + handler + validator + DTO…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#6-cqrs--event-driven-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"6. CQRS & Event-Driven Design","x":"Intent: Reads and writes are separated where it pays off; integration via events is reliable. Criteria - Commands (mutate, return Result) and queries (read, side-effect-free) are…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#7-microservices-readiness","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"7. Microservices Readiness","x":"Intent: Whether services (or future-extractable modules) are independently deployable and own their data. Criteria - Service boundaries align with bounded contexts; one team can…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#8-data-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"8. Data Architecture","x":"Intent: Persistence, consistency, and migrations are deliberate and safe. Criteria - Transaction boundaries match aggregate boundaries; unit-of-work scope is clear. - Migrations…","i":"Include"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#9-api--contract-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"9. API & Contract Design","x":"Intent: External and inter-service contracts are clear, stable, and evolvable. Criteria - Consistent resource/endpoint design (REST/minimal APIs/gRPC) with predictable shapes. -…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#10-cross-cutting-concerns","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"10. Cross-Cutting Concerns","x":"Intent: Validation, caching, resilience, configuration, and mapping are centralized and consistent. Criteria - Validation, logging, transactions handled by pipeline…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#11-security","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"11. Security","x":"Intent: AuthN/AuthZ, secrets, and data protection are correct by construction. Criteria - Authentication centralized; tokens validated; identity flows documented (e.g.,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#12-performance--scalability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"12. Performance & Scalability","x":"Intent: The system meets latency/throughput goals and scales horizontally. Criteria - Async I/O throughout; no sync-over-async; no blocking the request thread. - Hot-path query…","i":"Result Wait"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#13-observability--operability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"13. Observability & Operability","x":"Intent: You can understand and operate the system in production. Criteria - Structured logging with correlation/trace IDs flowing across module/service boundaries. - Distributed…","i":"Console.WriteLine"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#14-testability--test-strategy","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"14. Testability & Test Strategy","x":"Intent: The design supports fast, reliable, meaningful tests at the right levels. Criteria - Healthy test pyramid: many fast unit tests on domain/application, fewer integration,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#15-best-practices--code-quality","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"15. Best Practices & Code Quality","x":"Intent: Day-to-day craftsmanship that keeps the codebase healthy. Criteria - Analyzers at error severity (style, security, threading, maintainability) enforced in CI;…","i":"disable warning pragma"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#16-maintainability--evolvability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"16. Maintainability & Evolvability","x":"Intent: The system absorbs change cheaply and ages well. (The governance/documentation depth behind this (ADRs, fitness functions, diagrams) is scored separately in §34.)…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#17-devops--deployment","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"17. DevOps & Deployment","x":"Intent: Building, releasing, and provisioning are automated, repeatable, and safe. (The local developer experience / inner loop behind this (local orchestration, cross-repo dev,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#18-ui-architecture--component-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"18. UI Architecture & Component Design","x":"Intent: Components are cohesive, reusable, and composed cleanly, the UI has a deliberate structure, not page-sized blobs. Criteria - Container/presentational split: smart…","i":"EventCallback ShouldRender razor key"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#19-state-management--data-flow","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"19. State Management & Data Flow","x":"Intent: Client state has a clear owner and predictable flow; server state is cached and invalidated deliberately. Criteria - Single source of truth per piece of state; ownership…","i":"StateHasChanged IsDirty"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#20-design-system-theming--ui-consistency","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"20. Design System, Theming & UI Consistency","x":"Intent: A coherent visual language enforced by a component library, not re-implemented per screen. Criteria - Component library used consistently (e.g., MudBlazor): teams build…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#21-accessibility-a11y","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"21. Accessibility (a11y)","x":"Intent: The UI is usable by everyone, including assistive-technology users, and ideally enforced, not aspirational. Criteria - Semantic structure: correct…","i":"span div"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#22-responsive-design--cross-browserdevice","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"22. Responsive Design & Cross-Browser/Device","x":"Intent: The UI works across viewport sizes, input modes, and supported browsers. Criteria - Fluid/responsive layouts via the design system's grid/breakpoints; no fixed-width…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#23-front-end-performance--rendering","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"23. Front-End Performance & Rendering","x":"Intent: The UI loads and responds fast; rendering work is bounded. (Complements §12: this is the client side.) Criteria - Initial load: bundle/payload size controlled;…","i":"ShouldRender key"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#24-forms-validation--ux-safety","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"24. Forms, Validation & UX Safety","x":"Intent: Data entry is safe, forgiving, and consistent, users don't lose work or get confused by errors. Criteria - Validation parity: client-side validation for fast feedback…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#25-navigation-routing--information-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"25. Navigation, Routing & Information Architecture","x":"Intent: Users can find their way; routes are meaningful, guarded, and role-aware. Criteria - Route design: clean, bookmarkable, deep-linkable URLs; parameters typed and…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#26-front-end-security","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"26. Front-End Security","x":"Intent: The client doesn't become the weak link, XSS, token handling, and trust boundaries are correct. (Complements §11.) Criteria - Output encoding / XSS: no unsanitized HTML…","i":"MarkupString innerHTML"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#27-internationalization--localization","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"27. Internationalization & Localization","x":"Intent: The UI can be translated and respects culture, if in scope. (Score weight 0–1 if single-locale by design.) Criteria - Externalized strings: UI text in resource files, not…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#28-front-end-testing--quality","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"28. Front-End Testing & Quality","x":"Intent: The UI is verified at the right levels with stable, meaningful tests. (Complements §14.) Criteria - Component tests (e.g., bUnit) for rendering logic, parameters, events,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#29-resilience-reliability--business-continuity","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"29. Resilience, Reliability & Business Continuity","x":"Intent: The system survives partial failure and recovers from disaster within defined objectives. (Extends the resilience facets of §7/§12 into a first-class recovery story.)…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#30-compliance-privacy--data-governance","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"30. Compliance, Privacy & Data Governance","x":"Intent: Personal and regulated data is classified, governed, and handled lawfully across its lifecycle. (§11 defends against attackers; this answers to regulators.) Criteria -…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#31-cost-efficiency--finops","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"31. Cost Efficiency / FinOps","x":"Intent: Cloud spend is proportional to value and driven by data, not guesswork. (§17 mentions cost; this makes it a first-class axis.) Criteria - Right-sizing: compute/database…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#32-dependency--supply-chain-management","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"32. Dependency & Supply-Chain Management","x":"Intent: Third-party and inter-package dependencies are controlled, auditable, and evolve safely, especially critical for a framework that publishes packages. (Elevates §15's…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#33-developer-experience--inner-loop","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"33. Developer Experience & Inner Loop","x":"Intent: Developers build, run, test, and iterate locally with fast, low-friction feedback. (Promoted out of §17: that scores release/ops automation; this scores the inner loop.)…","i":"editorconfig local.props"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#34-architecture-governance--documentation","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"34. Architecture Governance & Documentation","x":"Intent: Decisions are recorded, conformance is enforced, and the system is documented so it stays coherent as it evolves. (Promoted out of §16: that scores the property of…","i":"CLAUDE.md"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#appendix-quick-scan-checklist","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"Appendix: Quick-Scan Checklist","x":"A 2-minute triage before the full evaluation: any \"no\" warrants a deeper look. - [ ] Can you draw the dependency graph and is it acyclic and inward-pointing? - [ ] Is the domain…"},{"u":"/docs/governance/common-ArchitectureScorecard.html","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.Common's architecture scores (replaces the former single-axis snapshot; see git…","i":"ResilienceCircuitBreakerFaultInjectionTests SpecificationsDoNotNavigateToOtherEntities CrossSourceSpecification.BuildAsync BaseIntegrationEvent.SchemaVersion AggregateRootEntityControllerBase ArchitectureEvaluationCriteria.md DomainInvariantViolationException LocalizedTextConventionTestsBase MMCA.Common.Testing.Architecture OpenIdConnectMetadataWarmupTask ResourceTranslationsAreComplete EventVersioningConventionTests"},{"u":"/docs/governance/common-ArchitectureScorecard.html#scorecard","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted column = Maturity·weight / Implementation·weight per row. Axis-gap findings: §17/§8 are mature-but-execution-deferred (mechanism shipped, deeper proof lives downstream);…","i":"SpecificationsDoNotNavigateToOtherEntities CrossSourceSpecification.BuildAsync BaseIntegrationEvent.SchemaVersion TransportDoesNotLeakIntoCoreLayers ArchitectureEvaluationCriteria.md CrossDataSourceDegradeConvention MMCA.Common.Testing.Architecture OpenIdConnectMetadataWarmupTask EventVersioningConventionTests ListPageQueryStateServiceTests PermissionAuthorizationHandler PiiErasureContractFitnessTests"},{"u":"/docs/governance/common-ArchitectureScorecard.html#indices","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Indices","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 314 ÷ 324 = 96.9% (unchanged on the twenty-seventh-wave re-score, 2026-08-14: no maturity score moved; the two proposed…"},{"u":"/docs/governance/common-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Dual-enforced Clean Architecture dependency rule (compile-time + fitness functions), §3 (impl 9): Source/Build/MMCA.Common.LayerEnforcement.targets:1-90 fails the build on…","i":"BaseIntegrationEvent.SchemaVersion MMCA.Common.Testing.Architecture EventVersioningConventionTests ResolveProjectReferences packages.lock.json FixedTimeEquals Result.Failure BeforeTargets Theory Fact"},{"u":"/docs/governance/common-ArchitectureScorecard.html#top-5-risks","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"Note (twenty-first wave, v1.121.0): earlier waves closed risks previously listed here (§29's restore drill, the §27 i18n train, §24 forms enforcement, §22's firefox gate, §23's…","i":"PiiErasureContractFitnessTests OutboxPollFilterProcessor NavigationContractTests required_status_checks PiiConventionTests CONTRIBUTING.md DEPLOYMENT.md IAnonymizable PiiRedactor COST.md main Pii"},{"u":"/docs/governance/common-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How this repo's quality relates to the other consumers (where the framework's quality propagates, where a repo diverges or under-uses it, and where a consumer does better than…"},{"u":"/docs/governance/common-RemediationBacklog.html","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md (canonical two-axis scoring: Maturity 96.9% / Implementation 84.8%, framework v1.152.0; the 2026-08-14 twenty-seventh-wave two-pass re-score…","i":"ArchitectureScorecard.md required_status_checks RedisDistributedLock IDistributedLock BenchmarkDotNet IsDirtyAccessor Performance baseline c911480 f292233 NoWarn verify"},{"u":"/docs/governance/common-RemediationBacklog.html#implementation-band-implementation--8-ranked-by-implpriority","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Implementation band (implementation <= 8, ranked by implPriority)","x":"Added 2026-07-28 when the ledger gained its second ranked axis. Until then implementation gaps were never ranked or scheduled, which is why consecutive steady-state cycles moved…","i":"ArchitecturalAnalysis.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-first-wave-2026-06-08","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: first wave (2026-06-08)","x":"Implemented in MMCA.Common, ✅ verified 2026-06-09: dotnet build -c Release is clean (0 warnings / 0 errors, all analyzers) and all 9 test projects pass (~1,611 tests, 0…","i":"MessageBusSettings.RetryLimit ConfigureBrokerTransport Directory.Packages.props IntegrationEventConsumer RetryMaxIntervalSeconds RetryMinIntervalSeconds DependencyVersionTests OutboxCleanupService UseMessageRetry MobileCardList BunitTestBase IAnonymizable"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-second-wave-2026-06-09","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: second wave (2026-06-09)","x":"✅ Verified: dotnet build -c Release clean (0/0) and all 9 test projects pass (1,511 tests, 0 failures). - ✅ 32 / 16: supply-chain. NuGet lock files (RestorePackagesWithLockFile,…","i":"RestorePackagesWithLockFile ServiceContractAttribute nuget.config CqrsMetrics WithMetrics AddMeter package Release dotnet snupkg build list"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-third-wave-front-end-2026-06-09","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: third wave (front-end, 2026-06-09)","x":"✅ Verified: build clean (0/0) and all 9 test projects pass (1,519 tests, 0 failures); UI tests 90 → 98 (8 new bUnit tests). - ✅ 19: UnsavedChangesGuard live-accessor. Added…","i":"Page.AssertNoAccessibilityViolationsAsync Deque.AxeCore.Playwright MobileInfiniteScrollList UnsavedChangesGuard MaxRenderedItems IsDirtyAccessor CurrentIsDirty PageLoading PageHeader Virtualize MMCATheme PageError"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-fourth-wave-breaking-changes--consumer-sweep-2026-06-09","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: fourth wave (breaking changes + consumer sweep, 2026-06-09)","x":"✅ Verified across all three repos (built/tested via local.props against Common source, no token): Common 1,523, ADC 1,241, Store 1,088 tests, 0 failures; all CI solutions build…","i":"AggregateConventionTests IntegrationEventConsumer UserNotification.Create EntityConventionTests OutboxCleanupService AddInboxMessages UserNotification BaseDomainEvent NoOpInboxStore InboxMessages EfInboxStore IDomainEvent"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-v1800-2026-06-26","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: v1.80.0 (2026-06-26)","x":"The single-axis backlog above is from the 2026-06-08/09 review (index 80%). The framework has since reached v1.82.0 and the canonical scoring was the in-repo, two-axis…","i":"PermissionAuthorizationHandler BaseDomainEvent.DateOccurred UserNotification.MarkAsRead PermissionRegistryBuilder AddAuthorizationPolicies ArchitectureScorecard.md GlobalRateLimitPartition PermissionPolicyProvider RateLimitPartitionTests RoleNames.ContentEditor UserNotification.ReadOn IPermissionRegistry"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-v1810v1820--governance-pass-2026-06-26","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: v1.81.0/v1.82.0 + governance pass (2026-06-26)","x":"Released since v1.80.0 (v1.81.0, v1.82.0) plus a sixth governance pass currently in flight (uncommitted). All of it lands in categories already scored 9-10, so the two-axis…","i":"ArchitectureEvaluationCriteria.md MMCA.Common.Aspire.Security SecurityHeadersMiddleware AddCommonSecurityHeaders ICspPolicyProvider MapCommonScalarUi Scalar.AspNetCore ValidAlgorithms RsaSha256 FACTS.md b9a6a28 COST.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-v1830v1840-2026-06-27","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: v1.83.0/v1.84.0 (2026-06-27)","x":"Released since v1.82.0 (v1.83.0, v1.84.0) plus a docs-only governance pass currently in flight (uncommitted). One score moved at this wave: §30 Implementation 7→8. The canonical…","i":"OpenIdConnectMetadataWarmupTask INotificationRecipientProvider ArchitectureScorecard.md IPushNotificationSender WarmupHostedService WarmupReadinessGate AddServiceDefaults PiiConventionTests PiiRedactorTests UserNotification IWarmupTask PiiRedactor"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-v1850-eighth-wave-under-8-implementation-remediation-2026-06-27","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress, v1.85.0 (eighth wave: under-8 Implementation remediation, 2026-06-27)","x":"The under-8 Implementation remediation (commit 78e5312, tag v1.85.0, HEAD 7082a5f) lifted every category scored Implementation one maturity score. Re-verified against current…","i":"MMCA.Common.Testing.Architecture ArchitectureRules.Slices.cs PasswordComplexityAttribute ArchitectureScorecard.md AuthModelValidationTests DataAnnotationsValidator ServiceContractAttribute TraceIdRatioBasedSampler SliceCohesionTestsBase ParentBasedSampler SliceCohesionTests NavigationFlow.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-v1860v1920-ninth-wave-i18n--re-score-2026-06-29","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress, v1.86.0→v1.92.0 (ninth wave: i18n + re-score, 2026-06-29)","x":"Re-scored against current source at framework v1.92.0 (HEAD 93ffcac, dirty tree). Canonical scoring is now Maturity 91.7% / Implementation 84.1% (was 92.8% / 85.0%) per the…","i":"PiiErasureContractFitnessTests WebApplicationExtensions.cs ArchitectureScorecard.md ConfigureBrokerTransport IntegrationEventConsumer User.PreferredCulture UseDelayedRedelivery cfg.UseMessageRetry PiiConventionTests DataSubjectSample PasswordHasher.cs IStringLocalizer"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-tenth-wave-focused-in-repo-remediation-2026-06-30","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: tenth wave (focused in-repo remediation, 2026-06-30)","x":"Four scores moved up on shipped, tested in-repo evidence; both indices rose for the first time in several waves: Maturity 91.7% → 92.9% (301/324), Implementation 84.1% → 84.9%…","i":"MMCA.Common.Testing.Architecture PaletteDark.PrimaryContrastText ResourceTranslationsAreComplete DatabaseRestoreDrillTests LocalizationResourceTests Directory.Packages.props PrimitivesSnapshotTests SupportedCultures.All PaletteDark.Primary WarningContrastText ErrorContrastText ACCESSIBILITY.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-eleventh-wave-adr-governance-2026-06-30","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: eleventh wave (ADR governance, 2026-06-30)","x":"No score moves. A full 34-category evidence re-score at framework v1.93.0 (HEAD 3e72bfa, dirty tree) re-confirmed every category at its tenth-wave value; indices hold at Maturity…","i":"AggregateRootEntityControllerBase EntityControllerBase OwnerOrAdminFilter OwnershipHelper Specification customer_id FACTS.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-twelfth-wave-under-8-implementation-lift-v1940-pending-2026-06-30","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: twelfth wave (under-8 Implementation lift, v1.94.0 pending, 2026-06-30)","x":"Two Implementation scores move up, Maturity holds: Implementation 84.9% → 85.3% (691/810), Maturity 92.9% (301/324) unchanged. Full Release build clean, 1685 tests pass. Held for…","i":"LocalizedTextConventionTestsBase ListPageQueryStateServiceTests SupportedCultures.PseudoLocale LocalizedTextConventionTests PseudoStringLocalizerFactory UseCommonRequestLocalization PseudoLocalizationE2ETests ListPageStateServiceTests LocalizationResourceTests PseudoLocalizer.Transform IStringLocalizerFactory PseudoLocalizationTests"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---fourteenth-wave-clean-tree-evidence-re-score-at-v11010-2026-07-03","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - fourteenth wave (clean-tree evidence re-score at v1.101.0, 2026-07-03)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.101.0 (HEAD 5e55be2, working tree clean: the recurring…","i":"ArchitectureScorecard.md FormsConventionTestsBase RegisterFormTests.cs Testing.Architecture Scalar.AspNetCore ValidationMessage FACTS.md slnx"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---defect-fix-wave-c-1c-7-2026-07-05","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - defect-fix wave C-1..C-7 (2026-07-05)","x":"Seven approved defect fixes, each behavior change landed with its pinning test flipped (or a new regression test) in the same change; build 0/0 and the full .slnx suite green.…","i":"Microsoft.Extensions.TimeProvider.Testing EntityServiceBase.GetAllForLookupAsync SessionCookieAuthenticationHandler OAuthControllerBase.CompleteAsync AuthenticatedServiceBase ChildEntityServiceBase LoginProtectionService LoggingQueryDecorator ITokenStorageService KeyNotFoundException OutboxCleanupService Uri.EscapeDataString"},{"u":"/docs/governance/common-RemediationBacklog.html#progress-sixteenth-wave-clean-tree-re-score-at-v11060-2026-07-06","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress: sixteenth wave (clean-tree re-score at v1.106.0, 2026-07-06)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.106.0 (HEAD 6f8b917, one commit past the v1.106.0 tag, working tree…","i":"ArchitecturalAnalysis.md ArchitectureScorecard.md Directory.Packages.props EncryptedStringConverter SECURITY.md FACTS.md b75fa8f Theory Fact"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---seventeenth-wave-evidence-re-score-at-v11080-2026-07-09","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - seventeenth wave (evidence re-score at v1.108.0, 2026-07-09)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.108.0 (git HEAD 6c3b3bc, working tree clean, one commit ahead of…","i":"ILiveChannelPublisher ACCESSIBILITY.md FACTS.md ci.yml"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---eighteenth-wave-runtime-performance-wave-2026-07-10","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - eighteenth wave (runtime performance wave, 2026-07-10)","x":"A cross-repo runtime-performance audit (4 parallel auditors: framework, ADC, Store, hosting/config) found the framework strong on read-path fundamentals (no-tracking, SQL…","i":"PublicEndpointOutputCachePolicy EFReadRepository.ApplyIncludes PooledConnectionLifetime HttpResilienceDefaults CachingQueryDecorator LocalView.FindEntry ExecuteUpdateAsync InProcessEventBus AllowAnonymous DetectChanges ExpandoObject CHANGELOG.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---remediation-wave-1-cross-repo-wave-plan-2026-07-11","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - remediation wave 1 (cross-repo wave plan, 2026-07-11)","x":"First wave of the 2026-07-11 cross-repo remediation plan (workspace plan file). Ships the shared §18/§19 fitness bases the ADC/Store maturity lifts need, closes the tenth-wave 20…","i":"StateManagementConventionTestsBase UIArchitectureConventionTestsBase ErrorMessages._localizer MobileInfiniteScrollList AllowedStaticMembers PrimaryContrastText WebVitalsCollector ErrorContrastText WebVitalsE2ETests DarkModeE2ETests NotificationBell CHANGELOG.md"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---eighteenth-wave-evidence-re-score-at-v11150-2026-07-12","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - eighteenth wave (evidence re-score at v1.115.0, 2026-07-12)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.115.0 (HEAD 37d0a3b, working tree clean, at the release tag). Three…","i":"ArchitectureScorecard.md MMCA.Common.UI.Maui PrimaryContrastText ErrorContrastText WebVitalsE2ETests DarkModeE2ETests MudDataGrid FACTS.md rgba"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twentieth-wave-evidence-re-score-at-v11170-2026-07-17","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twentieth wave (evidence re-score at v1.117.0, 2026-07-17)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.117.0 (HEAD 76d70cf, working tree clean). Four scores move.…","i":"ArchitectureScorecard.md NavigationContractTests required_status_checks AuthorizeAttribute NavigationFlow.md MMCA.Common.UI RouteAttribute RESPONSIVE.md FACTS.md bicep build Short"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-first-wave-evidence-re-score-at-v11210-2026-07-21","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-first wave (evidence re-score at v1.121.0, 2026-07-21)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.121.0 (HEAD 4a4fc05, working tree clean). One score moves.…","i":"ArchitectureScorecard.md required_status_checks BenchmarkDotNet CONTRIBUTING.md Notifications Performance baseline FACTS.md COST.md verify Short gate"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-second-wave-evidence-re-score-at-v11230-2026-07-23","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-second wave (evidence re-score at v1.123.0, 2026-07-23)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.123.0 (HEAD c911480, working tree clean). No score moves. Canonical…","i":"UnsavedChangesGuard.IsDirtyAccessor PiiErasureContractFitnessTests PasswordComplexityAttribute IIntegrationEventPublisher ArchitectureScorecard.md OpenApiContractTestsBase IConnectionMultiplexer EntityQueryPipeline IEventBus EditForm FACTS.md c911480"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-third-wave-evidence-re-score-at-v11280-2026-07-25","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-third wave (evidence re-score at v1.128.0, 2026-07-25)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.128.0 (HEAD 3dff29b, working tree clean). No score moves, the third…","i":"ArchitectureScorecard.md WebVitalsE2ETests ICommandHandler IQueryHandler pull_request permissions Unreleased FACTS.md TResult ci.yml github Result"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-fourth-wave-evidence-re-score-at-v11310-2026-07-28","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-fourth wave (evidence re-score at v1.131.0, 2026-07-28)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.131.0 (HEAD 2c52aa9, working tree clean). No score moves, the…","i":"ArchitectureScorecard.md OpenApiContractTestsBase AddCommonApiVersioning MMCA.Common.UI.Maui ICommandHandler ServiceContract AllowAnonymous AllowAnyOrigin IQueryHandler FACTS.md TResult CS1591"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-fifth-wave-evidence-re-score-at-v11350-2026-08-01","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-fifth wave (evidence re-score at v1.135.0, 2026-08-01)","x":"A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.135.0 (HEAD f292233, working tree clean). One score moves, ending…","i":"EntityQueryService.GetAllForLookupAsync DomainInvariantViolationException ArchitectureScorecard.md InProcessDistributedLock HttpResilienceDefaults IConnectionMultiplexer RedisDistributedLock NuGetAuditSuppress IdempotencyFilter IDistributedLock v1.128.0..HEAD AddCaching"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---twenty-sixth-wave-evidence-re-score-at-v11420-2026-08-07","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - twenty-sixth wave (evidence re-score at v1.142.0, 2026-08-07)","x":"Full 34-category two-pass re-score at HEAD 710d29d (clean tree). No scores move: 27 categories re-confirmed fresh, and seven first-pass lift proposals were refuted on the…","i":"GetAllForLookupAsync packages.lock.json AddMeter FACTS.md orderBy OrderBy secrets l.Name navbar NoWarn where"},{"u":"/docs/governance/common-RemediationBacklog.html#deferred---2026-07-19-full-review-recorded-not-scheduled","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deferred - 2026-07-19 full review (recorded, not scheduled)","x":"The 2026-07-19 full framework review shipped its accepted fixes on the review branch (rollback on business failure + post-commit dispatch, outbox leases + dead-letter visibility,…","i":"MMCA.Common.Infrastructure MMCA.Common.UI.Tests MMCA.Common.UI.Maui IServiceCollection IMessageBus LangVersion extension IsFailure preview TResult CS1591 NoWarn"},{"u":"/docs/governance/common-RemediationBacklog.html#recorded---2026-07-31-consumer-discovered-defect-not-scheduled","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Recorded - 2026-07-31 consumer-discovered defect (not scheduled)","x":"Found downstream while implementing MMCA.ADC BR-239 (public speaker visibility), which needed a filtered lookup read. Recorded rather than fixed in place: the consumer already…","i":"EntityQueryService.GetAllForLookupAsync MMCA.Common.Shared.ValueObjects.Email IRepository.GetAllForLookupAsync QueryFieldService.Validate InvalidOperationException GetOrBuildLookupSelector BaseLookup.Name nameProperty asTracking ToString orderBy OrderBy"},{"u":"/docs/governance/common-RemediationBacklog.html#priority-6-highest-leverage","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔴 Priority 6: highest leverage","x":"The package ships reusable Blazor primitives with no fast test tier. - ~~(medium) No component tests for the UI library~~ RESOLVED: Tests/Presentation/MMCA.Common.UI.Tests…","i":"Page.AssertNoAccessibilityViolationsAsync PiiErasureContractFitnessTests AuditableBaseEntity.Delete Deque.AxeCore.Playwright EncryptedStringConverter MobileInfiniteScrollList MMCA.Common.Testing.E2E MMCA.Common.Testing.UI MMCA.Common.UI.Tests OutboxCleanupService UnsavedChangesGuard DeleteConfirmation"},{"u":"/docs/governance/common-RemediationBacklog.html#priority-3-score-3-weight-3-one-rung-from-a-4","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟠 Priority 3: score 3, weight 3 (one rung from a 4)","x":"- ~~(medium) No broker retry policy on the extracted-microservice path~~ RESOLVED (re-verified 2026-06-29): ConfigureBrokerTransport applies cfg.UseMessageRetry (exponential) on…","i":"DomainAggregateRootsHaveNoPublicConstructors ResilienceCircuitBreakerFaultInjectionTests Add_DifferentCurrencies_ReturnsFailure HandleBeforeInternalNavigationAsync MobileInfiniteScrollListTests.cs AggregateRootsHaveResultFactory MessageBusSettings.RetryLimit AggregateConventionTestsBase DomainExposesAggregateRoots DomainFactoriesReturnResult RestorePackagesWithLockFile UnsavedChangesGuardTests.cs"},{"u":"/docs/governance/common-RemediationBacklog.html#priority-2-score-3-weight-2-polish--hardening","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟡 Priority 2: score 3, weight 2 (polish / hardening)","x":"- (medium) No consumer-side idempotency/inbox for at-least-once broker delivery: duplicate side effects possible in any non-idempotent consumer. (low) ~~Same misleading…","i":"EntityQueryPipeline.MaxUnboundedResultLimit ApplicationSettings.MaxPageSize MessageBusSettings.EnableInbox ArchitectureRules.Slices.cs MobileInfiniteScrollList OpenApiContractTestsBase ServiceContractAttribute Directory.Build.targets AddCommonApiVersioning required_status_checks SliceCohesionTestsBase MMCA.Common.UI.Maui"},{"u":"/docs/governance/common-RemediationBacklog.html#already-at-level-4-protect-dont-regress","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"✅ Already at level 4: protect, don't regress","x":"1 SOLID · 2 Design Patterns · 3 Clean Architecture · 5 Vertical Slice (maturity 3→4 on the slice-cohesion fitness function) · 7 Microservices Readiness · 8 Data Architecture · 10…","i":"MessageBusSettings.EnableInbox NavigationContractTests IConnectionMultiplexer required_status_checks WebVitalsE2ETests IDistributedLock BenchmarkDotNet EditorRequired Performance baseline navbar verify"},{"u":"/docs/governance/common-RemediationBacklog.html#deliberate--accepted-documented-caps-not-scheduled-work","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔒 Deliberate / accepted (documented caps, not scheduled work)","x":"Moved out of the active priority queue on 2026-07-02 (user-approved). Its computed priority = (4 − 2) × 2 = 4 is the highest weighted gap of any open category, but the unmet §31…","i":"NavigationFlow.md ACCESSIBILITY.md CONTRIBUTING.md NUGET_API_KEY RESILIENCE.md RESPONSIVE.md CHANGELOG.md release.yml SECURITY.md main.bicep CLAUDE.md README.md"},{"u":"/docs/governance/common-RemediationBacklog.html#mostly-consumer-assessed-the-shared-commonui-surface-is-scored-here","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"⚪ Mostly consumer-assessed (the shared Common.UI surface is scored here)","x":"21 Accessibility · 26 Front-End Security (Assessable mainly in consumer apps; 26 shared surface is covered under 11.) - 22 Responsive: CLOSED at Maturity 4 / Implementation 9…","i":"LocalizedTextConventionTests PseudoLocalizationE2ETests AuthModelValidationTests NavigationContractTests PasswordComplexity NavigationFlow.md RegisterFormTests ValidationMessage ResxMudLocalizer Forbidden EditForm slnx"},{"u":"/docs/governance/store-ArchitectureScorecard.html","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.Store's architecture scores. This is Store's first in-repo governance artifact…","i":"CK_InventoryItem_AvailableQuantity_NonNegative ArchitectureEvaluationCriteria.md ConstructorDependencyCountTests StateManagementConventionTests UIArchitectureConventionTests ObservabilityConventionTests MobileInfiniteScrollList PseudoLocalizationTests GracefulShutdownTests Money.ToDisplayString RemediationBacklog.md ServiceInfoController"},{"u":"/docs/governance/store-ArchitectureScorecard.html#executive-summary","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Executive summary","x":"MMCA.Store is a .NET 10.0 (LangVersion preview) DDD/Clean Architecture e-commerce system (Catalog, Sales, Identity modules; Stripe checkout) extracted into independently-hosted…","i":"MMCA.Common.Testing.Architecture IntegrationEventContractTests LocalizedTextConventionTests TreatWarningsAsErrors DataResidencyTests dbo.OutboxMessages PiiConventionTests Store_Identity Store_Catalog Store_Sales MMCAStore"},{"u":"/docs/governance/store-ArchitectureScorecard.html#scorecard","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted = Maturity·weight / Implementation·weight. Axis-gap finding: §21 Accessibility is honestly M3/I8 (the chromium axe gate earns Implementation 8; Maturity caps at 3…","i":"CK_InventoryItem_AvailableQuantity_NonNegative MMCA.Store.CrossService.IntegrationTests FrameworkVersionConsistencyTests IntegrationEventContractTests.cs ConstructorDependencyCountTests StateManagementConventionTests SQLitePCLRaw.bundle_e_sqlite3 UIArchitectureConventionTests CultureInfo.InvariantCulture LocalizedTextConventionTests ObservabilityConventionTests TranslationCompletenessTests"},{"u":"/docs/governance/store-ArchitectureScorecard.html#indices","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Indices","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 313 ÷ 320 = 97.8% (re-confirmed with no moves on the 2026-08-14 re-score; §12, §21, and §22 are the three categories at…"},{"u":"/docs/governance/store-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Clean Architecture + DDD + CQRS depth, fitness-enforced: §3/§4 (impl 9): inherited framework substance, guarded by the shared NetArchTest suite (23 test classes,…","i":"GracefulShutdownTests IAnonymizable"},{"u":"/docs/governance/store-ArchitectureScorecard.html#top-5-risks","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"1. Accessibility maturity is capped pending a human pass: §21 (mat 3, weight 3): the 23-scan axe suite gates the deploy (impl 8), but the rubric pairs axe-in-CI with a recorded…","i":"BrandColorTokenTests FormsConventionTests deploy.needs a1de5a89 MudForm"},{"u":"/docs/governance/store-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.Store: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How Store relates to MMCA.Common (the framework) and MMCA.ADC (the sibling consumer) is maintained once, for all three repos, in the workspace-internal…"},{"u":"/docs/governance/store-RemediationBacklog.html","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md (two-axis: Maturity 97.8% / Implementation 83.9%, full re-score 2026-07-28, re-confirmed with no score moves on the 2026-08-14 full…","i":"ArchitectureScorecard.md"},{"u":"/docs/governance/store-RemediationBacklog.html#priority-a11y--e2e-merge-gate-21-28-22","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔴 Priority: a11y / E2E merge gate (#21, #28, #22)","x":"The former single biggest maturity lever: 28 cleared 2026-07-03; 22 cleared on the 2026-07-17 re-score (the gate flip verified live) and reopened on the 2026-07-28 re-score when…","i":"github.event_name matrix.browser workflow_call deploy.needs deploy.yml browsers d057afc e2e.yml Theory needs Fact"},{"u":"/docs/governance/store-RemediationBacklog.html#priority-execution-quality-gaps-impl-not-maturity","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟠 Priority: execution-quality gaps (impl, not maturity)","x":"Ranked 2026-07-28 when the ledger gained its second ranked axis. Until then the items in this section were closed history plus two open levers, with no ranking and no inclusion…","i":"MMCA.Store.CrossService.IntegrationTests SqlServerIntegrationTestFixtureBase CultureInfo.InvariantCulture MobileInfiniteScrollList ProductVariantChanged NotifyStateChanged workflow_dispatch CatalogBrowse GetPagedAsync InventoryItem deploy.needs IsDrawerOpen"},{"u":"/docs/governance/store-RemediationBacklog.html#priority-minor--accept-or-polish","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟡 Priority: minor / accept-or-polish","x":"- [x] 32 Dependency & Supply-Chain, impl 7 → 8. DONE (2026-07-03, drift plan D8 + D9). Vulnerability gate is NuGetAudit + TreatWarningsAsErrors at restore, which fails…","i":"ServiceInfoController TreatWarningsAsErrors BrandColorTokenTests FormsConventionTests CustomerEmailRules NuGetAuditSuppress Store_Identity Store_Catalog Store_Sales ApiVersion Deprecated MMCAStore"},{"u":"/docs/governance/store-RemediationBacklog.html#defect-fix-wave-2026-07-05","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🐞 Defect-fix wave (2026-07-05)","x":"Four reviewed product defects fixed in one wave; every behavior change flipped its pinning test in the same change. - [x] S-1 Stripe network errors escaped the Result pattern.…","i":"Payment.Stripe.SessionRetrievalFailed Payment.Stripe.SessionCreationFailed Payment.Stripe.UnsupportedCurrency CartStateService.InitializeAsync ExportUserDataHandler HttpRequestException StripePaymentService CheckoutAndPayAsync DeleteUserHandler UserRole.IsAdmin CheckoutOutcome UserRole.Admin"},{"u":"/docs/governance/store-RemediationBacklog.html#deliberate--accepted-record-the-choice-dont-silently-leave-low","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate / accepted (record the choice; don't silently leave low)","x":"- ADR-042 device capability abstraction (latent, drift plan D8). The core extension point is converged (Store wires browser + MAUI capabilities via…","i":"AddBrowserDeviceCapabilities CultureInfo.InvariantCulture LocalizedTextConventionTests TranslationCompletenessTests UseMauiDeviceCapabilities Money.ToDisplayString ProductVariantChanged MMCA.Common.UI.Maui SliceCohesionTests DeepLinkListener ResxMudLocalizer DeviceUIModule"},{"u":"/docs/governance/store-RemediationBacklog.html#below-maturity-4-tracking-inclusion-policy-categories-scoring--4-maturity","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟠 Below-maturity-4 tracking (inclusion policy: categories scoring < 4 maturity)","x":"These categories score maturity 3; the ledger records them per its own line-4 inclusion policy (mirrors ADC's equivalent entries). - [x] 19 · State Management & Data Flow ·…","i":"StateManagementConventionTestsBase UIArchitectureConventionTestsBase StateManagementConventionTests UIArchitectureConventionTests ProductDetail.razor.cs OrderDetail.razor.cs ProductVariantsPanel StoreArchitectureMap OrderSummaryPanel OrderLinesPanel OPERATIONS.md sloAlertSpecs"},{"u":"/docs/governance/store-RemediationBacklog.html#resolved-this-cycle-2026-07-28-drift-wave-d1d2d5d6d7--e2e4e7e8","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Resolved this cycle (2026-07-28, drift wave: D1/D2/D5/D6/D7 + E2/E4/E7/E8)","x":"- [x] 29 Resilience: the DR drill was restoring a RETIRED database. The weekly dr-drill.yml had no rotation and fell through to its input default MMCAStore, the legacy archive no…","i":"AuthControllerBase.LoginAsync HandlerResultConventionTests PaymentReconciliationService DecoratorPipelineOrderTests PeriodicBackgroundService AddCommonRateLimiting skip_freshness_gates alertEmailAddress authIpPermitLimit Store_Identity RegisterAsync Store_Catalog"},{"u":"/docs/governance/store-RemediationBacklog.html#resolved-this-cycle-2026-07-25-performance-program-2","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Resolved this cycle (2026-07-25, performance program 2)","x":"Second evidence-led performance pass over Common/ADC/Store. Store's share shipped as two PRs plus the v1.127.0 framework sweep. - [x] Output cache shared across replicas. Catalog…","i":"AddStackExchangeRedisOutputCache Filter.Operator.NotSupported GetVariantCartInfoHandler BulkSetInventoryHandler IProductVariantService GetUnitPricesAsync IDistributedCache IntFilterStrategy OrderLines.Count PaymentInitiated EvictByTagAsync ProductVariants"},{"u":"/docs/governance/store-RemediationBacklog.html#resolved-this-cycle-2026-07-11-drift-convergence-drift-plan-d1-d13","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Resolved this cycle (2026-07-11 drift-convergence, drift plan D1-D13)","x":"- [x] 29 DR gates (drift plan D3). dr-freshness is now in deploy.needs (fails a deploy when the last successful dr-drill is stale), dr-drill.yml gained a weekly cron, and…","i":"ConstructorDependencyCountTests MMCA.Store.Gateway.Tests GracefulShutdownTests MMCA.Store.CI.slnf Store_Identity Store_Catalog workflow_call deploy.needs TimeProvider Store_Sales Directory db_owner"},{"u":"/docs/governance/store-RemediationBacklog.html#already-at-level-4-protect-dont-regress","d":"MMCA.Store: Architecture Remediation Backlog","k":"Architecture Governance","t":"✅ Already at level 4 (protect, don't regress)","x":"Both axes satisfied (maturity 4 AND implementation = 9), the true protect list: SOLID (1), Design Patterns (2), Clean Architecture (3), DDD (4), Data (8), API (9), Observability…","i":"CK_InventoryItem_AvailableQuantity_NonNegative FormsConventionTests IQueryable"},{"u":"/docs/guides/index.html","d":"Guides & Specifications","k":"Guides & Specifications","x":"The narrative documentation for the MMCA platform: adoption guides, business specifications, workflow analyses, and per-concern reference notes. Files are prefixed by the repo…"},{"u":"/docs/guides/index.html#framework-mmcacommon","d":"Guides & Specifications","k":"Guides & Specifications","t":"Framework (MMCA.Common)","x":"- Getting Started: stand up a new application from the MMCA.Templates scaffold, in six steps. - Build MMCA.ECommerce: the two-module store sample (Products + Orders) built end to…","i":"MMCA.Templates"},{"u":"/docs/guides/index.html#mmcastore-e-commerce","d":"Guides & Specifications","k":"Guides & Specifications","t":"MMCA.Store (e-commerce)","x":"- Business Specification - Business Workflow Analysis - Navigation Flow - Manual Screen-Reader Pass Runbook"},{"u":"/docs/guides/index.html#mmcaadc-conference","d":"Guides & Specifications","k":"Guides & Specifications","t":"MMCA.ADC (conference)","x":"- Business Specifications - Navigation Flow - Manual Screen-Reader Pass Runbook - Integration-Test Tier Rework Plan Related reading: the Architecture Decision Records and the…"},{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","x":"The automated layer (axe-core WCAG 2.1 AA scans in Tests/E2E/MMCA.ADC.E2E.Tests/AccessibilityTests.cs plus the shared Login/Register/Profile bases in MMCA.Common.Testing.E2E)…","i":"MMCA.Common.Testing.E2E RemediationBacklog.md"},{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#tools","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Tools","x":"Run against the app started via Aspire (dotnet run --project Source/Hosting/MMCA.ADC.AppHost), reaching the UI through the Gateway. Test with the keyboard only (no mouse) for the…","i":"dotnet start Ctrl Alt Cmd run"},{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#what-to-verify-on-every-flow","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"What to verify on every flow","x":"1. Landmarks and headings. The SR's landmark/heading list (NVDA Insert+F7) exposes a logical banner / navigation / main structure and a sensible h1...hN outline (one h1 per…","i":"MainLayout.razor navigation h1...hN banner Insert Shift main Tab"},{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#results-log","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Results log","x":"Record one row per pass. A flow with any unresolved blocker is a FAIL (open a backlog item for it)."},{"u":"/docs/guides/adc-ACCESSIBILITY-SCREENREADER-PASS.html#after-a-pass","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"After a pass","x":"- File any defect as a remediation item; cross-link it from the §21 row's evidence. - Update the dated row above so the scorecard can cite a real, current manual pass (this is…"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","x":"Status: complete (Phase 4 broker-transport tier landed 2026-07-06; Phase 5 residual = coverlet only). - Phase 0 ✅: Tests/WebAPI revived as MMCA.Common.API middleware unit tests…","i":"Microsoft.Testing.Extensions.CodeCoverage ISessionBookmarkValidationService IdentityIntegrationTestFixture SpeakerUnlinkedFromUserHandler AnonymousConferenceReadTests SpeakerLinkedToUserHandler MMCA.ADC.Integration.slnf MMCA.ADC.IntegrationTests IIntegrationEventHandler AddForwardedJwtBearer AttendeeBookmarkTests IBookmarkCountService"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#recommended-strategy-two-tiers","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Recommended strategy: two tiers","x":"1. Primary: per-service WebApplicationFactory : one in-process host per service (Identity / Conference / Engagement), cross-service edges mocked. AddBrokerMessaging…","i":"DistributedApplicationTestingBuilder SpeakerUnlinkedFromUser WebApplicationFactory SpeakerLinkedToUser AddBrokerMessaging UserRegistered Program"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#three-code-facts-that-shape-the-rework-verified","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Three code facts that shape the rework (verified)","x":"- Only Conference.Service is WAF-incompatible: it ends with StartAsync() + self-HTTP/2 WarmupViaHttpAsync + WaitForShutdownAsync(). Identity/Engagement/Notification use…","i":"AddCommonAuthentication AddForwardedJwtBearer WebApplicationFactory WaitForShutdownAsync Conference.Service WarmupViaHttpAsync JwtTokenGenerator IssuerSigningKey JwtBearerOptions app.RunAsync StartAsync authority"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#databases","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Databases","x":"- SQLite in-memory for the fast bulk tier (no Docker, CI-friendly; DatabaseInitStrategy=EnsureCreated). - MsSql Testcontainers for a tagged SQL-fidelity subset (soft-delete…","i":"SQLServerDbContext DataSources migrations"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#project-structure","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Project structure","x":"- One WAF test project per service (MMCA.ADC.{Identity,Conference,Engagement}.IntegrationTests): can't reference two Program-bearing hosts in one project. - One…","i":"MMCA.ADC.CrossService.IntegrationTests IntegrationTestBase MMCA.Common.Testing JwtTokenGenerator IntegrationTests ProjectReference MMCA.Common.API WebAPI.Tests Conference Engagement Identity MMCA.ADC"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#ci","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"CI","x":"- Add the SQLite per-service tier to CI.slnf (seconds, no Docker) → restores the authz/CRUD merge gate (11) with no workflow change. - Keep the container-based MsSql + RabbitMQ…","i":"CI.slnf"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#phased-sequencing-fastest-win-first","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Phased sequencing (fastest win first)","x":"- Phase 0: re-home WebAPI.Tests middleware unit tests; drop the dead WebAPI reference; re-add to slnx+CI.slnf. ~16 tests green; removes a non-building project (16). - Phase 1:…","i":"ISessionBookmarkValidationService IBookmarkCountService OwnerOrAdminFilter ServiceTestFixture JwtBearerOptions AttendeeClaims OrganizerUser WebAPI.Tests TProgram CI.slnf slnx"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#key-risks","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Key risks","x":"- The non-Identity JwtBearerOptions in-process override is the trickiest piece: prove it on one Conference auth test before fanning out. - SQLite vs SQL-Server fidelity (owned…","i":"JwtBearerOptions"},{"u":"/docs/guides/adc-IntegrationTestReworkPlan.html#critical-files","d":"Integration-Test Tier Rework Plan (RemediationBacklog #14)","k":"Guides & Specifications","t":"Critical files","x":"- Tests/Integration/MMCA.ADC.IntegrationTests/Infrastructure/TestWebApplicationFactory.cs (combined-host factory → split into per-service fixtures; its JWT config block is the…","i":"AddCommonAuthentication AddForwardedJwtBearer JwtTokenGenerator.cs MMCA.ADC.CI.slnf MMCA.ADC.slnx StartAsync partial Program public class"},{"u":"/docs/guides/adc-NavigationFlow.html","d":"Navigation Flow","k":"Guides & Specifications","x":"This document maps the site navigation flow for each actor in the MMCA.ADC application. Each mermaid diagram shows the pages accessible to that actor and the directional…"},{"u":"/docs/guides/adc-NavigationFlow.html#actors","d":"Navigation Flow","k":"Guides & Specifications","t":"Actors","x":"Roles & menu: Organizer is the only elevated role (default is Attendee). A Speaker is an attendee whose account is linked to a Speaker, surfaced via the speakerid claim. The left…","i":"IUIModule.NavItems speaker_id Organizer Attendee"},{"u":"/docs/guides/adc-NavigationFlow.html#1-anonymous-user","d":"Navigation Flow","k":"Guides & Specifications","t":"1. Anonymous User","x":"Pages accessible without authentication: home, login, register, and all public conference pages. ---"},{"u":"/docs/guides/adc-NavigationFlow.html#2-attendee-authenticated-user","d":"Navigation Flow","k":"Guides & Specifications","t":"2. Attendee (Authenticated User)","x":"Inherits all anonymous pages. Gains access to profile, feedback submission, and session bookmarking. Unauthenticated visitors are redirected to login. ---"},{"u":"/docs/guides/adc-NavigationFlow.html#3-speaker","d":"Navigation Flow","k":"Guides & Specifications","t":"3. Speaker","x":"Inherits all attendee pages. Gains access to the speaker dashboard for managing their own profile, viewing assigned sessions, and reviewing feedback ratings. ---"},{"u":"/docs/guides/adc-NavigationFlow.html#4-organizer","d":"Navigation Flow","k":"Guides & Specifications","t":"4. Organizer","x":"Authenticated users with the Organizer role. Inherits all attendee and public pages. Adds CRUD management for every conference entity (events, sessions, speakers, categories,…","i":"Organizer"},{"u":"/docs/guides/adc-NavigationFlow.html#5-functionality-flows-attendee--speaker","d":"Navigation Flow","k":"Guides & Specifications","t":"5. Functionality Flows (Attendee & Speaker)","x":"The diagrams in sections 1-4 map which pages each actor can reach. The diagrams below map how attendees and speakers accomplish each functionality, including inline actions…","i":"DeviceUIModule speaker_id route"},{"u":"/docs/guides/adc-NavigationFlow.html#navigation-patterns","d":"Navigation Flow","k":"Guides & Specifications","t":"Navigation Patterns","x":"- Unauthenticated users accessing protected pages are redirected to /login via the RedirectToLogin component. - Successful login/register redirects to Home (/) with a full page…","i":"RegisteredUser_AdminPages_ShouldBeForbidden Engagement.CheckIn IUIModule.NavItems Engagement.Points EventList.razor RedirectToLogin DeviceUIModule UserList.razor Routes.razor speaker_id attribute Authorize"},{"u":"/docs/guides/adc-specifications.html","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","x":"---"},{"u":"/docs/guides/adc-specifications.html#1-system-overview","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"1. System Overview","x":"ADC is a conference management system for the Atlanta Developers Conference. It provides backend services to manage multi-day conference events, sessions, speakers, rooms,…"},{"u":"/docs/guides/adc-specifications.html#2-domain-model","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"2. Domain Model","x":"Relationships: - Owns many Rooms (child entities) - Owns many EventSpeakers (child join entities linking Event ↔ Speaker) - Owns many EventQuestionAnswers (child feedback…","i":"Engagement.LivePolls Engagement.SessionQA User.LinkedSpeakerId Event.StartDate Session.EventId ContentEditor Event.EndDate EventSpeaker QuestionType Waitlisted Nominated Organizer"},{"u":"/docs/guides/adc-specifications.html#3-business-rules","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"3. Business Rules","x":"Reading guide: Some rules reference other rules defined later in the document (e.g., BR-63, BR-80 are defined in Section 10). Forward references use the BR- numbering…","i":"Event.QuestionModerationDefault Event.LastSessionizeRefreshBy Event.LastSessionizeRefreshOn SessionQuestionAnswerChanged SpeakerQuestionAnswerChanged EventQuestionAnswerChanged SessionCategoryItemChanged SpeakerCategoryItemChanged UserSessionBookmarkChanged Session.AccessibilityInfo Session.IsServiceSession SessionFeedbackSubmitted"},{"u":"/docs/guides/adc-specifications.html#4-use-cases--business-processes","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"4. Use Cases / Business Processes","x":"See UC-30 (Registration) and UC-31 (Login) in Section 12.2 for the current email + password authentication flows. --- Actors: Attendee, API consumer Preconditions: None (read…","i":"SpeakerQuestionAnswersController Event.LastSessionizeRefreshBy Event.LastSessionizeRefreshOn SpeakerQuestionAnswerChanged Engagement.LivePolls Engagement.SessionQA UserSessionBookmark skippedSoftDeleted IsServiceSession IsPlenumSession AllowAnonymous QuestionEntity"},{"u":"/docs/guides/adc-specifications.html#5-workflows--state-transitions","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"5. Workflows & State Transitions","x":"The Session.Status field is a free-text string imported from Sessionize. Default: null (for manually created sessions). Known Sessionize values: Accepted, Waitlisted, Accept…","i":"Session.Status ContentEditor IsConfirmed IsInformed Waitlisted Nominated Organizer Accepted Declined Decline Accept Queue"},{"u":"/docs/guides/adc-specifications.html#6-events--side-effects","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"6. Events & Side Effects","x":"Domain events are raised for entity mutations. Not all events have registered handlers: events without handlers serve as extension points for future requirements. Note: Only…","i":"SessionQuestionAnswerChanged SpeakerQuestionAnswerChanged EventQuestionAnswerChanged SessionCategoryItemChanged SpeakerCategoryItemChanged UserSessionBookmarkChanged SessionSpeakerChanged User.LinkedSpeakerId CategoryItemChanged EventSpeakerChanged UserPasswordChanged CategoryChanged"},{"u":"/docs/guides/adc-specifications.html#7-business-constraints--invariants","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"7. Business Constraints & Invariants","x":"---","i":"Speaker.LinkedUserId User.LinkedSpeakerId IsServiceSession Session.EventId QuestionEntity ContentEditor EventSpeaker nameProperty Waitlisted CreatedBy FirstName Nominated"},{"u":"/docs/guides/adc-specifications.html#8-external-integrations-business-perspective","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"8. External Integrations (Business Perspective)","x":"---","i":"Speaker.ProfilePicture Event.VenueMapUrl IsServiceSession IsPlenumSession QuestionSource SessionizeCode IsTopSpeaker RecordingUrl LiveUrl POST"},{"u":"/docs/guides/adc-specifications.html#9-glossary","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"9. Glossary","x":"---","i":"Event.IsPublished IsServiceSession IsPlenumSession ContentEditor IsTopSpeaker Organizer User.Role Admin Role true"},{"u":"/docs/guides/adc-specifications.html#ddd-structural-summary","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"DDD Structural Summary","x":"Why three bounded contexts instead of two: The original Events + Identity split grouped all conference-related entities together regardless of write profile. Separating…","i":"SessionQuestionAnswer SpeakerQuestionAnswer EventQuestionAnswer Question.IsRequired Session.EventId QuestionEntity SpeakerChanged ContentEditor QuestionType Room.EventId RoomChanged Organizer"},{"u":"/docs/guides/adc-specifications.html#10-specification-clarifications--addenda","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"10. Specification Clarifications & Addenda","x":"This section addresses gaps, ambiguities, and implicit design decisions identified during implementation review. New business rules are numbered BR-61+. API contract…","i":"SessionQuestionAnswersController SpeakerQuestionAnswersController TimeZoneInfo.ConvertTimeFromUtc EventQuestionAnswersController MMCA.ADC.Modules.Engagement RemoveSpeakerQuestionAnswer UpdateSpeakerQuestionAnswer AddSpeakerQuestionAnswer SessionFeedbackSubmitted EventFeedbackSubmitted CreateQuestionHandler SessionQuestionAnswer"},{"u":"/docs/guides/adc-specifications.html#11-api-contract-specifications","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"11. API Contract Specifications","x":"This section documents API design decisions that apply across all endpoints. --- All error responses use the RFC 9457 ProblemDetails format (the successor to RFC 7807, same…","i":"SessionQuestionAnswer SpeakerQuestionAnswer EventQuestionAnswer PaginationMetadata Session.Duration Speaker.FullName DomainException includeChildren FirstRowOnPage LastModifiedOn QuestionEntity TotalPageCount"},{"u":"/docs/guides/adc-specifications.html#12-authentication--identity-architecture","d":"ADC (Atlanta Developers Conference) - Business Specifications","k":"Guides & Specifications","t":"12. Authentication & Identity Architecture","x":"This section defines the authentication mechanism for both the Web UI (Blazor) and MAUI (mobile) clients, which share a common Razor class library. It replaces the device-based…","i":"CascadingAuthenticationState AuthenticationStateProvider Speaker.LinkedUserId User.LinkedSpeakerId UserPasswordChanged RefreshTokenExpiry UserIdentifierType currentPassword LinkedSpeakerId AllowAnonymous LastModifiedBy LastModifiedOn"},{"u":"/docs/guides/common-ACCESSIBILITY.html","d":"Accessibility (rubric §21)","k":"Guides & Specifications","x":"The shared MMCA.Common.UI surface targets WCAG 2.1 AA. Accessibility is enforced two ways: an automated axe-core gate in CI (the bulk of coverage) and a documented manual…","i":"MMCA.Common.UI"},{"u":"/docs/guides/common-ACCESSIBILITY.html#automated-coverage-axe-core-wcag-21-aa","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Automated coverage (axe-core, WCAG 2.1 AA)","x":"The ui-e2e CI job runs Playwright + axe-core against the backend-less gallery; chromium is the blocking merge gate (firefox/webkit advisory). Scanned states: Component render is…","i":"PrimitivesSnapshotTests RegisterPageE2ETests PrimaryContrastText ErrorContrastText DarkModeE2ETests PageLoadingState MMCA.Common.UI progressbar mmca_theme div"},{"u":"/docs/guides/common-ACCESSIBILITY.html#manual-screen-reader-pass","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Manual screen-reader pass","x":"Automation cannot judge reading order, focus management, or announcement quality, so the shared surface is walked manually. Checklist (re-run on any change to MainLayout, the…","i":"ValidationMessage MainLayout.razor PageLoadingState MainLayout EditForm main"},{"u":"/docs/guides/common-ACCESSIBILITY.html#known-limitations-tracked","d":"Accessibility (rubric §21)","k":"Guides & Specifications","t":"Known limitations (tracked)","x":"- ~~Dark-mode contrast (§20, not §21).~~ RESOLVED (2026-07-11). The two dark-palette WCAG AA contrast failures the prototype scan flagged (filled-primary button label ~2.65:1 on…","i":"PaletteDark.PrimaryContrastText WarningContrastText ErrorContrastText DarkModeE2ETests EF5350 rgba"},{"u":"/docs/guides/common-BUILD-BY-HAND.html","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","x":"This is the long-form walkthrough: every project, every file, and every load-bearing line that goes into an application on the MMCA.Common framework, in the order you would…","i":"Contoso.Support Tickets dotnet Orders Order new"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#what-you-will-build","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"What you will build","x":"A modular monolith with one business module and two hosts: - Orders (your business module): an Order aggregate with OrderComment children, opened through a Result-returning…","i":"AllowAnonymous OrderComment Result Order sql web"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-0-prerequisites-and-decisions","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 0: Prerequisites and decisions","x":"Install: - .NET 10 SDK (the framework targets net10.0 with LangVersion: preview for C extension types). - SQL Server reachable locally (LocalDB, a container, or the one Aspire…","i":"FrameworkVersionConsistencyTestsBase Directory.Packages.props MMCA.Common.API UseLocalMMCA LangVersion local.props install net10.0 package preview CS0103 dotnet"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-1-create-the-solution-and-the-build-plumbing","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 1: Create the solution and the build plumbing","x":"Scaffolded. dotnet new mmca-app writes every file in this phase. Read it to know what each one does; you do not need to type any of it. The plumbing files are the load-bearing,…","i":"Directory.Packages.props Directory.Build.props Contoso.Support.slnx local.props.template OrderIdentifierType PackageReference MMCA.Helpdesk auditSources editorconfig nuget.config global.json Contracts"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-2-scaffold-the-module-project-set","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 2: Scaffold the module project set","x":"Scaffolded. dotnet new mmca-app creates this project set for your first module, and pwsh build/add-module.ps1 adds another one later: it drives dotnet new mmca-module and then…","i":"Contoso.Support.Orders.Infrastructure Contoso.Support.Orders.Application Contoso.Support.Orders.Domain Contoso.Support.Orders.Shared Contoso.Support.Orders.API MMCA.Common.Infrastructure MMCA.Common.Application MMCA.Common.Domain MMCA.Common.Shared AddErrorResources MMCA.Common.API AllowAnonymous"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-3-the-vertical-slice-end-to-end-the-heart-of-it","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 3: The vertical slice end-to-end (the heart of it)","x":"Scaffolded. The generated module already contains this slice and six more, worked end to end. dotnet new mmca-command and dotnet new mmca-query add another one. This phase is the…","i":"EntityTypeConfigurationSQLServer ConcurrencyConventionTestsBase AddModuleOrdersInfrastructure ScanModuleApplicationServices AuditableAggregateRootEntity OrderOpenedIntegrationEvent OrderCommentIdentifierType IUnitOfWork.GetRepository AddApplicationDecorators IIntegrationEventHandler DomainEventDispatcher EntityControllerBase"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-4-dbcontext-model-and-migrations","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 4: DbContext model and migrations","x":"Partly scaffolded. The migrations project and its design-time factory are generated. Running dotnet ef migrations add InitialCreate is still yours, and for a module added later…","i":"ApplicationSettings.DatabaseInitStrategy InitializeDatabaseAsync SQLServerDbContext EnsureCreated InitialCreate DataSources migrations Migrate dotnet None add"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-5-compose-the-monolith-host-and-run-it","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 5: Compose the monolith host and run it","x":"Scaffolded. Both hosts, the AppHost, and the .resx pairs are generated. Read this phase before you touch any of them: the DI sequence, WaitFor(sql) rather than the database…","i":"MmcaCultureBootstrap.SetBrowserCultureAsync LocalizedTextConventionTestsBase LocalizationResourceTestsBase UseCommonRequestLocalization OrderOpenedIntegrationEvent UseCommonMiddlewarePipeline services.AddErrorResources AddApplicationDecorators YourModuleErrorResources EnsureSuccessStatusCode EndpointCultureApplier UseRequestLocalization"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-6-tests-and-the-architecture-fitness-map","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 6: Tests and the architecture-fitness map","x":"Scaffolded, with one deliberate gap. All three test projects and the map are generated. The IntegrationEventContractTests subclass is NOT: its frozen literal lists members…","i":"FrameworkVersionConsistencyTestsBase ConstructorDependencyCountTestsBase IntegrationEventContractTestsBase LocalizedTextConventionTestsBase MMCA.Common.Testing.Architecture SpecificationConventionTestsBase MicroserviceExtractionTestsBase ConcurrencyConventionTestsBase ControllerConventionTestsBase IntegrationEventContractTests LocalizationResourceTestsBase HandlerConventionTestsBase"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-7-upgrading-the-framework-version","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 7: Upgrading the framework version","x":"Not scaffolded. dotnet new mmca-app --framework-version picks the version you START on; moving to a later one is this phase. When a new MMCA.Common release ships, upgrade in one…","i":"FrameworkVersionConsistencyTestsBase Directory.Packages.props packages.lock.json UseLocalMMCA local.props your.slnx restore dotnet new"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#phase-8-extract-a-module-into-its-own-service-the-payoff","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Phase 8: Extract a module into its own service (the payoff)","x":"Not scaffolded. The generated solution carries the plumbing (the .Contracts proto convention and the .Service OpenAPI block in Directory.Build.props), but the extraction itself…","i":"GrpcResultExceptionInterceptor OrderOpenedIntegrationEvent MMCA.Common.Aspire.Hosting WithSQLServerDataSource AddGrpcServiceDefaults Directory.Build.props RequestVersionExact AddTypedGrpcClient WithJwksDiscovery MMCA.Common.Grpc Support_Identity OutboxMessages"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#verification-checklist","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Verification checklist","x":"1. Build green: dotnet build Contoso.Support.slnx with no warnings (TreatWarningsAsErrors + five analyzers). This is the primary automatable gate. 2. Unit + architecture tests…","i":"OrderOpenedIntegrationEvent Contoso.Support.slnx IArchitectureMap OutboxMessages InitialCreate OrderComment migrations AppHost dotnet build Order test"},{"u":"/docs/guides/common-BUILD-BY-HAND.html#where-to-look-next","d":"Building on MMCA.Common by Hand","k":"Guides & Specifications","t":"Where to look next","x":"- Getting Started: the one-command path that writes phases 1 through 6 for you. If you are starting a new solution rather than adding the framework to an existing one, that is…","i":"CLAUDE.md README.md Helpdesk Tickets Ticket"},{"u":"/docs/guides/common-COST.html","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","x":"MMCA.Common is a library, so it cannot provision anything: right-sizing, scale rules, budgets, and per-service cost attribution live in the consumer apps' IaC (e.g. MMCA.ADC's…"},{"u":"/docs/guides/common-COST.html#what-the-framework-does-for-cost","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"What the framework does for cost","x":"- Telemetry ingestion is the real line item, so high-volume / low-value spans are dropped. OutboxPollFilterProcessor (MMCA.Common.Aspire) suppresses the recurring OutboxPoll…","i":"http.client.open_connections OutboxPollFilterProcessor TraceIdRatioBasedSampler ConfigureOpenTelemetry OutboxCleanupService AddServiceDefaults MMCA.Common.Aspire ParentBasedSampler SocketsHttpHandler request.duration active_requests AppDependencies"},{"u":"/docs/guides/common-COST.html#recommended-consumer-defaults-set-these-downstream","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Recommended consumer defaults (set these downstream)","x":"- Telemetry retention & sampling. Tune Log Analytics retention to the minimum the consumer's compliance window allows, and set Telemetry:TracesSampleRatio (the built-in…"},{"u":"/docs/guides/common-COST.html#cost-attribution--guardrail-samples-distilled-from-mmcaadc","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Cost-attribution & guardrail samples (distilled from MMCA.ADC)","x":"These belong in the consumer's IaC, not the library, but the framework documents the shape so every consumer attributes spend and guards surges the same way. The worked, deployed…"},{"u":"/docs/guides/common-COST.html#out-of-scope-for-the-framework-by-design","d":"Cost & FinOps Notes (rubric §31)","k":"Guides & Specifications","t":"Out of scope for the framework (by design)","x":"Provisioning, scale rules, budgets, per-service cost attribution, and surge/revert automation are consumer/IaC concerns and are not added to the library: see also ADR-009…"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","x":"MMCA.ECommerce is the simplest e-commerce application on the MMCA.Common framework: a Products catalog module and an Orders module with line items, behind a REST API host and a…","i":"MMCA.Templates dotnet new"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#before-you-start","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Before you start","x":"- .NET 10 SDK (the framework targets net10.0 with LangVersion: preview). - Docker Desktop (Aspire provisions SQL Server as a container). - EF Core tools: dotnet tool install…","i":"MMCA.Templates LangVersion install net10.0 preview dotnet pwsh tool ps1"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#2-generate-the-solution-with-the-products-module","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"2. Generate the solution with the Products module","x":"Five options do most of this guide's old work. Three remove an axis a catalog product does not have, and the code for an axis you turn off is never generated: --flat drops the…","i":"ProductCreatedIntegrationEvent ProductCreatedHandler RequesterUserId Created Opened Name"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#3-add-the-orders-module","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"3. Add the Orders module","x":"build/add-module.ps1 ships inside the solution you just generated. It runs dotnet new mmca-module with the shape options passed through, then performs every wire-up the template…","i":"ECommerceArchitectureMap.cs SQLServerMigrationsAssembly services.AddErrorResources OrderItemIdentifierType WithSQLServerDataSource Directory.Build.props OrdersErrorResources MMCA.ECommerce.slnx ChangeItemQuantity ECommerce_Products appsettings.json ProjectReference"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#4-reshape-products-into-a-catalog-product","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"4. Reshape Products into a catalog product","x":"The scaffolded module arrives as the template's worked example in your namespaces, already shaped by the flags in step 2: no children, no status, no requester, Name instead of…","i":"UpdateRequestsAreConcurrencyAware Microsoft.EntityFrameworkCore Product.Description.TooLong ModuleApplicationDbContext ProductCreateRequestMapper DomainEntityState.Updated Product.Description.Empty Directory.Packages.props DependencyInjection.cs TreatWarningsAsErrors Product.InvalidPrice Product.Name.TooLong"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#5-reshape-orders-into-an-order-with-line-items","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"5. Reshape Orders into an order with line items","x":"Orders keeps the child-collection pattern the template scaffolded, retargeted. -Child Item already did the naming (the entity is OrderItem, the slices are AddItem / EditItem /…","i":"UpdateRequestsAreConcurrencyAware Order.Item.ProductName.TooLong Total_ExcludesSoftDeletedItems EnsureStatusAllowsItemChanges Microsoft.EntityFrameworkCore Order.InvalidStatusTransition Order.Item.ProductName.Empty ChangeOrderStatusRequest.cs OrderPlacedIntegrationEvent ModuleApplicationDbContext Order.CustomerName.TooLong ChangeItemQuantityCommand"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#6-point-the-ui-at-the-new-domain","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"6. Point the UI at the new domain","x":"The scaffolded Blazor host already has the load-bearing parts: the typed ECommerceApiClient calling the API server-side through Aspire service discovery (no CORS, no token), the…","i":"MMCA.ECommerce.Orders.Shared string.IsNullOrWhiteSpace Snackbar.RequiredFields Dialog.Delete.Heading System.Globalization GetProductsAsync ProjectReference missingRequired SectionHeading PageHeading es.resx _field"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#7-create-the-migrations","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"7. Create the migrations","x":"Neither module has a migration yet: any shape flag makes mmca-app drop the template's sample one (it described the sample shape), and -SkipMigration deferred the Orders one to…","i":"editorconfig migrations Migrations dotnet add"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#8-the-two-one-time-fixups-then-run-it","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"8. The two one-time fixups, then run it","x":"Apply the two fixups the scaffold deliberately leaves to you (they are name-dependent, so no generated value could be right). First, sort the using directives and the identifier…","i":"ProductCreatedIntegrationEvent IntegrationEventContractTests OrderPlacedIntegrationEvent ArchitectureTests.cs AllowAnonymous editorconfig SCAFFOLD IDE0021 SA1210 SA1211 DELTA Open"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#verification-checklist","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Verification checklist","x":"1. Baseline green immediately after mmca-app, before any edit: 81 tests. 2. After build/add-module.ps1: still green at 99 tests, both modules' scaffolded suites running. 3. After…","i":"MMCA.ECommerce.slnx OutboxMessages InitialCreate dotnet build test"},{"u":"/docs/guides/common-ECOMMERCE-SAMPLE.html#where-to-look-next","d":"Build MMCA.ECommerce: a Two-Module Store from the Templates","k":"Guides & Specifications","t":"Where to look next","x":"- MMCA.ECommerce: the finished result of this guide, build- and test-verified. - Getting started: the single-module path, the vertical-slice templates (mmca-command /…"},{"u":"/docs/guides/common-GETTING-STARTED.html","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","x":"MMCA.Common is a .NET 10 framework for DDD, Clean Architecture, and CQRS, shipped as a set of lockstep-versioned NuGet packages (the authoritative list and count live in…"},{"u":"/docs/guides/common-GETTING-STARTED.html#before-you-start","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Before you start","x":"- .NET 10 SDK. The framework targets net10.0 with LangVersion: preview for C extension types. - Docker Desktop. Aspire provisions SQL Server as a container, so you do not install…","i":"MMCA.Templates LangVersion install net10.0 preview dotnet tool"},{"u":"/docs/guides/common-GETTING-STARTED.html#1-install-the-template-pack","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"1. Install the template pack","x":"Four templates arrive: mmca-app (a whole solution), mmca-module (a business module across all five layers), and mmca-command / mmca-query (a single vertical slice)."},{"u":"/docs/guides/common-GETTING-STARTED.html#2-generate-the-solution","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"2. Generate the solution","x":"Three names, and they are independent: the solution (also your root namespace), the first module in plural PascalCase, and that module's aggregate root in singular PascalCase.…","i":"ProjectReference local.props Billing Invoice"},{"u":"/docs/guides/common-GETTING-STARTED.html#3-build-and-test-before-you-change-anything","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"3. Build and test before you change anything","x":"That is a warning-free build with TreatWarningsAsErrors and all five analyzers at error severity, and a passing test run including the architecture-fitness rules, with no…","i":"TreatWarningsAsErrors"},{"u":"/docs/guides/common-GETTING-STARTED.html#4-create-the-first-migration","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"4. Create the first migration","x":"The scaffold ships the migrations project and its design-time factory; the migration itself describes your entities, so it is yours to generate: Always pass --context…","i":"SQLServerDbContext DbSet"},{"u":"/docs/guides/common-GETTING-STARTED.html#5-run-it","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"5. Run it","x":"Run this from a real, interactive terminal. Launched from a headless or background shell the Aspire AppHost stalls at control-plane init and no dashboard appears. The dashboard…","i":"OrderOpenedIntegrationEvent AllowAnonymous POST GET sql web"},{"u":"/docs/guides/common-GETTING-STARTED.html#6-the-two-one-time-fixups","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"6. The two one-time fixups","x":"The scaffold deliberately does not hand these over, because renaming invalidates them and no fixed value is right for every name you could pick. Both are covered in full in the…","i":"IntegrationEventContractTestsBase Contoso.Support.Orders.Shared Zeta.App.Orders.Shared ArchitectureTests.cs editorconfig SCAFFOLD IDE0021 SA1211 Ticket DELTA using"},{"u":"/docs/guides/common-GETTING-STARTED.html#what-you-were-handed","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"What you were handed","x":"The Order aggregate arrives fully worked: a Result-returning factory, invariants, guarded mutations raising domain events, a child entity, soft-delete cascade, the caching pair,…","i":"AddApplicationDecorators Directory.Build.props OrderIdentifierType IArchitectureMap HandleFailure ModuleLoader ErrorType WaitFor global Result DbSet Order"},{"u":"/docs/guides/common-GETTING-STARTED.html#add-your-next-feature","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Add your next feature","x":"A vertical slice (the path every feature follows) is one command, run from the module's UseCases folder: Handlers, validators, and mappers are convention-scanned, so there is no…","i":"order.TransferToRequester AddErrorResources RequesterUserId AddDomainEvent Result.Combine ChangeStatus GetByIdAsync SaveChanges definition IsFailure CacheKey Comments"},{"u":"/docs/guides/common-GETTING-STARTED.html#surface-the-slice-at-the-edge","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Surface the slice at the edge","x":"The scaffold stops at the handler, and the template's closing instructions tell you to map the command in your module's controller. Every write in the generated app follows the…","i":"ThrowIfDomainExceptionAsync _transferRequesterUserId ChangeOrderStatusRequest Api.TransferOrderAsync EntityControllerBase TransferOrderCommand OrderDetail.es.resx ICacheInvalidating ChangeStatusAsync OrderDetail.razor OrderDetail.resx SupportApiClient"},{"u":"/docs/guides/common-GETTING-STARTED.html#then-what","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Then what","x":"- Upgrade the framework. Bump every MMCA.Common. entry in Directory.Packages.props together, in one pass. See Phase 7 and the versioning policy. - Add real authentication. Copy…","i":"Directory.Packages.props Authorize Contracts Service"},{"u":"/docs/guides/common-GETTING-STARTED.html#verification-checklist","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Verification checklist","x":"1. dotnet new mmca-app -n produced a solution that builds and tests green before you changed anything. 2. dotnet build .slnx is warning-free (TreatWarningsAsErrors + five…","i":"OutboxMessages InitialCreate migrations healthy YourApp dotnet build slnx test then add new"},{"u":"/docs/guides/common-GETTING-STARTED.html#where-to-look-next","d":"Getting Started: Build a New App on MMCA.Common","k":"Guides & Specifications","t":"Where to look next","x":"- Templates: every parameter of all four templates, dropping the Blazor UI host, and how the pack is built. ADR-065 explains why it is derived from the reference app rather than…"},{"u":"/docs/guides/common-RESILIENCE.html","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","x":"MMCA.Common is a library, so it cannot operate a deployment: restores, RTO/RPO, and SLO alerting are executed in the consumer apps' IaC (e.g. MMCA.ADC's…"},{"u":"/docs/guides/common-RESILIENCE.html#what-the-framework-provides-and-verifies-in-repo","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"What the framework provides (and verifies in-repo)","x":"Failure isolation, graceful degradation, graceful startup, and the restore procedure itself are therefore demonstrated and tested centrally: the framework drills backup→restore…","i":"ResilienceCircuitBreakerFaultInjectionTests OpenIdConnectMetadataWarmupTask WarmupReadinessHealthCheckTests AddStandardResilienceHandler ConfigureHttpClientDefaults WarmupReadinessHealthCheck DatabaseRestoreDrillTests ConfigureBrokerTransport WarmupHostedServiceTests WarmupReadinessGateTests ResilienceHandlerTests WarmupHostedService"},{"u":"/docs/guides/common-RESILIENCE.html#baseline-slo--error-budget-template-consumers-fill-in","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"Baseline SLO / error-budget template (consumers fill in)","x":"Adopt and tune per app; ADC's filled-in version lives in infra/DISASTER-RECOVERY.md + the SLO metric-alerts in infra/main.bicep. Define RTO/RPO per service (ADC's worked…","i":"requests"},{"u":"/docs/guides/common-RESILIENCE.html#restore-drill-runbook-reference","d":"Resilience & Business Continuity (rubric §29)","k":"Guides & Specifications","t":"Restore-drill runbook (reference)","x":"The only evidence backups actually restore is a periodic drill: restore a throwaway copy, confirm it comes back Online, record the measured restore time, then delete the copy.…"},{"u":"/docs/guides/common-RESPONSIVE.html","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","x":"This document is the supported-device and browser matrix for the shared MMCA.Common.UI component library. It makes the responsive contract explicit (the rubric §22 note that it…","i":"MMCA.Common.UI"},{"u":"/docs/guides/common-RESPONSIVE.html#breakpoints","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Breakpoints","x":"The framework keeps C viewport detection and CSS media queries aligned around one mobile threshold. The C 960px mobile cutoff and the CSS 1023.98px cutoff intentionally differ:…","i":"BreakpointConstants.IsMobileBreakpoint media i.e"},{"u":"/docs/guides/common-RESPONSIVE.html#touch-targets","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Touch targets","x":"Interactive controls on mobile surfaces meet a 48px minimum hit area (Material Design), exceeding both WCAG 2.5.8 Target Size (Minimum, AA, 24px) and WCAG 2.5.5 Target Size…"},{"u":"/docs/guides/common-RESPONSIVE.html#grid-density","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Grid density","x":"DataGridListPageBase exposes a DenseGrid property and a ToggleDensity() method. Derived list pages bind Dense=\"@DenseGrid\" on their MudDataGrid and surface a toggle. The chosen…","i":"ListPageQueryStateServiceTests ListPageStateServiceTests DataGridListPageBase ToggleDensity MudDataGrid DenseGrid TDto"},{"u":"/docs/guides/common-RESPONSIVE.html#browser-matrix","d":"Responsive Design & Cross-Browser Support (rubric §22)","k":"Guides & Specifications","t":"Browser matrix","x":"The shared UI is tested against three Playwright engines in CI (.github/workflows/ci.yml, ui-e2e job): a real-browser axe (WCAG 2.1 AA) + render smoke against the backend-less…","i":"false"},{"u":"/docs/guides/common-TEMPLATES.html","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","x":"MMCA.Templates is a dotnet new pack that scaffolds solutions, modules, and vertical slices on the MMCA.Common framework. It exists because standing up a new app by hand meant 12…","i":"MMCA.Templates UseCases dotnet new"},{"u":"/docs/guides/common-TEMPLATES.html#mmca-app","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-app","x":"The module and aggregate names are independent, so --module Billing --aggregate Invoice is fine. Everything derived from them follows: routes, the Aspire database resource, the…","i":"IntegrationEventContractTestsBase Contoso.Support.Orders.Shared ShipmentLineIdentifierType Zeta.App.Orders.Shared ArchitectureTests.cs builder.AddProject ProjectReference Contoso.Support EditLineRequest RequesterUserId AddLineRequest AppHost.csproj"},{"u":"/docs/guides/common-TEMPLATES.html#mmca-module","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-module","x":"All six behave exactly as they do for mmca-app, and they are per module: a solution can hold a flat, status-less catalog module beside one whose aggregate owns a growing child…","i":"SQLServerMigrationsAssembly services.AddErrorResources Architecture.Tests.csproj OrderItemIdentifierType Directory.Build.props Migrations.SqlServer Contoso.Support ErrorResources ModuleLoader DataSources FirstModule RemoveItem"},{"u":"/docs/guides/common-TEMPLATES.html#buildadd-moduleps1","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"build/add-module.ps1","x":"Since 1.4.0 every solution mmca-app generates ships this script, and it is the supported way to add a second module. It runs mmca-module with your shape options passed through,…","i":"IntegrationEventContractTests migrations copyOnly dotnet diff Name slnx add git"},{"u":"/docs/guides/common-TEMPLATES.html#mmca-command-and-mmca-query","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"mmca-command and mmca-query","x":"Run these from the module's UseCases folder. Each creates a folder named after the slice holding its two files. --child-collection exists because both handlers load through…","i":"EntityControllerBase MMCA.Templates GetByIdAsync definition CacheKey Comments includes UseCases contain dotnet nameof Result"},{"u":"/docs/guides/common-TEMPLATES.html#how-the-pack-is-built","d":"Templates: scaffolding an MMCA app","k":"Guides & Specifications","t":"How the pack is built","x":"The template content is the MMCA.Helpdesk reference application itself, staged at pack time. There is no second copy of the solution, so the template cannot drift from the app…"},{"u":"/docs/guides/common-VERSIONING.html","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","x":"MMCA.Common publishes fifteen NuGet packages that are versioned and released together as a single unit. They share one version number so a consumer never has to reason about…","i":"MMCA.Common.UI.Maui release.yml"},{"u":"/docs/guides/common-VERSIONING.html#semantic-versioning","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Semantic Versioning","x":"Versions follow SemVer 2.0: MAJOR.MINOR.PATCH: - MAJOR: reserved (see \"Breaking changes within 1.x\" below). - MINOR: new capability, and the channel breaking changes currently…","i":"vMAJOR.MINOR.PATCH MAJOR.MINOR.PATCH v1.51.0"},{"u":"/docs/guides/common-VERSIONING.html#what-counts-as-breaking","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"What counts as breaking","x":"A change is breaking if it is any of: - Removing or renaming a public type/member, or changing a signature. - Changing the meaning of an existing configuration key, or changing a…","i":"Result"},{"u":"/docs/guides/common-VERSIONING.html#breaking-changes-within-1x","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Breaking changes within 1.x","x":"Breaking changes ship as MINOR bumps, not MAJOR ones, and the version number is therefore not a reliable breakage signal on its own. This is deliberate and follows from the…","i":"IIntegrationEventPublisher IntegrationEventPublisher WithSQLServerDataSource WithDataSource IEventBus v1.123.0 v1.79.0"},{"u":"/docs/guides/common-VERSIONING.html#consumer-rollout","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Consumer rollout","x":"Per project convention, framework upgrades are swept across all consumers in one pass: there are no opt-in flags or phased rollouts for a MMCA.Common change. When a release…"},{"u":"/docs/guides/common-VERSIONING.html#deprecation","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Deprecation","x":"There is no [Obsolete] grace period today. Because the lockstep sweep updates every first-party caller in the same change set, a superseded API is removed in the release that…","i":"Obsolete"},{"u":"/docs/guides/common-VERSIONING.html#supply-chain","d":"Versioning & Breaking-Change Policy","k":"Guides & Specifications","t":"Supply chain","x":"- All package versions are centrally pinned (Directory.Packages.props). - NuGet lock files are committed for reproducible restores. - MassTransit is pinned to v8 by policy (v9…","i":"Directory.Packages.props MassTransit"},{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","x":"The automated layer (axe-core WCAG 2.1 AA scans in Tests/E2E/MMCA.Store.E2E.Tests/Workflows/AccessibilityTests.cs plus the shared Login/Register/Profile bases in…","i":"MMCA.Common.Testing.E2E"},{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#tools","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Tools","x":"Run against the app started via Aspire (dotnet run --project Source/Hosting/MMCA.Store.AppHost), reaching the Web UI at https://localhost:6002. Test with the keyboard only (no…","i":"dotnet start Ctrl Alt Cmd run"},{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#what-to-verify-on-every-flow","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"What to verify on every flow","x":"1. Landmarks and headings. The SR's landmark/heading list (NVDA Insert+F7) exposes a logical banner / navigation / main structure and a sensible h1...hN outline (one h1 per…","i":"Wcag21AaExceptMudPagerCombobox MainLayout.razor MMCA.Common.UI navigation h1...hN banner Insert Shift main Tab"},{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#results-log","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"Results log","x":"Record one row per pass. A flow with any unresolved blocker is a FAIL (open a backlog item for it)."},{"u":"/docs/guides/store-ACCESSIBILITY-SCREENREADER-PASS.html#after-a-pass","d":"Manual Screen-Reader Pass: Runbook (§21 Accessibility)","k":"Guides & Specifications","t":"After a pass","x":"- File any defect as a remediation item; cross-link it from the §21 row's evidence. - Update the dated row above so the scorecard can cite a real, current manual pass (this is…"},{"u":"/docs/guides/store-BusinessWorkflows.html","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications"},{"u":"/docs/guides/store-BusinessWorkflows.html#workflow-list-summary","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"Workflow List Summary","x":"---","i":"productId variantId imageId DELETE userId POST GET PUT"},{"u":"/docs/guides/store-BusinessWorkflows.html#1-identity-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"1. Identity Module Workflows","x":"Entry Point: POST /auth/register, AuthController.RegisterAsync(), AllowAnonymous Execution Path: Business Steps: 1. Validate registration input (email, password, first name, last…","i":"AuthController.RegisterAsync AuthController.LoginAsync User.RefreshTokenExpiry Customer.ChangeAddress CustomerAddressChanged Customer.ChangeEmail CustomerEmailChanged RequireAuthenticated Customer.ChangeName CustomerNameChanged User.RefreshToken CustomerCreated"},{"u":"/docs/guides/store-BusinessWorkflows.html#2-catalog-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"2. Catalog Module Workflows","x":"Entry Point: POST /categories, Admin only, [Idempotent] Response: 201 Created with CategoryDTO Entry Point: PUT /categories/{id}/name, Admin only Entry Point: PUT…","i":"CatalogFeatures.ProductImages ProductVariantPriceChanged ProductVariantCartInfoDTO ProductVariantSkuChanged IProductVariantService ProductVariantRemoved ProductNameChanged ParentCategoryId ProductImageData CategoryDeleted ProductImageDTO ProductDeleted"},{"u":"/docs/guides/store-BusinessWorkflows.html#3-sales-module-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"3. Sales Module Workflows","x":"Entry Point: POST /shoppingcarts/{customerId}/shoppingcartitems, Authenticated (owner or admin via OwnerOrAdminFilter) Decision Points: - Product variant doesn't exist - NotFound…","i":"ShoppingCartItemQuantityAdjusted InventoryItem.AvailableQuantity OrderPaymentFailedSagaHandler BulkSetInventoryResultDTO Order.InventoryRestored ProductVariant.NotFound ShoppingCartItemRemoved IProductVariantService ShoppingCartCheckedOut StripePaymentIntentId ShoppingCart.Status ShoppingCartCleared"},{"u":"/docs/guides/store-BusinessWorkflows.html#4-ui-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"4. UI Workflows","x":"The UI provides a complete shopping experience through the CartDrawer component and Blazor pages. The CartDrawer is the only cart UI: there is no dedicated cart page. It is a…","i":"ICartStateService IUIModule OnChange"},{"u":"/docs/guides/store-BusinessWorkflows.html#5-cross-module-interactions","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"5. Cross-Module Interactions","x":"Module dependency: Sales declares a hard dependency on Catalog (RequiresDependencies = true). When Catalog is disabled, a DisabledProductVariantService stub is registered and…","i":"DisabledProductVariantService IProductVariantService UserRegisteredHandler RequiresDependencies GetUnitPricesAsync GetIdBySkuAsync SkuExistsAsync ExistsAsync true"},{"u":"/docs/guides/store-BusinessWorkflows.html#6-external-interactions","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"6. External Interactions","x":"---","i":"StripePaymentService IDbContextFactory SmtpEmailSender"},{"u":"/docs/guides/store-BusinessWorkflows.html#7-cross-cutting-concerns-participating-in-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"7. Cross-Cutting Concerns Participating in Workflows","x":"---","i":"TransactionalCommandDecorator ProfilingCommandDecorator CachingCommandDecorator ProfilingQueryDecorator ICacheInvalidating OwnerOrAdminFilter IdempotencyFilter ITransactional ApiVersion Idempotent"},{"u":"/docs/guides/store-BusinessWorkflows.html#8-end-to-end-customer-journey","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"8. End-to-End Customer Journey","x":"Alternative Flows: - Payment fails - Order status PaymentFailed - customer can retry (create new Stripe session) - Cancel order - Status Cancelled (from PendingPayment,…","i":"StripePaymentIntentId PaymentFailed Cancelled"},{"u":"/docs/guides/store-BusinessWorkflows.html#9-potentially-missing-or-incomplete-workflows","d":"MMCA Business Workflow Analysis","k":"Guides & Specifications","t":"9. Potentially Missing or Incomplete Workflows","x":"--- This document is derived from source code analysis. All workflows, decisions, and behaviors described above are confirmed implementations traceable to the referenced source…","i":"OrderPaymentFailedSagaHandler OrderCancelledSagaHandler MarkAsDelivered SmtpEmailSender User.Deactivate UserDeactivated"},{"u":"/docs/guides/store-NavigationFlow.html","d":"Navigation Flow","k":"Guides & Specifications","x":"This document maps the site navigation flow for each actor in the MMCA.Store application. Each mermaid diagram shows the pages accessible to that actor and the directional…","i":"NavigationFlow.md"},{"u":"/docs/guides/store-NavigationFlow.html#actors","d":"Navigation Flow","k":"Guides & Specifications","t":"Actors","x":"Roles and enforcement: Admin is the only elevated role (registration creates a Customer). The 14 admin pages carry page-level [Authorize(Roles = \"Admin\")], regression-gated in CI…","i":"customer_id Authorize Customer Admin Roles"},{"u":"/docs/guides/store-NavigationFlow.html#1-anonymous-user","d":"Navigation Flow","k":"Guides & Specifications","t":"1. Anonymous User","x":"Pages accessible without authentication: home, login, register, and the public catalog. Add-to-cart on the product detail page sits inside an AuthorizeView; an anonymous visitor…","i":"AuthorizeView"},{"u":"/docs/guides/store-NavigationFlow.html#2-customer-authenticated-user","d":"Navigation Flow","k":"Guides & Specifications","t":"2. Customer (Authenticated User)","x":"Inherits all anonymous pages. Gains the profile page, the cart drawer (a layout component, not a route), checkout, and their own orders. Unauthenticated visitors deep-linking to…","i":"OrphanOrderRecovery Specification Authorize"},{"u":"/docs/guides/store-NavigationFlow.html#3-admin","d":"Navigation Flow","k":"Guides & Specifications","t":"3. Admin","x":"Inherits all customer pages, plus the admin CRUD surfaces for all three modules. Every page below carries [Authorize(Roles = \"Admin\")]; a customer deep-linking to any of them…","i":"Authorize Roles"},{"u":"/docs/guides/store-NavigationFlow.html#authorization-model","d":"Navigation Flow","k":"Guides & Specifications","t":"Authorization Model","x":"Three cooperating layers; the API is always the boundary: 1. Page-level route guards. The 14 admin pages carry [Authorize(Roles = \"Admin\")] and /profile / /orders carry…","i":"OwnershipHelper.GetOwnershipSpecification OwnerOrAdminFilter mmca_auth_access AuthorizeView customer_id Authorize c4adff2 Roles"},{"u":"/docs/guides/store-Specification.html","d":"MMCA Business Specification Document","k":"Guides & Specifications"},{"u":"/docs/guides/store-Specification.html#1-system-overview","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"1. System Overview","x":"MMCA is an e-commerce platform built with .NET 10.0 using DDD and Clean Architecture. The business logic is organized as modules (Catalog, Sales, Identity) that have been…"},{"u":"/docs/guides/store-Specification.html#2-core-business-entities","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"2. Core Business Entities","x":"Description: A classification grouping for products. Supports hierarchical (parent-child) structures for nested categorization (e.g., \"Jewelry\" \"Rings\"). Key Properties:…"},{"u":"/docs/guides/store-Specification.html#3-business-workflows","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"3. Business Workflows","x":"Trigger: A new user submits registration with first name, last name, email, and password. Steps: 1. Validate registration request (email format, password requirements) 2. Verify…","i":"IInventoryAllocationService.DecrementAsync CatalogFeatures.ProductImages payment_intent.payment_failed EventUtility.ConstructEvent IProductImageStorageService checkout.session.completed ProductImageStorageService OrderCancelledSagaHandler checkout.session.expired Order.InventoryRestored IProductVariantService ExecuteUpdateAsync"},{"u":"/docs/guides/store-Specification.html#4-order-status-state-machine","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"4. Order Status State Machine","x":"Cancellable States: PendingPayment, PaymentInitiated, PaymentFailed Manual Payment States: PendingPayment, PaymentInitiated, PaymentFailed Terminal States: Cancelled, Delivered ---"},{"u":"/docs/guides/store-Specification.html#5-business-rules","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"5. Business Rules","x":"---","i":"ProductVariantConfiguration.cs InventoryItemInvariants.cs AdjustInventoryHandler.cs ShoppingCartInvariants.cs CategoryConfiguration.cs CheckOutDomainService.cs CustomerConfiguration.cs UserRegisteredHandler.cs CancelOrderHandler.cs CategoryInvariants.cs CustomerInvariants.cs AddressInvariants.cs"},{"u":"/docs/guides/store-Specification.html#6-use-cases","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"6. Use Cases","x":"---"},{"u":"/docs/guides/store-Specification.html#7-domain-events-and-state-changes","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"7. Domain Events and State Changes","x":"---"},{"u":"/docs/guides/store-Specification.html#8-external-integrations","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"8. External Integrations","x":"Purpose: Processes online customer payments for orders. Business Impact: Enables the system to collect payments from customers and confirm payment success or failure…","i":"payment_intent.payment_failed EventUtility.ConstructEvent checkout.session.completed checkout.session.expired Result.Failure StripeSettings WebhookSecret SecretKey"},{"u":"/docs/guides/store-Specification.html#9-authorization-model","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"9. Authorization Model","x":"Ownership Enforcement: The OwnerOrAdminFilter validates that the route parameter id (CustomerIdentifierType) matches the authenticated user's customer ID, or that the user has…","i":"OwnerOrAdminFilter customer_id user_id email role iat jti sub"},{"u":"/docs/guides/store-Specification.html#10-cross-module-communication","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"10. Cross-Module Communication","x":"The system enforces strict module boundaries. Modules communicate only through shared interface contracts: Confirmed behaviors: - Sales module cannot directly access Catalog…","i":"DisabledProductVariantService IProductVariantService RequiresDependencies true"},{"u":"/docs/guides/store-Specification.html#11-user-interface","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"11. User Interface","x":"The UI is a Blazor Server + WebAssembly hybrid (InteractiveAuto render mode) using MudBlazor component library. It supports multiple hosting targets: - Web (Server + WASM):…","i":"UIModuleConfiguration.IsModuleEnabled ICartStateService InteractiveAuto configuration moduleName IUIModule Assembly NavItems"},{"u":"/docs/guides/store-Specification.html#12-cross-cutting-infrastructure","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"12. Cross-Cutting Infrastructure","x":"The IdempotencyFilter (applied via [Idempotent] attribute on Create endpoints) caches the first response for a given Idempotency-Key header value for 24 hours. Duplicate requests…","i":"TransactionalCommandDecorator ProfilingCommandDecorator CachingCommandDecorator ProfilingQueryDecorator ICacheInvalidating IDataSourceService IDbContextFactory IdempotencyFilter ITransactional SemaphoreSlim UseDataSource Idempotent"},{"u":"/docs/guides/store-Specification.html#13-testing","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"13. Testing","x":"- Full customer journey: Register - Browse - Add to Cart - Checkout - Admin Pay - Deliver - Order lifecycle: all state transitions including cancellation with inventory…","i":"MMCA.Store.Integration.slnf MMCA.Store.IntegrationTests WebApplicationFactory STORE_TEST_SQL_BASE"},{"u":"/docs/guides/store-Specification.html#14-missing-or-unclear-business-logic","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"14. Missing or Unclear Business Logic","x":"Observation: The SMTP email service infrastructure is implemented, but no domain event handlers trigger email notifications for events like order confirmation, payment receipt,…","i":"InventoryItemsController InventoryItemList MarkAsDelivered User.Deactivate UserDeactivated CategoryId Delivered GetPaged GetById GetAll Lookup Paid"},{"u":"/docs/guides/store-Specification.html#15-seed-data-initial-system-state","d":"MMCA Business Specification Document","k":"Guides & Specifications","t":"15. Seed Data (Initial System State)","x":"The system seeds the following data at startup: Users: - Admin: one seeded administrator account (Admin role, no Customer record; credentials are environment-specific and not…","i":"ExistsAsync"},{"u":"/","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"Senior Software Architect Ivan Ball-llovera Cloud-native enterprise architecture on the Microsoft stack I design and ship production-grade .NET platforms: modular monoliths that…"},{"u":"/","t":"Architecture that earns its keep","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"I am a Senior Software Architect with more than 25 years designing and delivering scalable, cloud-native systems on the Microsoft stack. My focus is Domain-Driven Design, Clean…"},{"u":"/","t":"The MMCA platform","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"A production-grade .NET 10 framework and a set of reference apps that demonstrate modern enterprise architecture end to end. It is built as a modular monolith that extracts…"},{"u":"/","t":"Deep dives on enterprise .NET","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"A long-form series turning the framework's decisions into teachable patterns, every claim grounded in real source. The three most recent: Run & extract · No. 33 Resilience and…"},{"u":"/","t":"Speaking & giving back","d":"Ivan Ball-llovera · Senior Software Architect","k":"Site","x":"I help run two community-driven Atlanta technology conferences and keep production-grade patterns free and in the open. See talks & community work Organizer & speaker Two Atlanta…"},{"u":"/resume.html","d":"Résumé","k":"Site","x":"Résumé Ivan Ball-llovera Senior Software Architect 25+ years designing and delivering scalable, cloud-native systems on the Microsoft stack: Domain-Driven Design, Clean…"},{"u":"/resume.html","t":"Professional summary","d":"Résumé","k":"Site","x":"Senior Software Architect with 25+ years designing and delivering scalable, cloud-native systems on the Microsoft stack. Deep expertise in Domain-Driven Design, Clean…"},{"u":"/resume.html","t":"Core competencies","d":"Résumé","k":"Site","x":"Architecture & design Domain-Driven Design, Clean Architecture, CQRS, Modular Monolith → Microservices, Event-Driven Architecture, Outbox Pattern, gRPC, API Gateway (YARP),…"},{"u":"/resume.html","t":"Professional experience","d":"Résumé","k":"Site","x":"Senior Software Engineer · Assurant June 2025 – Present · Architect-level scope: platform, security, and cross-team technical decisions Re-architected the AR.com renters quote…"},{"u":"/resume.html","t":"Featured project · MMCA platform","d":"Résumé","k":"Site","x":"Personal / open source · github.com/ivanball/MMCA.Common A production-grade .NET 10 reference platform demonstrating modern enterprise architecture end-to-end. The conference…"},{"u":"/resume.html","t":"Education","d":"Résumé","k":"Site","x":"B.S., Computer Science University of Havana (Faculty of Mathematics), Havana, Cuba (1994 – 1999)"},{"u":"/resume.html","t":"Languages","d":"Résumé","k":"Site","x":"English · Spanish (bilingual)"},{"u":"/resume.html","t":"Certifications","d":"Résumé","k":"Site","x":"✓ Azure Administrator Associate (AZ-104, 2025) ✓ Azure AI Fundamentals (AI-900, 2024) ✓ Azure Data Fundamentals (DP-900, 2021) ✓ Azure Fundamentals (AZ-900, 2021) → In progress…"},{"u":"/resume.html","t":"Professional development","d":"Résumé","k":"Site","x":"Continuously prototypes emerging technologies, with a current focus on Clean Architecture using .NET 10, Blazor, .NET MAUI, and ASP.NET Core Web API, and on AI-assisted…"},{"u":"/platform.html","d":"The MMCA Platform","k":"Site","x":"Featured work · Open source The MMCA platform A production-grade .NET 10 platform that demonstrates modern enterprise architecture end to end: an open-source framework of fifteen…"},{"u":"/platform.html","t":"MMCA.Common","d":"The MMCA Platform","k":"Site","x":"A NuGet package framework for building modular-monolith applications with DDD, Clean Architecture, and CQRS, and the extension points to extract a module into its own…"},{"u":"/platform.html","t":"Three reference applications","d":"The MMCA Platform","k":"Site","x":"The framework is consumed by real apps. Each one exercises the patterns differently, and the conference app ran a live event on Azure. Conference MMCA.ADC A production-deployed…"},{"u":"/platform.html","t":"From one graph, laptop to cloud","d":"The MMCA Platform","k":"Site","x":"Orchestrated locally with .NET Aspire, then deployed to Azure Container Apps from the same declarative model. The .NET Aspire dashboard: services, databases, and the broker as…"},{"u":"/platform.html","t":"Architectural styles the codebase commits to","d":"The MMCA Platform","k":"Site","x":"The recurring ideas every repo is built on, summarized from the onboarding primer's orientation chapter. Each is taught in full at its first concrete appearance in the reference…"},{"u":"/platform.html","t":"A two-axis architecture scorecard","d":"The MMCA Platform","k":"Site","x":"Every repo is scored against a 34-category rubric on two axes, Maturity (how complete the decision is) and Implementation (how well it is realized in code), with evidence and…"},{"u":"/platform.html","t":"Architecture Decision Records","d":"The MMCA Platform","k":"Site","x":"84 ADRs capture the context and trade-offs behind each cross-cutting pattern, so the design is teachable, not tribal knowledge. Every entry below links to the full record. 001…"},{"u":"/platform.html","t":"The reference library","d":"The MMCA Platform","k":"Site","x":"The full architecture documentation, maintained in this site's repository as its canonical home and rendered as browsable pages: every Architecture Decision Record, the…"},{"u":"/platform.html","t":"Use it, read it, or follow along","d":"The MMCA Platform","k":"Site","x":"The framework is Apache 2.0 and published to nuget.org. The fastest way in is the reference app: one module wired end to end through all five layers, with the extraction path…"},{"u":"/platform.html","t":"Get each deep dive by email","d":"The MMCA Platform","k":"Site","x":"One message per article, no digests and no other mail. Email address Subscribe"},{"u":"/writing.html","d":"Writing","k":"Site","x":"Writing Deep dives on enterprise .NET A long-form series that turns the MMCA framework's architecture decisions into teachable patterns, every claim grounded in real source. Read…"},{"u":"/writing.html","t":"Get each deep dive by email","d":"Writing","k":"Site","x":"One message per article, no digests and no other mail. Email address Subscribe"},{"u":"/speaking.html","d":"Speaking & Community","k":"Site","x":"Speaking & community Talks and giving back For more than 20 years I have been an active contributor to the Microsoft developer communities in Atlanta and South Florida: teaching,…"},{"u":"/speaking.html","t":"Recent sessions","d":"Speaking & Community","k":"Site","x":"Atlanta Cloud + AI Conference · 2026 The App You're Using Right Now Building Atlanta Cloud + AI's own platform with Claude in the loop A field report, not a slide deck about…"},{"u":"/speaking.html","t":"Organizing two Atlanta conferences","d":"Speaking & Community","k":"Site","x":"I help convene developers in person, giving the local community direct, no-cost access to expert content on the Microsoft platform. Lead organizer Atlanta Cloud + AI Conference…"},{"u":"/speaking.html","t":"User groups","d":"Speaking & Community","k":"Site","x":"An active participant in Atlanta's Microsoft technology user-group ecosystem, the same community network from which the conferences draw their speakers and attendees. • Atlanta…"},{"u":"/speaking.html","t":"Open source & mentorship","d":"Speaking & Community","k":"Site","x":"My MMCA framework is Apache-2.0 licensed and documented with architecture decision records, so the patterns are not just usable but teachable. I mentor developers one on one,…"},{"u":"/speaking.html","t":"What I speak on","d":"Speaking & Community","k":"Site","x":"Sessions and workshops for conferences, user groups, and teams. Clean Architecture & DDD on .NET Modular monolith → microservices The transactional outbox Database-per-service…"},{"u":"/contact.html","d":"Contact","k":"Site","x":"Contact Let's connect Happy to talk architecture, the MMCA platform, speaking at your conference or user group, or comparing notes on .NET and Azure. The fastest ways to reach…"},{"u":"/contact.html","t":"Three places to start","d":"Contact","k":"Site","x":"Open source The MMCA platform A .NET 10 framework and three reference apps, graded in the open against a 34-category rubric. See the architecture → Writing Deep dives on…"},{"u":"https://medium.com/@ivanball76/retries-are-not-a-recovery-plan-resilience-handlers-rto-rpo-and-a-restore-you-actually-drilled-3c7474814123","t":"Resilience and recovery objectives","d":"Article no. 33","k":"Run & extract","x":"Standard resilience on every outbound client, plus declared RTO/RPO and a drilled restore.","e":1},{"u":"https://medium.com/@ivanball76/extracting-a-module-to-a-grpc-service-live-799926cf8a32","t":"Extracting a module to a gRPC service","d":"Article no. 32","k":"Run & extract","x":"A step-by-step extraction of an in-process module into its own gRPC service, database, and auth.","e":1},{"u":"https://medium.com/@ivanball76/aspire-one-command-brings-up-the-whole-distributed-app-379b5cffdeed","t":"Aspire: one command","d":"Article no. 31","k":"Run & extract","x":"Model services, databases, and the broker as one Aspire graph that runs from laptop to Azure with one command.","e":1},{"u":"https://medium.com/@ivanball76/defending-the-api-edge-three-controls-that-cover-the-whole-surface-d958ecae1091","t":"Rate limiting and brute-force protection","d":"Article no. 30","k":"Auth & the edge","x":"Two layers that cover the whole API edge: endpoint rate limits plus lockout-based brute-force defense on identity.","e":1},{"u":"https://medium.com/@ivanball76/resource-ownership-authorization-which-rows-you-may-touch-not-just-which-actions-cb8e78867bae","t":"Resource-ownership authorization","d":"Article no. 29","k":"Auth & the edge","x":"Beyond roles and permissions: which rows you may touch, enforced per resource.","e":1},{"u":"https://medium.com/@ivanball76/generic-entity-controllers-and-the-dynamic-query-contract-adr-034-2b5c799bc69f","t":"Generic entity controllers","d":"Article no. 28","k":"Auth & the edge","x":"A write-once REST surface every entity inherits, plus a bounded dynamic query contract that is never open SQL.","e":1},{"u":"https://medium.com/@ivanball76/one-rotating-refresh-token-and-reuse-detection-that-makes-theft-self-limiting-fab42234a04a","t":"One rotating refresh token","d":"Article no. 27","k":"Auth & the edge","x":"A short-lived JWT plus one server-stored refresh token that rotates on every use, with reuse detection that makes a stolen token end its own session.","e":1},{"u":"https://medium.com/@ivanball76/google-and-github-login-without-leaking-tokens-external-oauth-behind-your-own-jwts-d68ba5e3aca4","t":"External OAuth login behind your own JWTs","d":"Article no. 26","k":"Auth & the edge","x":"Sign in with Google or GitHub without leaking provider tokens: external identity exchanged for your own JWTs at the boundary.","e":1},{"u":"https://medium.com/@ivanball76/browser-session-cookie-auth-for-blazor-ssr-surviving-the-f5-eb0ea317820e","t":"Browser session-cookie auth for Blazor SSR","d":"Article no. 25","k":"Auth & the edge","x":"HttpOnly session cookies and an SSR-time scheme so [Authorize] passes during prerender, with the API still the boundary.","e":1},{"u":"https://medium.com/@ivanball76/permission-based-authorization-capabilities-over-role-checks-ea6574cbee27","t":"Permission-based authorization over roles","d":"Article no. 24","k":"Auth & the edge","x":"A capability layer over RBAC: permission policies that resolve on demand from a central registry.","e":1},{"u":"https://medium.com/@ivanball76/delete-automapper-explicit-compile-time-dto-mapping-that-you-can-actually-test-9c7013cc5d3f","t":"Delete AutoMapper: manual DTO mapping","d":"Article no. 23","k":"Auth & the edge","x":"Why source-generated, per-entity mappers beat reflection-based mapping for clarity and speed.","e":1},{"u":"https://medium.com/@ivanball76/ephemeral-by-design-sub-second-live-channels-over-one-signalr-hub-0248050e0c8b","t":"Live channels over one SignalR hub","d":"Article no. 22","k":"Auth & the edge","x":"Sub-second ephemeral events (polls, Q&A, live counts) fanned out over the existing notification hub, with nothing persisted.","e":1},{"u":"https://medium.com/@ivanball76/notifications-as-a-vertical-slice-in-app-inbox-real-time-push-native-push-and-email-c59d5a4f3b69","t":"Notifications as a vertical slice","d":"Article no. 21","k":"Auth & the edge","x":"A notifications feature built as a clean vertical slice across every layer.","e":1},{"u":"https://medium.com/@ivanball76/problem-details-across-http-and-grpc-rfc-9457-9f20157cf7de","t":"Problem Details across HTTP and gRPC","d":"Article no. 20","k":"Auth & the edge","x":"One error contract mapped consistently to HTTP Problem Details and gRPC status.","e":1},{"u":"https://medium.com/@ivanball76/the-self-invalidating-cache-that-lives-in-the-pipeline-not-your-handlers-e11548062d2f","t":"The self-invalidating cache","d":"Article no. 19","k":"Auth & the edge","x":"A caching decorator where commands invalidate and queries populate, plus an authenticated output-cache tier at the API edge.","e":1},{"u":"https://medium.com/@ivanball76/idempotency-in-one-attribute-safe-retries-for-http-apis-065848fd03f4","t":"Idempotency in one attribute","d":"Article no. 18","k":"Auth & the edge","x":"Dedup client retries with an Idempotency-Key header and cached replay, plus a consumer-side inbox for brokers.","e":1},{"u":"https://medium.com/@ivanball76/password-hashing-done-right-pbkdf2-sha512-600k-iterations-timing-safe-d64ddb802403","t":"Password hashing done right","d":"Article no. 17","k":"Auth & the edge","x":"The non-negotiables of password storage in .NET, done correctly and tested.","e":1},{"u":"https://medium.com/@ivanball76/cross-service-auth-without-a-shared-secret-jwks-dual-fetch-478e6f688c7e","t":"JWKS cross-service auth","d":"Article no. 16","k":"Auth & the edge","x":"Validate another service's RS256 tokens via JWKS discovery, with no shared secret crossing a boundary.","e":1},{"u":"https://medium.com/@ivanball76/event-schema-versioning-never-silently-reshape-an-event-93cd5d4a156d","t":"Event-schema versioning","d":"Article no. 15","k":"Data & persistence","x":"Every integration event carries a schema version; breaking changes get a new event type and an upcaster, never a silent reshape.","e":1},{"u":"https://medium.com/@ivanball76/self-ordering-modules-discovered-kahn-ordered-and-extractable-2ce7283a26b5","t":"Self-ordering modules","d":"Article no. 14","k":"Data & persistence","x":"Modules declare their dependencies and load in topological order, so registration is never hand-sequenced.","e":1},{"u":"https://medium.com/@ivanball76/optimistic-concurrency-that-survives-the-round-trip-rowversion-from-database-to-dto-and-back-93d4a794716f","t":"Optimistic concurrency: RowVersion round-trips","d":"Article no. 13","k":"Data & persistence","x":"Carry the RowVersion from database to DTO and back, so a concurrent edit fails fast as a conflict instead of silently overwriting.","e":1},{"u":"https://medium.com/@ivanball76/ef-core-include-chains-are-a-trap-navigation-populators-decouple-eager-loading-c378fa4497ac","t":"Navigation populators","d":"Article no. 12","k":"Data & persistence","x":"Eager-load relationships that cross containers and data sources without N+1 or a leaky abstraction.","e":1},{"u":"https://medium.com/@ivanball76/one-entity-model-three-databases-polyglot-persistence-behind-a-single-attribute-760e77974d5d","t":"Polyglot persistence: one model, three engines","d":"Article no. 11","k":"Data & persistence","x":"SQL Server, Cosmos, and SQLite behind a single entity model, with the engine chosen by attribute.","e":1},{"u":"https://medium.com/@ivanball76/database-per-service-inside-a-monolith-and-why-265092eb03f1","t":"Database-per-service inside a monolith","d":"Article no. 10","k":"Data & persistence","x":"Give each module its own database and outbox before you extract it, so extraction changes hosting, not data.","e":1},{"u":"https://medium.com/@ivanball76/the-transactional-outbox-in-net-10-never-lose-an-event-again-f5a9b7a89e51","t":"The transactional outbox","d":"Article no. 9","k":"Core patterns","x":"Events that survive a crash: persist them atomically with your data, then dispatch at least once.","e":1},{"u":"https://medium.com/@ivanball76/compose-validators-dont-copy-them-a-reusable-fluentvalidation-kit-8865a6003a9c","t":"Compose validators, don't copy them","d":"Article no. 8","k":"Core patterns","x":"A validation kit that composes FluentValidation rules instead of copy-pasting them across features.","e":1},{"u":"https://medium.com/@ivanball76/the-cqrs-decorator-pipeline-logging-caching-and-transactions-without-touching-a-handler-fb7679b8bde8","t":"The CQRS decorator pipeline","d":"Article no. 7","k":"Core patterns","x":"Thin command and query handlers wrapped by a Scrutor decorator chain whose order is load-bearing.","e":1},{"u":"https://medium.com/@ivanball76/specifications-over-linq-spaghetti-composable-reusable-query-intent-8a40dafcbd3d","t":"Specifications over LINQ spaghetti","d":"Article no. 6","k":"Core patterns","x":"Compose queries from reusable specification objects instead of scattering LINQ across handlers.","e":1},{"u":"https://medium.com/@ivanball76/kill-the-anemic-domain-model-rich-aggregates-with-factory-methods-that-return-result-44f2e3d89794","t":"Kill the anemic domain model","d":"Article no. 5","k":"Core patterns","x":"Push behavior into rich aggregates with factory methods and invariants instead of bags of public setters.","e":1},{"u":"https://medium.com/@ivanball76/stop-throwing-exceptions-for-control-flow-the-result-railway-in-c-7a02050b554e","t":"The Result railway in C#","d":"Article no. 4","k":"Core patterns","x":"Model expected failures as Result values with a transport-agnostic error type, and keep exceptions for the genuinely exceptional.","e":1},{"u":"https://medium.com/@ivanball76/what-good-architecture-actually-means-a-34-category-rubric-you-can-score-yourself-against-4002291a6b6a","t":"The 34-category architecture rubric","d":"Article no. 3","k":"Orientation","x":"A two-axis rubric for scoring architecture on maturity and implementation, so 'good architecture' stops being a vibe.","e":1},{"u":"https://medium.com/@ivanball76/modular-monolith-to-microservices-without-the-rewrite-8c3603614f12","t":"Modular monolith to microservices","d":"Article no. 2","k":"Orientation","x":"The cornerstone idea: build the monolith now and extract a service later with no rewrite, via module discovery, gRPC contracts, and a YARP gateway.","e":1},{"u":"https://medium.com/@ivanball76/i-open-sourced-the-enterprise-net-77f9200f3728","t":"Open-sourced and graded against 34 categories","d":"Article no. 1","k":"Orientation","x":"Why I open-sourced a production .NET framework and scored it against a 34-category architecture rubric, gaps and all.","e":1}]}
\ No newline at end of file
diff --git a/docs-src/adr/037-field-level-encryption-at-rest.md b/docs-src/adr/037-field-level-encryption-at-rest.md
index 3141f07..e39b4dc 100644
--- a/docs-src/adr/037-field-level-encryption-at-rest.md
+++ b/docs-src/adr/037-field-level-encryption-at-rest.md
@@ -1,7 +1,7 @@
# ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)
## Status
-Accepted (2026-07-06; revised 2026-07-24, 2026-07-25).
+Accepted (2026-07-06; revised 2026-07-24, 2026-07-25, 2026-08-15).
## Context
Transparent database encryption (TDE) protects the data files as a whole, but it decrypts
@@ -32,82 +32,143 @@ Provide a single framework-owned EF Core value converter that transparently encr
rest with authenticated encryption, applied per property in an entity configuration.
1. **A sealed EF value converter.** `EncryptedStringConverter`
- (`Source/Core/MMCA.Common.Infrastructure/Persistence/Encryption/EncryptedStringConverter.cs:42`) is a
+ (`Source/Core/MMCA.Common.Infrastructure/Persistence/Encryption/EncryptedStringConverter.cs:72`) is a
`ValueConverter` in the `MMCA.Common.Infrastructure.Persistence.Encryption` namespace
- (`EncryptedStringConverter.cs:5`). It encrypts on write and decrypts on read, so application and domain
+ (`EncryptedStringConverter.cs:6`). It encrypts on write and decrypts on read, so application and domain
code keep an ordinary `string` property and never see ciphertext. It is applied per property:
`builder.Property(e => e.SocialSecurityNumber).HasConversion(new EncryptedStringConverter(encryptionKey))`
- (`EncryptedStringConverter.cs:14`, `:15`). The documented example targets a stored-only field rather
+ (`EncryptedStringConverter.cs:15`, `:16`). The documented example targets a stored-only field rather
than a lookup key, for the reason recorded in the 2026-07-24 revision below.
2. **AES-256-GCM authenticated encryption.** Both directions use `AesGcm`
- (`EncryptedStringConverter.cs:84`, `:113`), which provides confidentiality **and** integrity. The key
- must be exactly 32 bytes (256 bits): the constructor null-checks it
- (`ArgumentNullException.ThrowIfNull`, `EncryptedStringConverter.cs:59`) and throws `ArgumentException`
- on any other length (`EncryptedStringConverter.cs:60`, `:62`). `GenerateKey()` produces a
+ (`EncryptedStringConverter.cs:200`, `:235`), which provides confidentiality **and** integrity. Every key
+ must be exactly 32 bytes (256 bits), enforced on both construction paths: the single-key path
+ null-checks the array (`ArgumentNullException.ThrowIfNull`, `EncryptedStringConverter.cs:129`) and throws
+ `ArgumentException` on any other length (`EncryptedStringConverter.cs:130`, `:132`), and the key-ring path
+ applies the same rule per entry (`EncryptedStringConverter.cs:166`, `:168`). `GenerateKey()` produces a
cryptographically random 32-byte key via `RandomNumberGenerator.GetBytes(32)`
- (`EncryptedStringConverter.cs:72`). The nonce and tag sizes are fixed constants: `NonceSize = 12`
- (96 bits, NIST recommended, `EncryptedStringConverter.cs:45`) and `TagSize = 16`
- (128 bits, `EncryptedStringConverter.cs:48`).
-
-3. **Self-describing storage layout, Base64 in a string column.** Encrypt writes UTF-8 plaintext bytes
- (`EncryptedStringConverter.cs:79`), draws a fresh random nonce (`EncryptedStringConverter.cs:80`),
- runs `AesGcm.Encrypt` (`EncryptedStringConverter.cs:85`), then concatenates
- `[nonce (12)] [ciphertext (N)] [tag (16)]` (`EncryptedStringConverter.cs:87`, `:88`) and Base64-encodes
- the result (`EncryptedStringConverter.cs:93`). Decrypt reverses it: `FromBase64String`
- (`EncryptedStringConverter.cs:101`), a length guard that throws `CryptographicException` when the input
- is shorter than nonce plus tag (`EncryptedStringConverter.cs:103`, `:104`), spans that slice out the
- nonce, ciphertext, and tag (`EncryptedStringConverter.cs:106`, `:108`, `:109`), `AesGcm.Decrypt` which
- validates the tag while decrypting (`EncryptedStringConverter.cs:114`), and a UTF-8 decode
- (`EncryptedStringConverter.cs:116`). The layout is transparent to application code.
-
-4. **Ciphertext is non-deterministic.** A fresh random nonce per encryption
- (`EncryptedStringConverter.cs:80`) means the same plaintext encrypts to different ciphertext on every
- write (proven at `Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EncryptedStringConverterTests.cs:38`,
- and distinct plaintexts differ at `EncryptedStringConverterTests.cs:24`). The consequence is deliberate:
- an encrypted column cannot be equality-filtered, index-seeked, sorted, or joined on in the database.
-
-5. **Empty and null values pass through unencrypted.** Both directions short-circuit on a null-or-empty
- string (`EncryptedStringConverter.cs:76`, `:98`), so a NULL or empty column stays as-is rather than
+ (`EncryptedStringConverter.cs:125`). The envelope sizes are fixed constants: `VersionSize = 1`
+ (`EncryptedStringConverter.cs:75`), `NonceSize = 12` (96 bits, NIST recommended,
+ `EncryptedStringConverter.cs:78`) and `TagSize = 16` (128 bits, `EncryptedStringConverter.cs:81`).
+
+3. **A versioned, self-describing storage envelope, Base64 in a string column.** Encrypt resolves the
+ current key (`EncryptedStringConverter.cs:190`), writes UTF-8 plaintext bytes
+ (`EncryptedStringConverter.cs:192`), draws a fresh random nonce (`EncryptedStringConverter.cs:193`),
+ runs `AesGcm.Encrypt` (`EncryptedStringConverter.cs:201`), then concatenates
+ `[key version (1)] [nonce (12)] [ciphertext (N)] [tag (16)]` (`EncryptedStringConverter.cs:203` through
+ `:208`) and Base64-encodes the result (`EncryptedStringConverter.cs:210`). Decrypt reverses it:
+ `FromBase64String` (`EncryptedStringConverter.cs:218`), a length guard that throws
+ `CryptographicException` when the input is shorter than version plus nonce plus tag
+ (`EncryptedStringConverter.cs:220`, `:221`), the version byte read from position 0
+ (`EncryptedStringConverter.cs:223`), spans that slice out the nonce, ciphertext, and tag
+ (`EncryptedStringConverter.cs:227`, `:229`, `:230`), `AesGcm.Decrypt` which validates the tag while
+ decrypting (`EncryptedStringConverter.cs:236`), and a UTF-8 decode (`EncryptedStringConverter.cs:238`).
+ The envelope is transparent to application code: a stored value carries everything needed to read it back
+ except the key material itself.
+
+4. **A key ring, with one version nominated as current.** The converter can be constructed over an
+ `IReadOnlyDictionary` of versioned keys plus the version to write with
+ (`EncryptedStringConverter.cs:109`). Writes always use the current version
+ (`EncryptedStringConverter.cs:116`); reads resolve their key from the version byte in the stored value
+ itself (`EncryptedStringConverter.cs:224`), so a value written under an older version keeps decrypting
+ for as long as that version stays registered. A version with no key registered throws
+ `CryptographicException` naming only the version number, never key material
+ (`EncryptedStringConverter.cs:225`). The ring is validated once at construction
+ (`EncryptedStringConverter.cs:146`): not null (`:150`), not empty (`:152`), no null entry (`:159`),
+ every key exactly 32 bytes (`:166`), and the nominated current version actually present (`:174`). It is
+ then defensively copied into a `FrozenDictionary` (`EncryptedStringConverter.cs:181`), so mutating the
+ dictionary the caller passed in cannot change which keys the converter uses. The original single-key
+ `byte[]` constructor remains (`EncryptedStringConverter.cs:94`) and is now sugar for a one-entry ring at
+ version 1 (`EncryptedStringConverter.cs:87`, `:137`).
+
+5. **The version byte is authenticated, not merely stored.** The version is passed to AES-GCM as associated
+ data on encrypt (`EncryptedStringConverter.cs:198`, `:201`) and on decrypt
+ (`EncryptedStringConverter.cs:231`, `:236`), so the authentication tag covers it. Rewriting the version
+ byte of a stored value fails decryption rather than silently selecting a different key, and it fails even
+ when the substituted version happens to map to the same key (test at
+ `Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EncryptedStringConverterTests.cs:226`, which
+ registers one key under two versions and still gets a `CryptographicException`).
+
+6. **Ciphertext is non-deterministic.** A fresh random nonce per encryption
+ (`EncryptedStringConverter.cs:193`) means the same plaintext encrypts to different ciphertext on every
+ write (proven at `EncryptedStringConverterTests.cs:38`, and distinct plaintexts differ at
+ `EncryptedStringConverterTests.cs:24`). The consequence is deliberate: an encrypted column cannot be
+ equality-filtered, index-seeked, sorted, or joined on in the database.
+
+7. **Empty and null values pass through unencrypted.** Both directions short-circuit on a null-or-empty
+ string (`EncryptedStringConverter.cs:186`, `:215`), so a NULL or empty column stays as-is rather than
becoming ciphertext (tests at `EncryptedStringConverterTests.cs:82` and `:95`).
-6. **Key management is the consumer's responsibility, supplied as a constructor argument.** The converter
- takes a raw `byte[]` key on construction (`EncryptedStringConverter.cs:54`); there is no DI
- registration, no options type, and no key-provider abstraction in the Infrastructure layer (a grep of
- `MMCA.Common.Infrastructure` for encryption options or a key-provider interface finds only the converter
- itself). The adopting entity configuration passes the key in. The XML documentation directs consumers to
- store that key in Azure Key Vault, user-secrets, or environment variables, never hardcoded
- (`EncryptedStringConverter.cs:33`, `:34`, `:35`).
-
-7. **Unit-tested but not yet adopted.** `EncryptedStringConverterTests`
- (`Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EncryptedStringConverterTests.cs:6`) covers a
- plaintext round-trip (`EncryptedStringConverterTests.cs:10`), a Unicode round-trip
- (`EncryptedStringConverterTests.cs:129`), non-deterministic output (`EncryptedStringConverterTests.cs:24`,
- `:38`), the 32-byte key generation (`EncryptedStringConverterTests.cs:52`, `:61`), the invalid-length and
- null-key guards (`EncryptedStringConverterTests.cs:71`, `:123`), the empty-string passthrough
- (`EncryptedStringConverterTests.cs:82`, `:95`), and the too-short-ciphertext `CryptographicException`
- (`EncryptedStringConverterTests.cs:108`). Adoption, however, is zero: a ripgrep across all four
- repositories (`MMCA.Common`, `MMCA.Store`, `MMCA.ADC`, `MMCA.Helpdesk`) finds
- `new EncryptedStringConverter(` only in the converter's own XML-doc example
- (`EncryptedStringConverter.cs:15`) and in that test file. No `*Configuration.cs` in any repo calls
- `.HasConversion(new EncryptedStringConverter(...))`, and Store, ADC, and Helpdesk contain no reference to
- the type at all. The encrypt/decrypt path is exercised by tests, not by any live column.
+8. **Key management is the consumer's responsibility, supplied as a constructor argument.** The converter
+ takes raw key material on construction, either a single `byte[]` (`EncryptedStringConverter.cs:94`) or a
+ whole ring (`EncryptedStringConverter.cs:109`); there is no DI registration, no options type, and no
+ key-provider abstraction in the Infrastructure layer (a grep of `MMCA.Common.Infrastructure` for
+ encryption options or a key-provider interface finds only the converter itself). The adopting entity
+ configuration passes the keys in. The XML documentation directs consumers to store them in Azure Key
+ Vault, user-secrets, or environment variables, never hardcoded (`EncryptedStringConverter.cs:34`, `:35`,
+ `:36`).
+
+9. **Stateless and context-free by design.** The converter holds nothing but the frozen ring captured at
+ construction, and key-version resolution is data-driven from the stored envelope
+ (`EncryptedStringConverter.cs:223`, `:224`), never from the `DbContext`. That is not an oversight: an EF
+ value converter runs inside the provider's materialization path as a pair of compiled expressions
+ (`EncryptedStringConverter.cs:116`, `:117`) and has no access to the context, the current user, or any
+ ambient request scope. Per-tenant or per-request key selection is therefore deliberately out of scope for
+ this converter (`EncryptedStringConverter.cs:62` through `:70`); a design that needs it wants a
+ `SaveChanges` interceptor or application-layer encryption above EF Core, where the request context is
+ still reachable.
+
+10. **Unit-tested but not yet adopted.** `EncryptedStringConverterTests`
+ (`Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EncryptedStringConverterTests.cs:6`) covers a
+ plaintext round-trip (`EncryptedStringConverterTests.cs:10`), a Unicode round-trip
+ (`EncryptedStringConverterTests.cs:129`), non-deterministic output (`EncryptedStringConverterTests.cs:24`,
+ `:38`), the 32-byte key generation (`EncryptedStringConverterTests.cs:52`, `:61`), the invalid-length and
+ null-key guards (`EncryptedStringConverterTests.cs:71`, `:123`), the empty-string passthrough
+ (`EncryptedStringConverterTests.cs:82`, `:95`), the too-short-ciphertext `CryptographicException`
+ (`EncryptedStringConverterTests.cs:108`), the version byte the single-key constructor stamps
+ (`EncryptedStringConverterTests.cs:145`), a key-ring round trip (`EncryptedStringConverterTests.cs:157`),
+ a full rotation round trip in which pre-rotation ciphertext stays readable while new writes carry the new
+ version (`EncryptedStringConverterTests.cs:175`), an unregistered version
+ (`EncryptedStringConverterTests.cs:205`), the tampered-version-byte failure
+ (`EncryptedStringConverterTests.cs:226`), the four ring-validation guards
+ (`EncryptedStringConverterTests.cs:244`, `:250`, `:259`, `:268`), and the defensive copy
+ (`EncryptedStringConverterTests.cs:281`). Adoption, however, is still zero: a ripgrep across all four
+ repositories (`MMCA.Common`, `MMCA.Store`, `MMCA.ADC`, `MMCA.Helpdesk`) finds
+ `new EncryptedStringConverter(` only in the converter's own XML-doc example
+ (`EncryptedStringConverter.cs:16`) and in that test file. No `*Configuration.cs` in any repo calls
+ `.HasConversion(new EncryptedStringConverter(...))`, and Store, ADC, and Helpdesk contain no reference to
+ the type at all. The encrypt/decrypt path is exercised by tests, not by any live column.
## Rationale
- **Authenticated, not merely confidential.** AES-GCM binds a 128-bit tag to the ciphertext
- (`EncryptedStringConverter.cs:48`, `:85`), so a tampered or truncated value fails to decrypt via the tag
- check in `AesGcm.Decrypt` (`EncryptedStringConverter.cs:114`) rather than silently returning corrupted
- plaintext, and a value too short to even hold a nonce and tag is rejected up front
- (`EncryptedStringConverter.cs:103`). At-rest integrity comes for free with confidentiality.
+ (`EncryptedStringConverter.cs:81`, `:201`), so a tampered or truncated value fails to decrypt via the tag
+ check in `AesGcm.Decrypt` (`EncryptedStringConverter.cs:236`) rather than silently returning corrupted
+ plaintext, and a value too short to even hold a version, nonce and tag is rejected up front
+ (`EncryptedStringConverter.cs:220`). At-rest integrity comes for free with confidentiality.
+- **The envelope should describe itself, and the description should be authenticated.** A stored value that
+ carries its own key version needs no side table, no column convention, and no deployment-ordered guess
+ about which key wrote it: the reader is told (`EncryptedStringConverter.cs:223`). Passing that byte as
+ associated data (`EncryptedStringConverter.cs:198`, `:231`) closes the obvious follow-on question, because
+ a self-describing envelope whose description is unauthenticated is an invitation to rewrite the
+ description. One byte of overhead buys both properties.
+- **Rotation has to be possible without a maintenance window.** Reads resolving their key from the data and
+ writes using the current version turn key rotation into four independent steps (add the new key as current
+ while keeping the old one registered, deploy, re-encrypt rows in the background at whatever pace the table
+ allows, then retire the old version) rather than one bulk re-encryption that has to complete before the
+ application can come back up (`EncryptedStringConverter.cs:45` through `:61`).
+- **Breaking the format now is free, and will not be later.** The un-versioned layout had no decode path to
+ preserve because it has no readers: this ADR has recorded zero adopted columns since 2026-07-06. A format
+ break costs nothing while adoption is zero and costs a migration for every encrypted row afterwards, so
+ taking it now avoids shipping a legacy-decode branch that would then live forever.
- **One framework-owned primitive.** As with password hashing (ADR-032), the algorithm, key size, nonce
size, and storage layout are decided once in a single shared type, so a future hardening is one edit that
every eventual adopter inherits rather than per-app crypto scattered across modules.
- **Non-determinism is the right confidentiality default.** A random nonce per write
- (`EncryptedStringConverter.cs:80`) defeats equality and frequency analysis over the ciphertext, which a
+ (`EncryptedStringConverter.cs:193`) defeats equality and frequency analysis over the ciphertext, which a
deterministic scheme would leak; the cost is queryability, which is the correct trade for a genuinely
sensitive column that the application reads by primary key rather than by the encrypted value.
- **Transparent at the EF boundary.** Because the conversion lives on the property mapping
- (`EncryptedStringConverter.cs:12`), entities keep `string` properties and no handler, DTO, or domain code
+ (`EncryptedStringConverter.cs:13`), entities keep `string` properties and no handler, DTO, or domain code
changes when a column becomes encrypted.
## Trade-offs
@@ -117,25 +178,40 @@ rest with authenticated encryption, applied per property in an entity configurat
ADR-005 names this converter as the mechanism for erasure fields that must remain retrievable
(`ADRs/005-soft-delete-vs-erasure.md:17`), but that pairing is available, not yet applied. This is the same
shipped-but-unadopted posture ADR-018 records for polyglot persistence.
-- **Encrypted columns are not queryable.** The random nonce (`EncryptedStringConverter.cs:80`) makes
+- **Encrypted columns are not queryable.** The random nonce (`EncryptedStringConverter.cs:193`) makes
ciphertext non-deterministic, so there is no equality filter, index seek, sort, or join on an encrypted
column. A field that must be both encrypted and looked up needs a separate deterministic scheme or a blind
index, neither of which this converter provides.
-- **Key management is entirely the consumer's, with no rotation story.** The converter takes a raw key
- (`EncryptedStringConverter.cs:54`) and the stored layout is nonce plus ciphertext plus tag only, carrying
- no key identifier or version (`EncryptedStringConverter.cs:87`). Rotating the key therefore requires bulk
- re-encryption, there is no built-in decrypt-with-old / encrypt-with-new path, and losing the key makes the
- data permanently unrecoverable. Envelope encryption and key versioning are out of scope for this converter.
+- **Key management is still entirely the consumer's; the ring is a mechanism, not a service.** The converter
+ takes raw key material (`EncryptedStringConverter.cs:94`, `:109`) and holds whatever ring it was handed,
+ frozen at construction (`EncryptedStringConverter.cs:181`). There is no key-provider abstraction, no Key
+ Vault integration, and no automatic refresh: adding a version means constructing a new converter, which in
+ practice means a deployment. Losing a key still makes every row written under that version permanently
+ unrecoverable, and the ring makes that failure mode more granular rather than less likely. Envelope
+ encryption over a key-encryption key remains out of scope.
+- **Rotation is enabled, not automated.** The format and the ring make a zero-downtime rotation possible
+ (`EncryptedStringConverter.cs:45` through `:61`), but the re-encryption pass itself is the adopter's to
+ write and to run, and nothing in the framework reports how many rows still carry an old version. Retiring a
+ version early throws `CryptographicException` on every unconverted row
+ (`EncryptedStringConverter.cs:225`), which is the loud failure rather than the silent one, but it is still
+ an outage for that column.
+- **One byte caps the ring at 256 live versions.** The version prefix is a single `byte`
+ (`EncryptedStringConverter.cs:75`, `:109`). That is ample for annual or quarterly rotation over any
+ realistic system lifetime, and it is a deliberate trade of headroom for a one-byte envelope, but versions
+ wrap rather than grow: a scheme that rotates far more often would have to reuse retired numbers, and reused
+ numbers are exactly the ambiguity the version byte exists to prevent.
- **Per-property wiring, not a global switch.** Encryption is opted into one `HasConversion` call at a time
- in each entity configuration (`EncryptedStringConverter.cs:12`), so a column that should be encrypted but is
+ in each entity configuration (`EncryptedStringConverter.cs:13`), so a column that should be encrypted but is
never wired silently stays plaintext, the same audit-the-inventory caveat as ADR-005.
-- **Storage and CPU overhead.** Every value grows by 28 bytes (12-byte nonce plus 16-byte tag,
- `EncryptedStringConverter.cs:45`, `:48`) before Base64 inflation, and every read and write performs an
- AES-GCM operation.
-- **Test coverage stops at the too-short guard.** Integrity rests on AES-GCM's tag (a property of the
- primitive), and the only malformed-input regression test is the short-ciphertext case
- (`EncryptedStringConverterTests.cs:108`); there is no explicit bit-flip-tamper or wrong-key test, so a
- refactor that weakened tag validation would not be caught by the current suite.
+- **Storage and CPU overhead.** Every value grows by 29 bytes (1-byte key version plus 12-byte nonce plus
+ 16-byte tag, `EncryptedStringConverter.cs:75`, `:78`, `:81`) before Base64 inflation, and every read and
+ write performs an AES-GCM operation.
+- **Malformed-input coverage stops short of a ciphertext bit-flip.** Integrity rests on AES-GCM's tag (a
+ property of the primitive). The suite now covers the short-value guard
+ (`EncryptedStringConverterTests.cs:108`), an unregistered version (`:205`), and a rewritten version byte
+ under a shared key (`:226`), but there is still no test that flips a bit inside the ciphertext or decrypts
+ under a wrong key at the same version, so a refactor that weakened tag validation over the ciphertext body
+ would not be caught by the current suite.
## Related
ADR-005 (soft-delete vs erasure: the other sensitive-data control, which names this converter as the
@@ -167,3 +243,46 @@ Every `EncryptedStringConverter.cs` line citation in this record was also rebase
revision added the non-determinism constraint paragraph to the type's XML documentation, which
pushed the class declaration and the whole implementation body down by fourteen lines; the anchors
here had not moved with it and now point at the current lines.
+
+## Revision (2026-08-15)
+Behavior change, not a documentation correction. The stored layout is now a **versioned envelope**:
+Base64 of `[key version (1)] [nonce (12)] [ciphertext (N)] [tag (16)]` rather than the previous
+`[nonce] [ciphertext] [tag]`, and the converter can be constructed over a whole ring of versioned keys
+with one nominated as current (`EncryptedStringConverter.cs:109`). Writes stamp the current version,
+reads resolve their key from the version byte in the value itself, and the version byte travels as
+AES-GCM associated data (`EncryptedStringConverter.cs:198`, `:231`) so the authentication tag covers
+it: rewriting the version of a stored value fails decryption even when the substituted version maps to
+the same key. That turns the "no rotation story" trade-off recorded above into a four-step,
+zero-downtime rotation (add the new key as current, deploy, re-encrypt in the background, retire the
+old version), and it retires the claim that the layout carries no key identifier. Decision items 3, 4,
+5, 8 and 9 and the matching Rationale and Trade-off bullets were rewritten to the new reality, the
+per-value overhead moved from 28 bytes to 29 before Base64, and the citations were rebased once more
+against the current file.
+
+**There is no legacy decode path.** A value in the old un-versioned format does not read back under the
+new converter: its first byte is a nonce byte, not a version. That is a deliberate break, and the
+reason it is affordable is the posture this ADR has recorded honestly since 2026-07-06, namely that
+adoption is zero. No entity configuration in any of the four repositories wires the converter, so there
+are no stored values to migrate and no compatibility branch worth carrying forever. The window in which
+the format is free to change closes at the first adopted column, which is precisely why the change was
+made before that rather than after it.
+
+The redesign was prompted by reader feedback on the published article about this converter, which asked
+the obvious question the original design did not answer: what happens when the key has to change. The
+right answer was in the storage format, not in the documentation, so the record is being corrected by
+changing the code rather than by explaining the gap more carefully.
+
+One thing deliberately did **not** change. The converter stays stateless and context-free: version
+resolution is data-driven from the envelope and never consults the `DbContext`, because an EF value
+converter is a pair of compiled expressions in the provider's materialization path and cannot reach the
+context, the current user, or any ambient scope. Per-tenant and per-request key selection therefore
+remain out of scope here (new Decision item 9); they need a `SaveChanges` interceptor or
+application-layer encryption above EF Core.
+
+Test coverage grew from 11 cases to 21, adding the rotation round trip
+(`EncryptedStringConverterTests.cs:175`), the tampered-version-byte failure (`:226`), the unregistered
+version (`:205`), the four ring-validation guards (`:244`, `:250`, `:259`, `:268`), the defensive copy
+of the caller's dictionary (`:281`), and the version byte the single-key constructor stamps (`:145`).
+
+This revision documents work that lands via MMCA.Common PR #247 and ships in the next framework
+release; it is not in a published package as of this date.
diff --git a/docs/adr/037-field-level-encryption-at-rest.html b/docs/adr/037-field-level-encryption-at-rest.html
index 002782d..bc95d90 100644
--- a/docs/adr/037-field-level-encryption-at-rest.html
+++ b/docs/adr/037-field-level-encryption-at-rest.html
@@ -6,21 +6,21 @@
ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter) · MMCA · Ivan Ball-llovera
-
+
-
+
-
+
@@ -192,7 +192,7 @@
Architecture Decision Record
ADR-037: Field-Level Encryption at Rest (AES-256-GCM EF Converter)
Transparent database encryption (TDE) protects the data files as a whole, but it decrypts
transparently for anyone who can query the database, so a leaked backup restored on a compromised
@@ -219,52 +219,91 @@
Decision
rest with authenticated encryption, applied per property in an entity configuration.
A sealed EF value converter.EncryptedStringConverter
- (Source/Core/MMCA.Common.Infrastructure/Persistence/Encryption/EncryptedStringConverter.cs:42) is a
+ (Source/Core/MMCA.Common.Infrastructure/Persistence/Encryption/EncryptedStringConverter.cs:72) is a
ValueConverter<string, string> in the MMCA.Common.Infrastructure.Persistence.Encryption namespace
- (EncryptedStringConverter.cs:5). It encrypts on write and decrypts on read, so application and domain
+ (EncryptedStringConverter.cs:6). It encrypts on write and decrypts on read, so application and domain
code keep an ordinary string property and never see ciphertext. It is applied per property:
builder.Property(e => e.SocialSecurityNumber).HasConversion(new EncryptedStringConverter(encryptionKey))
- (EncryptedStringConverter.cs:14, :15). The documented example targets a stored-only field rather
+ (EncryptedStringConverter.cs:15, :16). The documented example targets a stored-only field rather
than a lookup key, for the reason recorded in the 2026-07-24 revision below.
AES-256-GCM authenticated encryption. Both directions use AesGcm
- (EncryptedStringConverter.cs:84, :113), which provides confidentiality and integrity. The key
- must be exactly 32 bytes (256 bits): the constructor null-checks it
- (ArgumentNullException.ThrowIfNull, EncryptedStringConverter.cs:59) and throws ArgumentException
- on any other length (EncryptedStringConverter.cs:60, :62). GenerateKey() produces a
+ (EncryptedStringConverter.cs:200, :235), which provides confidentiality and integrity. Every key
+ must be exactly 32 bytes (256 bits), enforced on both construction paths: the single-key path
+ null-checks the array (ArgumentNullException.ThrowIfNull, EncryptedStringConverter.cs:129) and throws
+ ArgumentException on any other length (EncryptedStringConverter.cs:130, :132), and the key-ring path
+ applies the same rule per entry (EncryptedStringConverter.cs:166, :168). GenerateKey() produces a
cryptographically random 32-byte key via RandomNumberGenerator.GetBytes(32)
- (EncryptedStringConverter.cs:72). The nonce and tag sizes are fixed constants: NonceSize = 12
- (96 bits, NIST recommended, EncryptedStringConverter.cs:45) and TagSize = 16
- (128 bits, EncryptedStringConverter.cs:48).
+ (EncryptedStringConverter.cs:125). The envelope sizes are fixed constants: VersionSize = 1
+ (EncryptedStringConverter.cs:75), NonceSize = 12 (96 bits, NIST recommended,
+ EncryptedStringConverter.cs:78) and TagSize = 16 (128 bits, EncryptedStringConverter.cs:81).
-
Self-describing storage layout, Base64 in a string column. Encrypt writes UTF-8 plaintext bytes
- (EncryptedStringConverter.cs:79), draws a fresh random nonce (EncryptedStringConverter.cs:80),
- runs AesGcm.Encrypt (EncryptedStringConverter.cs:85), then concatenates
- [nonce (12)] [ciphertext (N)] [tag (16)] (EncryptedStringConverter.cs:87, :88) and Base64-encodes
- the result (EncryptedStringConverter.cs:93). Decrypt reverses it: FromBase64String
- (EncryptedStringConverter.cs:101), a length guard that throws CryptographicException when the input
- is shorter than nonce plus tag (EncryptedStringConverter.cs:103, :104), spans that slice out the
- nonce, ciphertext, and tag (EncryptedStringConverter.cs:106, :108, :109), AesGcm.Decrypt which
- validates the tag while decrypting (EncryptedStringConverter.cs:114), and a UTF-8 decode
- (EncryptedStringConverter.cs:116). The layout is transparent to application code.
+
A versioned, self-describing storage envelope, Base64 in a string column. Encrypt resolves the
+ current key (EncryptedStringConverter.cs:190), writes UTF-8 plaintext bytes
+ (EncryptedStringConverter.cs:192), draws a fresh random nonce (EncryptedStringConverter.cs:193),
+ runs AesGcm.Encrypt (EncryptedStringConverter.cs:201), then concatenates
+ [key version (1)] [nonce (12)] [ciphertext (N)] [tag (16)] (EncryptedStringConverter.cs:203 through
+ :208) and Base64-encodes the result (EncryptedStringConverter.cs:210). Decrypt reverses it:
+ FromBase64String (EncryptedStringConverter.cs:218), a length guard that throws
+ CryptographicException when the input is shorter than version plus nonce plus tag
+ (EncryptedStringConverter.cs:220, :221), the version byte read from position 0
+ (EncryptedStringConverter.cs:223), spans that slice out the nonce, ciphertext, and tag
+ (EncryptedStringConverter.cs:227, :229, :230), AesGcm.Decrypt which validates the tag while
+ decrypting (EncryptedStringConverter.cs:236), and a UTF-8 decode (EncryptedStringConverter.cs:238).
+ The envelope is transparent to application code: a stored value carries everything needed to read it back
+ except the key material itself.
+
+
A key ring, with one version nominated as current. The converter can be constructed over an
+ IReadOnlyDictionary<byte, byte[]> of versioned keys plus the version to write with
+ (EncryptedStringConverter.cs:109). Writes always use the current version
+ (EncryptedStringConverter.cs:116); reads resolve their key from the version byte in the stored value
+ itself (EncryptedStringConverter.cs:224), so a value written under an older version keeps decrypting
+ for as long as that version stays registered. A version with no key registered throws
+ CryptographicException naming only the version number, never key material
+ (EncryptedStringConverter.cs:225). The ring is validated once at construction
+ (EncryptedStringConverter.cs:146): not null (:150), not empty (:152), no null entry (:159),
+ every key exactly 32 bytes (:166), and the nominated current version actually present (:174). It is
+ then defensively copied into a FrozenDictionary (EncryptedStringConverter.cs:181), so mutating the
+ dictionary the caller passed in cannot change which keys the converter uses. The original single-key
+ byte[] constructor remains (EncryptedStringConverter.cs:94) and is now sugar for a one-entry ring at
+ version 1 (EncryptedStringConverter.cs:87, :137).
+
+
The version byte is authenticated, not merely stored. The version is passed to AES-GCM as associated
+ data on encrypt (EncryptedStringConverter.cs:198, :201) and on decrypt
+ (EncryptedStringConverter.cs:231, :236), so the authentication tag covers it. Rewriting the version
+ byte of a stored value fails decryption rather than silently selecting a different key, and it fails even
+ when the substituted version happens to map to the same key (test at
+ Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EncryptedStringConverterTests.cs:226, which
+ registers one key under two versions and still gets a CryptographicException).
Ciphertext is non-deterministic. A fresh random nonce per encryption
- (EncryptedStringConverter.cs:80) means the same plaintext encrypts to different ciphertext on every
- write (proven at Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EncryptedStringConverterTests.cs:38,
- and distinct plaintexts differ at EncryptedStringConverterTests.cs:24). The consequence is deliberate:
- an encrypted column cannot be equality-filtered, index-seeked, sorted, or joined on in the database.
+ (EncryptedStringConverter.cs:193) means the same plaintext encrypts to different ciphertext on every
+ write (proven at EncryptedStringConverterTests.cs:38, and distinct plaintexts differ at
+ EncryptedStringConverterTests.cs:24). The consequence is deliberate: an encrypted column cannot be
+ equality-filtered, index-seeked, sorted, or joined on in the database.
Empty and null values pass through unencrypted. Both directions short-circuit on a null-or-empty
- string (EncryptedStringConverter.cs:76, :98), so a NULL or empty column stays as-is rather than
+ string (EncryptedStringConverter.cs:186, :215), so a NULL or empty column stays as-is rather than
becoming ciphertext (tests at EncryptedStringConverterTests.cs:82 and :95).
Key management is the consumer's responsibility, supplied as a constructor argument. The converter
- takes a raw byte[] key on construction (EncryptedStringConverter.cs:54); there is no DI
- registration, no options type, and no key-provider abstraction in the Infrastructure layer (a grep of
- MMCA.Common.Infrastructure for encryption options or a key-provider interface finds only the converter
- itself). The adopting entity configuration passes the key in. The XML documentation directs consumers to
- store that key in Azure Key Vault, user-secrets, or environment variables, never hardcoded
- (EncryptedStringConverter.cs:33, :34, :35).
+ takes raw key material on construction, either a single byte[] (EncryptedStringConverter.cs:94) or a
+ whole ring (EncryptedStringConverter.cs:109); there is no DI registration, no options type, and no
+ key-provider abstraction in the Infrastructure layer (a grep of MMCA.Common.Infrastructure for
+ encryption options or a key-provider interface finds only the converter itself). The adopting entity
+ configuration passes the keys in. The XML documentation directs consumers to store them in Azure Key
+ Vault, user-secrets, or environment variables, never hardcoded (EncryptedStringConverter.cs:34, :35,
+ :36).
+
+
Stateless and context-free by design. The converter holds nothing but the frozen ring captured at
+ construction, and key-version resolution is data-driven from the stored envelope
+ (EncryptedStringConverter.cs:223, :224), never from the DbContext. That is not an oversight: an EF
+ value converter runs inside the provider's materialization path as a pair of compiled expressions
+ (EncryptedStringConverter.cs:116, :117) and has no access to the context, the current user, or any
+ ambient request scope. Per-tenant or per-request key selection is therefore deliberately out of scope for
+ this converter (EncryptedStringConverter.cs:62 through :70); a design that needs it wants a
+ SaveChanges interceptor or application-layer encryption above EF Core, where the request context is
+ still reachable.
Unit-tested but not yet adopted.EncryptedStringConverterTests
(Tests/Core/MMCA.Common.Infrastructure.Tests/Persistence/EncryptedStringConverterTests.cs:6) covers a
@@ -272,11 +311,18 @@
Decision
(EncryptedStringConverterTests.cs:129), non-deterministic output (EncryptedStringConverterTests.cs:24,
:38), the 32-byte key generation (EncryptedStringConverterTests.cs:52, :61), the invalid-length and
null-key guards (EncryptedStringConverterTests.cs:71, :123), the empty-string passthrough
- (EncryptedStringConverterTests.cs:82, :95), and the too-short-ciphertext CryptographicException
- (EncryptedStringConverterTests.cs:108). Adoption, however, is zero: a ripgrep across all four
+ (EncryptedStringConverterTests.cs:82, :95), the too-short-ciphertext CryptographicException
+ (EncryptedStringConverterTests.cs:108), the version byte the single-key constructor stamps
+ (EncryptedStringConverterTests.cs:145), a key-ring round trip (EncryptedStringConverterTests.cs:157),
+ a full rotation round trip in which pre-rotation ciphertext stays readable while new writes carry the new
+ version (EncryptedStringConverterTests.cs:175), an unregistered version
+ (EncryptedStringConverterTests.cs:205), the tampered-version-byte failure
+ (EncryptedStringConverterTests.cs:226), the four ring-validation guards
+ (EncryptedStringConverterTests.cs:244, :250, :259, :268), and the defensive copy
+ (EncryptedStringConverterTests.cs:281). Adoption, however, is still zero: a ripgrep across all four
repositories (MMCA.Common, MMCA.Store, MMCA.ADC, MMCA.Helpdesk) finds
new EncryptedStringConverter( only in the converter's own XML-doc example
- (EncryptedStringConverter.cs:15) and in that test file. No *Configuration.cs in any repo calls
+ (EncryptedStringConverter.cs:16) and in that test file. No *Configuration.cs in any repo calls
.HasConversion(new EncryptedStringConverter(...)), and Store, ADC, and Helpdesk contain no reference to
the type at all. The encrypt/decrypt path is exercised by tests, not by any live column.
@@ -284,19 +330,34 @@
Decision
Rationale
Authenticated, not merely confidential. AES-GCM binds a 128-bit tag to the ciphertext
- (EncryptedStringConverter.cs:48, :85), so a tampered or truncated value fails to decrypt via the tag
- check in AesGcm.Decrypt (EncryptedStringConverter.cs:114) rather than silently returning corrupted
- plaintext, and a value too short to even hold a nonce and tag is rejected up front
- (EncryptedStringConverter.cs:103). At-rest integrity comes for free with confidentiality.
+ (EncryptedStringConverter.cs:81, :201), so a tampered or truncated value fails to decrypt via the tag
+ check in AesGcm.Decrypt (EncryptedStringConverter.cs:236) rather than silently returning corrupted
+ plaintext, and a value too short to even hold a version, nonce and tag is rejected up front
+ (EncryptedStringConverter.cs:220). At-rest integrity comes for free with confidentiality.
+
The envelope should describe itself, and the description should be authenticated. A stored value that
+ carries its own key version needs no side table, no column convention, and no deployment-ordered guess
+ about which key wrote it: the reader is told (EncryptedStringConverter.cs:223). Passing that byte as
+ associated data (EncryptedStringConverter.cs:198, :231) closes the obvious follow-on question, because
+ a self-describing envelope whose description is unauthenticated is an invitation to rewrite the
+ description. One byte of overhead buys both properties.
+
Rotation has to be possible without a maintenance window. Reads resolving their key from the data and
+ writes using the current version turn key rotation into four independent steps (add the new key as current
+ while keeping the old one registered, deploy, re-encrypt rows in the background at whatever pace the table
+ allows, then retire the old version) rather than one bulk re-encryption that has to complete before the
+ application can come back up (EncryptedStringConverter.cs:45 through :61).
+
Breaking the format now is free, and will not be later. The un-versioned layout had no decode path to
+ preserve because it has no readers: this ADR has recorded zero adopted columns since 2026-07-06. A format
+ break costs nothing while adoption is zero and costs a migration for every encrypted row afterwards, so
+ taking it now avoids shipping a legacy-decode branch that would then live forever.
One framework-owned primitive. As with password hashing (ADR-032), the algorithm, key size, nonce
size, and storage layout are decided once in a single shared type, so a future hardening is one edit that
every eventual adopter inherits rather than per-app crypto scattered across modules.
Non-determinism is the right confidentiality default. A random nonce per write
- (EncryptedStringConverter.cs:80) defeats equality and frequency analysis over the ciphertext, which a
+ (EncryptedStringConverter.cs:193) defeats equality and frequency analysis over the ciphertext, which a
deterministic scheme would leak; the cost is queryability, which is the correct trade for a genuinely
sensitive column that the application reads by primary key rather than by the encrypted value.
Transparent at the EF boundary. Because the conversion lives on the property mapping
- (EncryptedStringConverter.cs:12), entities keep string properties and no handler, DTO, or domain code
+ (EncryptedStringConverter.cs:13), entities keep string properties and no handler, DTO, or domain code
changes when a column becomes encrypted.
Trade-offs
@@ -307,25 +368,40 @@
Trade-offs
ADR-005 names this converter as the mechanism for erasure fields that must remain retrievable
(ADRs/005-soft-delete-vs-erasure.md:17), but that pairing is available, not yet applied. This is the same
shipped-but-unadopted posture ADR-018 records for polyglot persistence.
-
Encrypted columns are not queryable. The random nonce (EncryptedStringConverter.cs:80) makes
+
Encrypted columns are not queryable. The random nonce (EncryptedStringConverter.cs:193) makes
ciphertext non-deterministic, so there is no equality filter, index seek, sort, or join on an encrypted
column. A field that must be both encrypted and looked up needs a separate deterministic scheme or a blind
index, neither of which this converter provides.
-
Key management is entirely the consumer's, with no rotation story. The converter takes a raw key
- (EncryptedStringConverter.cs:54) and the stored layout is nonce plus ciphertext plus tag only, carrying
- no key identifier or version (EncryptedStringConverter.cs:87). Rotating the key therefore requires bulk
- re-encryption, there is no built-in decrypt-with-old / encrypt-with-new path, and losing the key makes the
- data permanently unrecoverable. Envelope encryption and key versioning are out of scope for this converter.
+
Key management is still entirely the consumer's; the ring is a mechanism, not a service. The converter
+ takes raw key material (EncryptedStringConverter.cs:94, :109) and holds whatever ring it was handed,
+ frozen at construction (EncryptedStringConverter.cs:181). There is no key-provider abstraction, no Key
+ Vault integration, and no automatic refresh: adding a version means constructing a new converter, which in
+ practice means a deployment. Losing a key still makes every row written under that version permanently
+ unrecoverable, and the ring makes that failure mode more granular rather than less likely. Envelope
+ encryption over a key-encryption key remains out of scope.
+
Rotation is enabled, not automated. The format and the ring make a zero-downtime rotation possible
+ (EncryptedStringConverter.cs:45 through :61), but the re-encryption pass itself is the adopter's to
+ write and to run, and nothing in the framework reports how many rows still carry an old version. Retiring a
+ version early throws CryptographicException on every unconverted row
+ (EncryptedStringConverter.cs:225), which is the loud failure rather than the silent one, but it is still
+ an outage for that column.
+
One byte caps the ring at 256 live versions. The version prefix is a single byte
+ (EncryptedStringConverter.cs:75, :109). That is ample for annual or quarterly rotation over any
+ realistic system lifetime, and it is a deliberate trade of headroom for a one-byte envelope, but versions
+ wrap rather than grow: a scheme that rotates far more often would have to reuse retired numbers, and reused
+ numbers are exactly the ambiguity the version byte exists to prevent.
Per-property wiring, not a global switch. Encryption is opted into one HasConversion call at a time
- in each entity configuration (EncryptedStringConverter.cs:12), so a column that should be encrypted but is
+ in each entity configuration (EncryptedStringConverter.cs:13), so a column that should be encrypted but is
never wired silently stays plaintext, the same audit-the-inventory caveat as ADR-005.
-
Storage and CPU overhead. Every value grows by 28 bytes (12-byte nonce plus 16-byte tag,
- EncryptedStringConverter.cs:45, :48) before Base64 inflation, and every read and write performs an
- AES-GCM operation.
-
Test coverage stops at the too-short guard. Integrity rests on AES-GCM's tag (a property of the
- primitive), and the only malformed-input regression test is the short-ciphertext case
- (EncryptedStringConverterTests.cs:108); there is no explicit bit-flip-tamper or wrong-key test, so a
- refactor that weakened tag validation would not be caught by the current suite.
+
Storage and CPU overhead. Every value grows by 29 bytes (1-byte key version plus 12-byte nonce plus
+ 16-byte tag, EncryptedStringConverter.cs:75, :78, :81) before Base64 inflation, and every read and
+ write performs an AES-GCM operation.
+
Malformed-input coverage stops short of a ciphertext bit-flip. Integrity rests on AES-GCM's tag (a
+ property of the primitive). The suite now covers the short-value guard
+ (EncryptedStringConverterTests.cs:108), an unregistered version (:205), and a rewritten version byte
+ under a shared key (:226), but there is still no test that flips a bit inside the ciphertext or decrypts
+ under a wrong key at the same version, so a refactor that weakened tag validation over the ciphertext body
+ would not be caught by the current suite.
Related
ADR-005 (soft-delete vs erasure: the other sensitive-data control, which names this converter as the
@@ -353,6 +429,43 @@
Revision (2026-07-25)
revision added the non-determinism constraint paragraph to the type's XML documentation, which
pushed the class declaration and the whole implementation body down by fourteen lines; the anchors
here had not moved with it and now point at the current lines.
+
Revision (2026-08-15)
+
Behavior change, not a documentation correction. The stored layout is now a versioned envelope:
+ Base64 of [key version (1)] [nonce (12)] [ciphertext (N)] [tag (16)] rather than the previous
+ [nonce] [ciphertext] [tag], and the converter can be constructed over a whole ring of versioned keys
+ with one nominated as current (EncryptedStringConverter.cs:109). Writes stamp the current version,
+ reads resolve their key from the version byte in the value itself, and the version byte travels as
+ AES-GCM associated data (EncryptedStringConverter.cs:198, :231) so the authentication tag covers
+ it: rewriting the version of a stored value fails decryption even when the substituted version maps to
+ the same key. That turns the "no rotation story" trade-off recorded above into a four-step,
+ zero-downtime rotation (add the new key as current, deploy, re-encrypt in the background, retire the
+ old version), and it retires the claim that the layout carries no key identifier. Decision items 3, 4,
+ 5, 8 and 9 and the matching Rationale and Trade-off bullets were rewritten to the new reality, the
+ per-value overhead moved from 28 bytes to 29 before Base64, and the citations were rebased once more
+ against the current file.
+
There is no legacy decode path. A value in the old un-versioned format does not read back under the
+ new converter: its first byte is a nonce byte, not a version. That is a deliberate break, and the
+ reason it is affordable is the posture this ADR has recorded honestly since 2026-07-06, namely that
+ adoption is zero. No entity configuration in any of the four repositories wires the converter, so there
+ are no stored values to migrate and no compatibility branch worth carrying forever. The window in which
+ the format is free to change closes at the first adopted column, which is precisely why the change was
+ made before that rather than after it.
+
The redesign was prompted by reader feedback on the published article about this converter, which asked
+ the obvious question the original design did not answer: what happens when the key has to change. The
+ right answer was in the storage format, not in the documentation, so the record is being corrected by
+ changing the code rather than by explaining the gap more carefully.
+
One thing deliberately did not change. The converter stays stateless and context-free: version
+ resolution is data-driven from the envelope and never consults the DbContext, because an EF value
+ converter is a pair of compiled expressions in the provider's materialization path and cannot reach the
+ context, the current user, or any ambient scope. Per-tenant and per-request key selection therefore
+ remain out of scope here (new Decision item 9); they need a SaveChanges interceptor or
+ application-layer encryption above EF Core.
+
Test coverage grew from 11 cases to 21, adding the rotation round trip
+ (EncryptedStringConverterTests.cs:175), the tampered-version-byte failure (:226), the unregistered
+ version (:205), the four ring-validation guards (:244, :250, :259, :268), the defensive copy
+ of the caller's dictionary (:281), and the version byte the single-key constructor stamps (:145).
+
This revision documents work that lands via MMCA.Common PR #247 and ships in the next framework
+ release; it is not in a published package as of this date.
diff --git a/sitemap.xml b/sitemap.xml
index 4c4fd7f..014f317 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -222,7 +222,7 @@
https://ivanball.github.io/docs/adr/037-field-level-encryption-at-rest.html
- 2026-07-26
+ 2026-08-150.6
From 95b270de4931a5b3e17e4cc8bee5c72b85f780e2 Mon Sep 17 00:00:00 2001
From: Ivan Ball-llovera
Date: Sat, 15 Aug 2026 13:16:05 -0400
Subject: [PATCH 2/2] docs: refresh ADR index row for 037
The index summary still described the un-versioned Base64 nonce+ct+tag
layout and a single 32-byte key. Updated to the versioned envelope and
the key ring, keeping the row's existing style: mechanism first, then
the unadopted posture, which is now also the reason the old format was
free to replace.
Row count and ADR range are unchanged.
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01KwnaQjfkHbHqoMm4Pec3oE
---
docs-src/adr/README.md | 2 +-
docs/adr/index.html | 2 +-
sitemap.xml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs-src/adr/README.md b/docs-src/adr/README.md
index 0348686..350763f 100644
--- a/docs-src/adr/README.md
+++ b/docs-src/adr/README.md
@@ -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::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. |
diff --git a/docs/adr/index.html b/docs/adr/index.html
index 6e11694..25d690b 100644
--- a/docs/adr/index.html
+++ b/docs/adr/index.html
@@ -383,7 +383,7 @@
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).
+
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.