diff --git a/assets/data/search-index.json b/assets/data/search-index.json index 956bf04..9425670 100644 --- a/assets/data/search-index.json +++ b/assets/data/search-index.json @@ -1 +1 @@ -{"v":1,"n":1340,"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 AsyncMethodsDeclareTrailingCancellationToken MessageBusSettings.EnableDelayedRedelivery PushNotificationSettings.ChannelKeyPattern ApplicationDbContext.ConfigureConventions Microsoft.CodeAnalysis.PublicApiAnalyzers RegisterUpcastedIntegrationEventConsumer ApiParameterDescriptorBackfillProvider IWriteRepository.SetOriginalRowVersion ServiceInfoVersioningContractTestsBase ApplicationDbContext.OnModelCreating MMCA.Common.LayerEnforcement.targets"},{"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 (31 DTO mappers across Store + ADC: 20 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. Revised 2026-08-23 (the [ServiceContract] marker now has a dedicated fitness rule behind it, ServiceContractPurityTestsBase, subclassed in all four repos; it is a…","i":"ServiceContractPurityTestsBase ServiceContract"},{"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 grpc"},{"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. Amended by ADR-089 (2026-08-18): the Gateway keeps the route-to-service map this record gave it, but stops expressing it as MapForwarder calls in code. YARP…","i":"MapForwarder ReverseProxy"},{"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). Amended by ADR-087 (2026-08-18): the resilience objective extends past outbound HTTP and gRPC clients for the first time, to the outbox's broker publish,…"},{"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/009-resilience-and-recovery-objectives.html#revision-2026-08-18","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"This record's first Decision point scoped resilience to \"every outbound HttpClient and gRPC client registered through the framework's extension methods\". That scope was accurate…","i":"BrokerResilienceDefaults BrokenCircuitException HttpResilienceDefaults CommandTimeoutSeconds EnableRetryOnFailure ResiliencePipeline DbContextFactory OutboxProcessor HttpClient"},{"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…","i":"OutputCacheEvictionRequested"},{"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). At…","i":"RegisterUpcastedIntegrationEventConsumer MMCA.Helpdesk.Architecture.Tests EventVersioningConventionTests ProductCreatedIntegrationEvent MMCA.Store.Architecture.Tests OutputCacheEvictionRequested TicketOpenedIntegrationEvent MMCA.ADC.Architecture.Tests OrderPlacedIntegrationEvent EventConventionTestsBase CommonArchitectureMap map.ModuleNames.Count"},{"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). Revised 2026-08-18 (the pipeline order…"},{"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#revision-2026-08-18","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two decorators were added to both chains, so the order recorded in the Decision above is no longer the shipped one. The registration site is unchanged in kind:…","i":"CancellationTokenSource.CreateLinkedTokenSource cqrs.authorization.denied.count DecoratorPipelineOrderTestsBase AuthorizationCommandDecorator ExpectedCommandDecorators AddApplicationDecorators ExpectedQueryDecorators AuthorizationDenied ICurrentUserService IPermissionRegistry IRequiresPermission budget.CancelAfter"},{"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, and the Failure error type the timeout decorator reuses because the taxonomy has no timeout member), ADR-003…","i":"IPermissionRegistry MMCA.Common.Cqrs HasPermission SaveChanges Failure"},{"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. Revised 2026-08-18 (two new rule families, namespace dependency cycles and trailing CancellationToken declarations, plus a third enforcement layer: a compile-time…","i":"CancellationToken proto"},{"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 (a third joined them on 2026-08-18: see the Revision at the end). 1. Compile-time guard.…","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#revision-2026-08-18","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two new rule families joined the shared library, and a third enforcement layer joined the two the Decision above describes. The counts in MMCA.Common/FACTS.md move with them: 102…","i":"AsyncMethodsDeclareTrailingCancellationToken Microsoft.CodeAnalysis.PublicApiAnalyzers ArchitectureRules.CancellationTokens dotnet_analyzer_diagnostic.severity NamespacesHaveNoDependencyCycles MMCA.Common.Infrastructure Context.ConnectionAborted IHostedService.StartAsync ArchitectureRules.Cycles TenancySettingsValidator InternalAPI.Shipped.txt NamespaceCycleTestsBase"},{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-08-18-section-b-rule-families","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-18): Section B rule families","x":"A second entry on the same date, kept separate rather than folded into the one above because it lands with a different wave and changes a number that revision states. Two rule…","i":"IdempotencyConventionTestsBase ArchitectureRules.Protos ProtoContractTestsBase PublicAPI.Shipped.txt FrozenProtoContracts csharp_namespace SolutionFileName FactsGenerator justification NonIdempotent Idempotent ProtoFiles"},{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-08-23-superseded-counts-a-re-anchored-citation-and-the-real-test-floor","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-23): superseded counts, a re-anchored citation, and the real test floor","x":"No rule family joined or left the library in this entry. It corrects three things the two 2026-08-18 revisions above state, and it is kept as its own entry rather than edited…","i":"Microsoft.CodeAnalysis.PublicApiAnalyzers SpecificationFitnessTests MMCA.Common.UI.E2E.Tests FrameworkSanityTests FactsGenerator FACTS.md"},{"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 the lockstep release cadence the public API baseline is pinned to), ADR-006/007/008…","i":"CancellationToken"},{"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":"IdempotencyConventionTestsBase IDistributedLock justification NonIdempotent ObjectResult Idempotent NoContent HttpPost"},{"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#revision-2026-08-18","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The last Trade-off above is the one this revision addresses, and it does so by changing what is required. Nothing here makes an endpoint idempotent. What it requires is that…","i":"PostActions_ShouldDeclare_IdempotencyIntent IdempotencyConventionTestsBase AttributeTargets.Method NonIdempotentAttribute GetCustomAttributes AuthControllerBase EnableRateLimiting MMCA.Common.API AttributeUsage justification Justification NonIdempotent"},{"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 NonIdempotent"},{"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…","i":"RateLimitingSettings UserPolicy"},{"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#revision-2026-08-18","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The layering above is unchanged: the global limiter is still authenticated-only, infrastructure and anonymous traffic are still exempt, auth-ip still covers login and register by…","i":"RateLimitAlgorithm.FixedWindow RedisFixedWindowRateLimiter IConnectionMultiplexer Interlocked.Exchange RateLimitingSettings StringIncrementAsync PerUserPermitLimit AuthIpPermitLimit GlobalPermitLimit SegmentsPerWindow allowDistributed SlidingWindow"},{"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…","i":"RateLimitingSettings IncrementAsync UseRateLimiter Distributed INCR"},{"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 and 2026-08-23)."},{"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). Revised 2026-08-18 (the inbox stays opt-in, but being off is no longer silent: a broker-connected host running NoOpInboxStore…","i":"NoOpInboxStore InboxMessages"},{"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 InProcess"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-08-18","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The decision is unchanged: the inbox is still opt-in and NoOpInboxStore is still the default. What changed is that the default is now loud. 1. A broker-connected host with no…","i":"ApplicationDbContext.OnModelCreating MessageBusProvider.InProcess InboxDisabledWarningService IX_InboxMessages_MessageId IEntityTypeConfiguration base.OnModelCreating AddBrokerMessaging SQLServerDbContext AddInboxMessages CosmosDbContext SqliteDbContext ConfigureInbox"},{"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":"OutputCacheEvictionRequested MMCA.Common.OutputCache HybridCacheService ICacheService remarks INCR"},{"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 MiddlewarePipelineStepNames.OutputCache AbsoluteExpirationRelativeToNow PublicEndpointOutputCachePolicy CacheOptions.DefaultExpiration CacheOptions.DefaultDuration DistributedCacheEntryOptions DistributedCacheService IConnectionMultiplexer LoginProtectionService MemoryDistributedCache"},{"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 remarks absexp"},{"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":"RegisterUpcastedIntegrationEventConsumer OutputCacheEvictionRequested LoginProtectionService HybridCacheService ICacheInvalidating IQueryCacheable IncrementAsync ICacheService WRONGTYPE TEvent"},{"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 WebApplicationExtensions.cs AddRedisDistributedCache DistributedCacheService HybridCacheEntryOptions TimeSpan.FromSeconds app.UseOutputCache HybridCacheService DefaultExpiration"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-18","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Every previous revision moved Tier 1. This one moves Tier 2, and it is the first change to the output-cache edge since ADR-040. Tier 2 as decided here is per-process by…","i":"RegisterUpcastedIntegrationEventConsumer RegisterOutputCacheEvictionConsumer RegisterIntegrationEventConsumer AddOutputCacheEvictionHandler OutputCacheEvictionRequested OperationCanceledException OutputCacheEvictionHandler BestEffort.ExecuteAsync MMCA.Common.OutputCache MMCA.Common.BestEffort cache.eviction.failed registerFaultConsumer"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-23","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"One correction of substance plus a line-anchor re-verification. No decision and no behavior changed. 1. The counter trade-off now names the contradiction a reader will hit.…","i":"RegisterUpcastedIntegrationEventConsumer MiddlewarePipelineStepNames.OutputCache RegisterOutputCacheEvictionConsumer BaseIntegrationEvent.SchemaVersion AddStackExchangeRedisOutputCache OutputCacheEvictionRequested DistributedCacheService app.UseOutputCache IncrementAsync UseOutputCache ICacheService AddCaching"},{"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). Revised 2026-08-18 (a targeting-context accessor is now registered, so the built-in Targeting and Percentage filters give consistent per-user bucketing…"},{"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#revision-2026-08-18","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Progressive rollout is now usable, because the targeting context exists. The Decision above listed the Percentage / TimeWindow / Targeting filters as \"available\", and the last…","i":"CurrentUserTargetingContextAccessor FeatureGateCommandDecorator ITargetingContextAccessor featureGated.FeatureName AddHttpContextAccessor IHttpContextAccessor ICurrentUserService ClaimTypes.Role IFeatureManager IsEnabledAsync Identity.Name WithTargeting"},{"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, now with Authorization registered directly inside it so a disabled…","i":"FeatureGate Groups 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, call-site hoist recorded 2026-08-23)."},{"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 IdentityModuleDbSeederBase AuthenticationServiceBase ChangePasswordHandlerBase Rfc2898DeriveBytes.Pbkdf2 HashAlgorithmName.SHA512 IdentityModuleDbSeeder AuthenticationService ChangePasswordHandler LegacyHmacSaltSize"},{"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/032-password-hashing.html#revision-2026-08-23","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision is unchanged: one framework-owned IPasswordHasher, PBKDF2-HMAC-SHA512 for new passwords, salt-length dispatch on verification. What changed is where the last two…","i":"IdentityModuleDbSeederBase ChangePasswordHandlerBase IdentityModuleDbSeeder AuthenticationService ChangePasswordHandler IPasswordHasher ChangePassword VerifyPassword HashPassword HandlerName CreateUser Accounts"},{"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:78 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). Revised 2026-08-18: the same token gains an HTTP-native…","i":"SetOriginalRowVersion IConcurrencyAware SupportsIfMatch GetByIdAsync RowVersion ETag"},{"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#revision-2026-08-18","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"This record chose a body round-trip: the client echoes RowVersion on the update request. HTTP has had a standard way to say the same thing since long before this framework, ETag…","i":"RewriteConflictToPreconditionFailed EntityControllerBase.GetByIdAsync UpdateRequestsAreConcurrencyAware DbUpdateConcurrencyException DbUpdateExceptionHandler SupportsIfMatchAttribute ServiceFilterAttribute SetOriginalRowVersion IAsyncActionFilter SetConcurrencyETag HttpContext.Items IConcurrencyAware"},{"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, and whose own 2026-08-18 revision adds…","i":"AuditableBaseEntity GetByIdAsync 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, 2026-08-18). Revised 2026-08-18: the versioned-envelope converter is no longer unpublished, it is included in…"},{"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 main 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":"OutputCacheEvictionRequested 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":"UserSessionBookmarkCacheEvictionHandler RegisterOutputCacheEvictionConsumer AddStackExchangeRedisOutputCache AddOutputCacheEvictionHandler OutputCacheEvictionRequested AddRedisDistributedCache AddPublicEndpointPolicy BookmarkCountsCache IDistributedCache EvictByTagAsync AddOutputCache NowNextCache"},{"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":"MMCA.Common.OutputCache MMCA.Common.BestEffort 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#revision-2026-08-18","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two meters and one hop. Two new failure counters, each on its own meter. cache.eviction.failed, tagged cachetag, on MMCA.Common.OutputCache…","i":"GatewayCorrelationMiddleware besteffort.dispatch.failed CorrelationIdMiddleware MMCA.Common.Idempotency MMCA.Common.OutputCache MMCA.Common.BestEffort cache.eviction.failed MMCA.Common.Scheduler MMCA.Common.Broker MMCA.Common.Outbox MMCA.Common.Cqrs Idempotency"},{"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). Revised 2026-08-07 (validator hoisted into a shared generic, the 30-second constant moved, the two apps revoke at different speeds). Revised 2026-08-23:…","i":"MiddlewarePipelineBuilder WebApplicationExtensions"},{"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 MiddlewarePipelineBuilder.CreateDefault SoftDeletedUserCache.MarkDeletedAsync SoftDeletedUserCache.MarkerDuration context.RequestServices.GetService MiddlewarePipelineBuilder.Build SoftDeletedUserMiddlewareTests AuditableAggregateRootEntity SoftDeletedUserCache.KeyFor UseCommonMiddlewarePipeline ICurrentUserService.UserId TenantResolutionMiddleware"},{"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…","i":"TenantResolutionMiddleware"},{"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/047-soft-deleted-user-session-revocation.html#revision-2026-08-23","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"Re-verified against current source. The decision, the cache design, the fail-open policy and the per-app asymmetry are all unchanged; what moved is where the middleware's…","i":"MiddlewarePipelineBuilder.CreateDefault app.UseCommonMiddlewarePipeline MiddlewarePipelineBuilder.Build UseCommonMiddlewarePipeline WebApplicationExtensions.cs TenantResolutionMiddleware ISoftDeletedUserValidator SoftDeletedUserMiddleware SoftDeletedUserFilter UseAuthentication TenantResolution UseAuthorization"},{"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":"ActivityIdentifierType 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/048-primitive-identifier-type-aliases.html#revision-2026-08-18","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"No decision, no behavior and no citation in this record changed. What changed is the standing of the deferral it records. The last Trade-offs entry above (\"Revisiting the trade…","i":"UserIdentifierType TIdentifierType CheckIn Source razor int"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-08-23","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"No decision and no rationale changed. Two counts did, both because Conference gained an alias. Conference's alias file declares seventeen aliases, ActivityIdentifierType = int…","i":"ActivityIdentifierType SpeakerIdentifierType System.Guid Guid int"},{"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, 2026-08-14, 2026-08-18 and 2026-08-23)."},{"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; 767 gated sites as of the 2026-08-23…","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/049-library-configureawait-policy.html#revision-2026-08-18","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"A re-measurement only, in the same terms as the 2026-08-14 pass. The policy, the gate and the exemption are unchanged; two of the three counted figures moved. 1. Framework site…","i":"CodeAnalysisTreatWarningsAsErrors TreatWarningsAsErrors MMCA.Common.UI.Maui MMCA.Common.UI.Web ConfigureAwait MMCA.Common.UI analyzers warning CA2007 dotnet format await"},{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-23","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"A re-measurement only, in the same terms as the 2026-08-18 pass. The policy, the gate and the exemption are unchanged; both counted figures moved. 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). Revised 2026-08-23 (post-commit enqueue is recorded as two patterns, not one: see the revision at the end)."},{"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 unitOfWork.SaveChangesAsync LiveChannelPublishQueue SessionScoringProcessor sp.GetRequiredService IServiceScopeFactory SessionScoringQueue BackgroundService SaveChangesAsync TryAddSingleton ITransactional"},{"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/052-background-job-execution.html#revision-2026-08-23","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision is unchanged: post-commit work is still enqueued only once the write is durable. What changed is the record of how. This ADR stated the domain-event handler as the…","i":"SessionQuestionUpvoteChangedHandler TransactionalCommandDecorator SessionQuestionUpvoteChanged unitOfWork.SaveChangesAsync LivePollVoteChangedHandler BestEffort.ExecuteAsync ModerateQuestionHandler EnqueueModeratedAsync EnqueueSubmittedAsync SubmitQuestionHandler CloseLivePollHandler IDomainEventHandler"},{"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 QuerySpecification SessionsController Expression.Invoke EFReadRepository IEntityQuerier IRepository.cs IEntityReader ListAsync"},{"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 IUnitOfWork.GetReadRepository OrdersByCustomerSpecification PublishedEventSpecification TableNoTrackingSingleQuery ParameterReplacer.Replace TableNoTrackingSplitQuery OwnedByUserSpecification publicSpecification.And EntityQueryService.cs ProductVariantService SpecificationComposer"},{"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. (Superseded by the Revision (2026-08-21) below: the split has real dependents shipped in both MMCA.ADC and MMCA.Store,…","i":"QuerySpecification Expression.Invoke ParameterReplacer Specification.cs ISpecification IEntityReader IUnitOfWork 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/055-repository-and-specification-contract.html#revision-2026-08-18","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Five changes, four of them widening the contract and one of them fixing a correctness defect. The decision this record states is unchanged: data access is still repository plus…","i":"NavigationMetadata.UnsupportedIncludes QueryFieldService.ApplySorting PushNotificationDTOProjection PushNotificationDTOProjector KeysetQueryBuilder.Compare PaginationTieBreakProperty EFReadRepositoryDecorator CrossSourceSpecification Error.InvalidEntityField SpecificationExtensions KeysetCollectionResult ExecuteProjectedAsync"},{"u":"/docs/adr/055-repository-and-specification-contract.html#revision-2026-08-21","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-08-21)","x":"Nothing in the contract changed; its consumers did. This revision records the first real adoption of the two surfaces this record had honestly flagged as unconsumed: the narrow…","i":"PublicConferenceVisibility SpecificationExtensions specification.Criteria ProductVariantService GetPageByCursorAsync GetProjectedAsync GetReadRepository AndSpecification IReadRepository IEntityQuerier IEntityReader specification"},{"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 App.razor MudTable ADCHome"},{"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, 2026-08-18, and 2026-08-23)."},{"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 MiddlewarePipelineOrderTestsBase MMCA.Common.Testing.Architecture ProductionHostApplicationFactory DecoratorPipelineOrderTestsBase MiddlewarePipelineOrderTests.cs ProblemDetailsContractTestsBase AssertProblemDetailsShapeAsync GracefulShutdownTestsBase AddApplicationDecorators ChangePreferencesCommand"},{"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; vault-backed configuration source recorded and citations re-anchored 2026-08-23)."},{"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; the same identity also lets a…","i":"AddCommonKeyVaultConfiguration azureADOnlyAuthentication USE_MANAGED_IDENTITY_SQL DefaultAzureCredential useManagedIdentitySql AZURE_CLIENT_ID hasSmtpPassword IConfiguration MMCA.Templates KeyVault__Uri keyVaultUrl claude.yml"},{"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":"AZURE_CLIENT_ID 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 (037-field-level-encryption-at-rest.md:108-110 directs a consumer to keep the field-encryption key in Key Vault but decides no delivery mechanism, and nothing wires that…"},{"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). Revised 2026-08-18: Store's two operational extras (the outbox-dead-letter scheduled query rule and the outside-in Gateway availability web test with its…","i":"OPERATIONS.md main.bicep main"},{"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 RunbookHeadingRegex alertEmailAddress MinimumAlertSpecs infra.main.bicep ResourceAssembly"},{"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":"environmentName sloAlertSpecs metricAlerts prefix env 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 PasswordResetTestsBase.cs MMCA.Common.Testing.E2E ProfileManagementTests AxeOptions.Wcag21Aa PrimaryContrastText WarningContrastText GalleryAxeTestBase ErrorContrastText AxeRunOptions"},{"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 PasswordResetTestsBase 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…","i":"Directory.Packages.props"},{"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 IntegrationEventContractTests 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 EnableDelayedRedelivery RetryMaxIntervalSeconds RetryMinIntervalSeconds builder.Configuration UseDelayedRedelivery UsingAzureServiceBus"},{"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).…","i":"IValidateOptions IOptions"},{"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":"CreateCheckoutSessionCommandValidator GatewayRateLimitingSettings ForgotPasswordHandlerBase IConnectionStringSettings IPushNotificationSettings CheckoutRedirectSettings ConnectionStringSettings PushNotificationSettings RecordRoomCheckInHandler AddCommonAuthentication LoginProtectionSettings SecurityHeadersSettings"},{"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":"TenancySettingsValidator ValidateDataAnnotations IDataSourceResolver IValidatableObject IValidateOptions IOptionsMonitor TenancySettings 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":"MiddlewarePipelineOrderTestsBase MiddlewarePipelineStepNames DesignTimeDbContextHelper MiddlewarePipelineBuilder ApplicationDbContext IgnoreQueryFilters EFReadRepository AddMultiTenancy configuration ITenantEntity OnConfiguring GetService"},{"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":"CrossTenantWriteException.ForUnresolvedTenant MiddlewarePipelineBuilder.CreateDefault IPhysicalDbContextFactory.Create MiddlewarePipelineOrderTestsBase CosmosDbContext.OnModelCreating TenantSaveChangesInterceptor UseCommonMiddlewarePipeline TenantResolutionMiddleware CrossTenantWriteException DesignTimeDbContextHelper ITenantContext.SetTenant CachingCommandDecorator"},{"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, 2026-08-18). 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 ScheduledJobOverrideSettings.Cron DesignTimeDbContextHelper PeriodicBackgroundService Directory.Packages.props EnsurePermissionRegistry ValidateDataAnnotations PollingIntervalSeconds SchedulerSettings.Jobs SyncRegistrationsAsync ResolveCronExpression AuditTrailCleanupJob"},{"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":"OutputCacheEvictionRequested DistributedCacheService MMCA.Common.OutputCache 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 IConnectionMultiplexer CachingQueryDecorator DisableLocalCacheRead"},{"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; revised 2026-08-18). The implementation lands in the MMCA.Common \"enterprise capability wave\" release. Unlike the wave's other features this one is NOT…","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 ExportRowLimitHeaderName OpenApiContractTestsBase MaxUnboundedResultLimit QueryFilterModelBinder IEntityControllerBase EntityControllerBase"},{"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). Revised 2026-08-19 (refreshed the WebApplicationBuilderExtensions.cs cross-reference anchor, which moved to :555). Revised 2026-08-21: the order became…","i":"MiddlewarePipelineBuilder.CreateDefault WebApplicationBuilderExtensions.cs MiddlewarePipelineOrderTestsBase"},{"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:46), and…","i":"MiddlewarePipelineBuilder.CreateDefault MiddlewarePipelineOrderTestsBase DecoratorPipelineOrderTestsBase UseCommonRequestLocalization MiddlewarePipelineStepNames UseCommonMiddlewarePipeline TenantResolutionMiddleware ISoftDeletedUserValidator MiddlewarePipelineBuilder SoftDeletedUserMiddleware MiddlewarePipelineStep OidcDiscoveryEndpoint"},{"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":"- Two of this record's original costs are retired (2026-08-21). As accepted, nothing froze the order (no test referenced the method; the adjacencies were protected by comments…","i":"MiddlewarePipelineOrderTestsBase MiddlewarePipelineStepNames MapOidcDiscoveryEndpoint UseCommonSecurityHeaders PreForwardedCapture HttpContext.Items KnownIPNetworks InsertBefore KnownProxies PreForwarded Controllers 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 AddDefaultPolicy AllowCredentials IHostEnvironment AllowAnyHeader AllowAnyMethod"},{"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). Revised 2026-08-23: the adopter counts were refreshed (ADC Conference's ActivityChanged joined the base-derived set) and three source citations were…","i":"ActivityChanged"},{"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 additionalPortMappings"},{"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/adr/085-identifier-type-aliases-revisited.html","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#status","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Revised 2026-08-23 (the alias count and the migration-surface census were recounted, the census gained a stated methodology, and the CheckIn and…","i":"CheckIn"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#context","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Context","x":"ADR-048 decided that every entity identity is a primitive named through a per-module global using {Entity}IdentifierType = ... alias, and recorded the cost in one line of…","i":"SpeakerIdentifierType UserIdentifierType StronglyTypedId IdentifierType Notification System.Guid Entity global Source using Vogen and"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#decision","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Decision","x":"Keep the aliases. The wrapper-struct alternative is evaluated in this record, priced, and deferred again, this time against explicit triggers. Inside a module an identifier is…","i":"SessionIdentifierType SponsorIdentifierType EventIdentifierType UserIdentifierType checkedInByUserId IEntityDTOMapper TIdentifierType IdentifierType ValueConverter JsonConverter CheckInScope BaseEntity"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#rationale","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Rationale","x":"- The cost is paid once and the benefit accrues per defect avoided, and the defect count is currently zero. No production incident in any of the four repos has been traced to a…","i":"System.Text.Json Guid int"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#trade-offs","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The exposure is unmitigated, not reduced. This record buys no safety whatsoever. Every transposition ADR-048 could not catch is still uncatchable today, and the CheckIn…","i":"CheckIn.Create CheckIn Create int"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#related","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Related","x":"ADR-048 (the decision this record revisits and upholds; its Status now points here), ADR-068 (the deliberate opposite case: domain values carry invariants and therefore do get…"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#revision-2026-08-23","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision, the priced alternative, the three triggers and the trade-offs are unchanged. What changed is arithmetic and three citations. The alias count is 44 across 10 files,…","i":"ActivityIdentifierType SpeakerIdentifierType IEntityDTOMapper TIdentifierType IdentifierType CheckInScope System.Guid TEntityDTO sponsorId IBaseDTO CheckIn TEntity"},{"u":"/docs/adr/086-process-manager-deferred.html","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records"},{"u":"/docs/adr/086-process-manager-deferred.html#status","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18) as a documented deferral. Nothing ships with this record: no state machine, no correlation store, no new package. What ships is the shape the coordinator…"},{"u":"/docs/adr/086-process-manager-deferred.html#context","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Context","x":"ADR-054 decided how this workspace achieves cross-boundary consistency without two-phase commit: choreography. Each step of a workflow raises a domain event, each compensating…","i":"PaymentReconciliationService PeriodicBackgroundService SagaStateMachineInstance MassTransitStateMachine Order.InventoryRestored InMemorySagaRepository Order.Status SaveChanges Source ISaga"},{"u":"/docs/adr/086-process-manager-deferred.html#decision","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Decision","x":"Defer the process manager, and record its shape so the deferral is a design decision rather than an omission. A durable multi-step workflow coordinator in this workspace is a…","i":"MassTransit.Azure.ServiceBus.Core MassTransitStateMachine MassTransit.RabbitMQ CorrelationId MassTransit InProcess TInstance Result"},{"u":"/docs/adr/086-process-manager-deferred.html#rationale","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Rationale","x":"- Choreography is genuinely correct for the workflow that exists. This is not a case of the simpler option being tolerated. Checkout's saga state is two fields on Order, and an…","i":"Order"},{"u":"/docs/adr/086-process-manager-deferred.html#trade-offs","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The first workflow to hit the trigger pays the full cost at once, under whatever deadline made it appear. Deferral moves the work onto the critical path of the feature that…","i":"SQLServerDbContext"},{"u":"/docs/adr/086-process-manager-deferred.html#related","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Related","x":"ADR-054 (the accepted mechanism this record defers an alternative to: choreographed compensation, the persisted aggregate marker, and the reconciliation sweep that would remain…"},{"u":"/docs/adr/087-broker-poison-message-handling.html","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records"},{"u":"/docs/adr/087-broker-poison-message-handling.html#status","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Amends ADR-009: the outbox's broker publish gains a circuit breaker, which is the first resilience policy this workspace applies to something other than an…"},{"u":"/docs/adr/087-broker-poison-message-handling.html#context","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Context","x":"Delivery in this workspace has always been at-least-once with retries on both legs: the outbox retries a failed publish with jittered exponential backoff and eventually…","i":"rabbitmq_delayed_message_exchange DeadLetterRetentionDays"},{"u":"/docs/adr/087-broker-poison-message-handling.html#decision","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Decision","x":"Three changes, each scoped to one failure: second-level redelivery configured per transport, a fault consumer with its own meter, and a circuit breaker around the outbox's broker…","i":"RegisterIntegrationEventConsumer settings.EnableDelayedRedelivery FaultIntegrationEventConsumer OperationCanceledException RedeliveryIntervalsSeconds broker.circuit.open.count BuildRedeliveryIntervals cfg.UseDelayedRedelivery ConfigureBrokerTransport EnableDelayedRedelivery BrokenCircuitException fault.FaultedMessageId"},{"u":"/docs/adr/087-broker-poison-message-handling.html#rationale","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Rationale","x":"- The transport asymmetry follows a real capability difference, not a preference. RabbitMQ needs a plugin the dev container lacks; Service Bus does not. A single default would be…","i":"BrokenCircuitException true"},{"u":"/docs/adr/087-broker-poison-message-handling.html#trade-offs","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Trade-offs","x":"- Delayed redelivery is off where the plugin problem lives. RabbitMQ is the local transport and also a plausible self-hosted production transport; both get default-off, so the…","i":"RegisterIntegrationEventConsumer RedeliveryIntervalsSeconds broker.fault.count MMCA.Common.Aspire BrokerMetrics internal"},{"u":"/docs/adr/087-broker-poison-message-handling.html#related","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox publish leg this breaker wraps, and the retry, jittered backoff and dead-lettering that BrokenCircuitException reuses unchanged), ADR-066 (the transport…","i":"RedeliveryIntervalsSeconds BrokenCircuitException MMCA.Common.Broker MMCA.Common.Outbox MMCA.Common.Cqrs"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#status","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Extends ADR-019 with a fourth, edge-tier layer whose posture is the deliberate opposite of the service tier's authenticated-only global limiter; nothing in…"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#context","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Context","x":"ADR-008 made the Gateway the only client entry point and gave it three jobs: the route-to-service map, CORS, and forwarding the caller's Authorization header. Nothing was added…","i":"CorrelationIdMiddleware AddCommonRateLimiting MMCA.Common.Aspire MMCA.Common.API Authorization"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#decision","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Decision","x":"Ship a gateway edge kit in a Gateway namespace inside MMCA.Common.Aspire, owning exactly three responsibilities, and record three more as deliberately declined. 1. Correlation is…","i":"PartitionedRateLimiter.CreateChained AddGatewayDownstreamHealthChecks RateLimitPartition.GetNoLimiter GatewayCorrelationMiddleware GatewayRateLimitingSettings HttpContext.TraceIdentifier Connection.RemoteIpAddress Validator.ValidateObject ValidateDataAnnotations AddGatewayRateLimiting GlobalConcurrencyLimit UseGatewayCorrelation"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#rationale","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Rationale","x":"- The edge is the only place that sees a request exactly once. That is what makes ensure-at-the-edge correct and mint-per-service wrong: not that the service version is broken,…","i":"Ready Live"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#trade-offs","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The limiter closes over an eagerly-bound copy of the settings (GatewayRateLimitingExtensions.cs:154, consumed at :164-172), so an IOptionsMonitor reload never reaches it.…","i":"BypassPathPrefixes IOptionsMonitor PermitLimit IOptions"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#related","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (the record that made the Gateway the only entry point and gave it routing, CORS and auth forwarding; this is the first record to add cross-cutting behavior to it),…"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#status","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18; revised 2026-08-23: ADC's table gained a 27th route, /Activities, on 2026-08-19, and the bicep anchors below are corrected). Amends ADR-008: that record…","i":"MapForwarder ReverseProxy"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#context","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Context","x":"Before this record, both gateways built their route table by hand, in code. ADC made 26 MapForwarder calls and Store made 10. Neither host called AddReverseProxy or…","i":"HttpResilienceDefaults.TotalRequestTimeout AddServiceDiscoveryDestinationResolver ForwarderRequestConfig.ActivityTimeout AddHttpForwarderWithServiceDiscovery ForwarderRequestConfig HttpVersion.Version20 RequestVersionExact appsettings.json AddReverseProxy IHttpForwarder LoadFromConfig ForwardHttp2"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#decision","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Decision","x":"Make configuration the single source of the gateway route table, and pin it with a test. Each gateway calls…","i":"HttpResilienceDefaults.TotalRequestTimeout Http2ForwardingConfigFilter RequestVersionExact appsettings.json MapReverseProxy IHttpForwarder RouteMapTests ForwardHttp2 IProxyConfig MapForwarder ReverseProxy HttpRequest"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#rationale","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Rationale","x":"- The drift already happened, in the repository that has the most gateway tests. ADC is the careful consumer, and it still carried three unpinned routes, an off-by-one comment…"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#trade-offs","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Trade-offs","x":"- The compiler stopped helping. A misspelled cluster reference, a malformed path pattern or a route that shadows another is a runtime failure, discovered as a 404 or a 502, where…","i":"appsettings.json ForwardHttp2 IProxyConfig MapForwarder Order"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#related","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (amended: the Gateway keeps the route-to-service map it was given, now expressed as configuration rather than as forwarder registrations), ADR-088 (the other half of this…","i":"HttpResilienceDefaults.TotalRequestTimeout"},{"u":"/docs/adr/090-event-upcaster-registration.html","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records"},{"u":"/docs/adr/090-event-upcaster-registration.html#status","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-21). Completes the follow-up named in ADR-010: that record established the versioning policy (a SchemaVersion signal plus a new-type-and-upcaster discipline for…","i":"SchemaVersion"},{"u":"/docs/adr/090-event-upcaster-registration.html#context","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Context","x":"ADR-010 splits event evolution into a signal and a discipline. The signal (SchemaVersion, a fitness-function-gated property on every integration event) shipped with ADR-010…","i":"InProcessMessageBus SchemaVersion"},{"u":"/docs/adr/090-event-upcaster-registration.html#decision","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Decision","x":"1. A typed upcaster abstraction, in the Application layer. IEventUpcaster (Source/Core/MMCA.Common.Application/Interfaces/IEventUpcaster.cs) is a pure payload mapping from a…","i":"RegisterUpcastedIntegrationEventConsumer EventUpcastersHaveUniqueSourceTypes EventUpcastersIncreaseSchemaVersion UpcastingIntegrationEventConsumer ArchitectureRules.Upcasters.cs services.AddEventUpcaster AddUserDataExportSection EventConventionTestsBase IIntegrationEventHandler IntegrationEventConsumer DomainEventDispatcher EventUpcasterRegistry"},{"u":"/docs/adr/090-event-upcaster-registration.html#rationale","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Rationale","x":"- The discipline becomes a mechanism. ADR-010's own framing (a thing that matters is a check, not a comment) now applies to the upcaster half: the transform has a first-class…","i":"SchemaVersion MessageId"},{"u":"/docs/adr/090-event-upcaster-registration.html#trade-offs","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Trade-offs","x":"- The upcast walk advances by declared target type, not runtime type. A misbehaving upcaster that returns an instance of some other type cannot send the walk into an unvalidated…","i":"UpcastingIntegrationEventConsumer OutboxMessage.DeserializeEvent OutputCacheEvictionRequested DomainEventDispatcher AddEventUpcaster"},{"u":"/docs/adr/091-cache-backed-password-reset.html","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records"},{"u":"/docs/adr/091-cache-backed-password-reset.html#status","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-22). Extends ADR-029 (the cache-backed login-protection idiom this record reuses) and ADR-032 (which decided how a password is stored, never how a user who has…"},{"u":"/docs/adr/091-cache-backed-password-reset.html#context","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Context","x":"Both consumer apps shipped authenticated password change (PUT /Auth/password) and nothing for a user who cannot sign in at all. The recorded fallback in MMCA.ADC's specification…","i":"ResetTokenExpiresAt ResetTokenHash ResetAttempts PUT"},{"u":"/docs/adr/091-cache-backed-password-reset.html#decision","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Decision","x":"1. The reset token is a cache record, not a schema change. IPasswordResetTokenService (Source/Core/MMCA.Common.Application/Auth/IPasswordResetTokenService.cs) is two methods,…","i":"CryptographicOperations.FixedTimeEquals PasswordResetAuthControllerBase IPasswordResetTokenService FindUntrackedByEmailAsync ForgotPasswordHandlerBase ResetPasswordHandlerBase PasswordReset__ResetUrl PasswordResetController ValidateAndConsumeAsync Auth.InvalidResetToken LoginProtectionService ForgotPasswordCommand"},{"u":"/docs/adr/091-cache-backed-password-reset.html#rationale","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Rationale","x":"- No migration is the whole point. A reset credential is short-lived by nature, and the expiry semantics a reset needs (a TTL, a single use, an attempt cap) are native to a cache…","i":"Result.Success"},{"u":"/docs/adr/091-cache-backed-password-reset.html#trade-offs","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cache eviction invalidates outstanding tokens. A Redis restart, an eviction under memory pressure, or a fall back to the in-memory store on a different replica all silently…","i":"IncrementAsync"},{"u":"/docs/adr/092-web-vitals-budget-gate.html","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records"},{"u":"/docs/adr/092-web-vitals-budget-gate.html#status","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23)."},{"u":"/docs/adr/092-web-vitals-budget-gate.html#context","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Context","x":"Rubric section 23 asks for client-side performance that is measured rather than assumed, naming Core Web Vitals (LCP, INP, CLS) or an equivalent as the evidence…"},{"u":"/docs/adr/092-web-vitals-budget-gate.html#decision","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Decision","x":"Ship the measurement infrastructure and the assert mechanics in MMCA.Common.Testing.E2E, default the budget to the Core Web Vitals good band, and let the assertions ride the…","i":"MMCA.Common.Testing.E2E WEB_VITALS_OUTPUT_DIR WebVitalsBudgetTests BeLessThanOrEqualTo PerformanceObserver AssertWithinBudget WebVitalsCollector WriteArtifactAsync durationThreshold WebVitalsArtifact WebVitalsE2ETests E2E.Tests.csproj"},{"u":"/docs/adr/092-web-vitals-budget-gate.html#rationale","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Rationale","x":"- The good band is an external contract, which is what makes an absolute ceiling defensible here. ADR-060 refused absolute latency because a nanosecond count is a property of the…"},{"u":"/docs/adr/092-web-vitals-budget-gate.html#trade-offs","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- The gate is ui-scoped and may legitimately skip. Both apps gate e2e-gate on a ui change filter (ADC deploy.yml:538, Store :544) and deploy accepts skipped for it (ADC :896,…","i":"WEB_VITALS_OUTPUT_DIR InteractiveServer InteractiveAuto skipped deploy"},{"u":"/docs/adr/092-web-vitals-budget-gate.html#related","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-063 (the structural sibling: the same package, the same Playwright suite and the same deploy gate, applied to WCAG 2.1 AA instead of load performance), ADR-060 (the backend…"},{"u":"/docs/adr/093-container-image-posture.html","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records"},{"u":"/docs/adr/093-container-image-posture.html#status","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23) for the three build decisions below. The two runtime postures in \"Open postures\" are recorded as undecided: they describe what the images do today and the…"},{"u":"/docs/adr/093-container-image-posture.html#context","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Context","x":"Eleven Dockerfiles produce every deployable container in the two Azure-hosted applications: six in MMCA.ADC (four services, the Gateway, the Blazor web host) and five in…","i":"publish latest build final base COPY"},{"u":"/docs/adr/093-container-image-posture.html#decision","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Decision","x":"1. The GitHub Packages credential is a BuildKit secret, never an ARG or ENV. Both applications' nuget.config source-maps MMCA. to GitHub Packages, so every restore inside an…","i":"Directory.Packages.props TreatWarningsAsErrors GITHUB_TOKEN nuget.config ENTRYPOINT history publish secrets docker dotnet build final"},{"u":"/docs/adr/093-container-image-posture.html#open-postures-undecided","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Open postures (undecided)","x":"The base image is a floating tag, not a digest. All eleven images start from mcr.microsoft.com/dotnet/aspnet:10.0 with no digest pin (.../MMCA.ADC.Conference.Service/Dockerfile:1…","i":"aspnet latest final USER app"},{"u":"/docs/adr/093-container-image-posture.html#rationale","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Rationale","x":"- A secret that is never a layer cannot leak from a layer. BuildKit secret mounts are the only mechanism that keeps the credential out of the image, the build cache and docker…","i":"history docker ARG ENV RUN"},{"u":"/docs/adr/093-container-image-posture.html#trade-offs","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Trade-offs","x":"- Eleven copies drift independently. There is no shared base Dockerfile and no test that compares them, so a fix applied to one image is applied to one image. The ReadyToRun…","i":"csproj"},{"u":"/docs/adr/093-container-image-posture.html#related","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Related","x":"ADR-038 (supply-chain provenance: it gates the package graph with lock files, a vulnerability audit and an SBOM, and stops at the repository boundary, so the image layers this…"},{"u":"/docs/adr/094-client-entity-data-access.html","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/094-client-entity-data-access.html#status","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23)."},{"u":"/docs/adr/094-client-entity-data-access.html#context","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Context","x":"ADR-034 decided the server half of entity data access: a generic controller base with a dynamic query contract, where filters arrive as filters[Property].operator /…","i":"QueryFilterModelBinder MMCA.Common.UI operator Property filters value"},{"u":"/docs/adr/094-client-entity-data-access.html#decision","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Decision","x":"Client-side entity data access goes through one hand-written base hierarchy in MMCA.Common.UI. - One HTTP root: AuthenticatedServiceBase…","i":"DomainInvariantViolationException IdempotencyHeaders.IdempotencyKey EntityServiceBase.GetPagedAsync CreateAuthenticatedClientAsync ResetCancellationTokenAsync ListPageQueryStateService AuthenticatedServiceBase CultureDelegatingHandler Directory.Packages.props PersistentComponentState EnsureSuccessStatusCode ObjectDisposedException"},{"u":"/docs/adr/094-client-entity-data-access.html#rationale","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A hand-written typed base beats a generated client here because the surface is already generic. ADR-034 collapsed N entity endpoints into one shape, so there is exactly one…","i":"EnsureSuccessStatusCode"},{"u":"/docs/adr/094-client-entity-data-access.html#trade-offs","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- No generated client means drift is caught at runtime, not at build time. A server-side rename of a query parameter or a DTO property does not fail the UI build; it fails the…","i":"ChildEntityServiceBase.PostAsync ConfigureHttpClientDefaults EntityServiceBaseTests.cs AuthenticatedServiceBase ChildEntityServiceBase AddServiceDefaults CartStateService RetryPolicy protected AddAsync readonly static"},{"u":"/docs/adr/094-client-entity-data-access.html#related","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-034 (the server surface this contract calls, and the filter grammar the client constructs), ADR-017 (the server-side filter whose client half is specified here: who mints the…","i":"DataGridListPageBase TDto"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#status","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23)."},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#context","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Context","x":"ADR-005 makes deletion soft: an IAuditableEntity sets IsDeleted = true (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IAuditableEntity.cs:11) and a named global query…","i":"IAuditableEntity HasFilter IsDeleted true"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#decision","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Decision","x":"Make the filter a convention: every unique index on a soft-deletable entity excludes deleted rows, automatically, in every context of every consumer. - A model-finalizing…","i":"ApplicationDbContext.ConfigureConventions SoftDeleteUniqueIndexConvention SoftDeleteFilterSql.Build DataSource.CosmosDB HasSoftDeleteFilter additionalFilter IAuditableEntity HasColumnName filter null AND"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#rationale","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Rationale","x":"- The database should agree with what the application shows. The query filter already says a soft-deleted row does not exist; a unique index that disagrees is the one place the…","i":"DedupKey Build NULL NOT"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#trade-offs","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Trade-offs","x":"- It moves schema in consumers, invisibly from the entity configuration. Adopting the convention is a database-contract change: nothing in an entity configuration changed, but…","i":"IX_CategoryItem_CategoryId_Name ignoreQueryFilters builder.HasIndex index.GetFilter IX_User_Email IsDeleted IsUnique x.Email false true"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#related","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (decides soft-delete over erasure and owns the query filter that hides the row, but says nothing about uniqueness: this ADR closes that gap), ADR-057 (the expand/contract…","i":"ApplicationDbContext"},{"u":"/docs/adr/096-best-effort-side-effects.html","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/096-best-effort-side-effects.html#status","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23)."},{"u":"/docs/adr/096-best-effort-side-effects.html#context","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Context","x":"A command that has already committed often has follow-up work attached to it: evict the output-cache entries the write invalidated, broadcast the new state to a live channel,…","i":"MarkAsFailed Exception catch"},{"u":"/docs/adr/096-best-effort-side-effects.html#decision","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Decision","x":"One framework helper defines the contract, and a swallow that does not go through it is a deliberate, documented exception. - BestEffort.ExecuteAsync(operation, logger, action,…","i":"BestEffortLog.DispatchFailed besteffort.dispatch.failed OperationCanceledException OutputCacheEvictionHandler BestEffort.ExecuteAsync MMCA.Common.OutputCache CancellationToken.None MMCA.Common.BestEffort ArgumentNullException cache.eviction.failed ProductVariantChanged TryEvictByTagAsync"},{"u":"/docs/adr/096-best-effort-side-effects.html#rationale","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Rationale","x":"- One policy beats five local leniencies. Each feature record is still right about its own degradation; what they could not each decide is the shape of the swallow. A single…","i":"AddVariantHandler"},{"u":"/docs/adr/096-best-effort-side-effects.html#trade-offs","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing gates use of the helper. There is no fitness rule, analyzer or architecture test that fails a build for a hand-rolled catch (Exception) that should have been a…","i":"besteffort.dispatch.failed cache.eviction.failed SubmitQuestionHandler MMCA.Common.Aspire BestEffort Exception catch"},{"u":"/docs/adr/096-best-effort-side-effects.html#related","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-024 (push delivery failure is non-fatal and recorded rather than raised, one of the local leniencies this policy generalizes), ADR-026 (eviction is best-effort, and its…","i":"besteffort.dispatch.failed OutputCacheEvictionHandler"},{"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 UserSessionBookmarkCacheEvictionHandlerTests AddEventQuestionAnswerCommandValidatorTests AddSessionCategoryItemCommandValidatorTests"},{"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,465 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: 1685 production types across 26 groups + 1780 test/testing types in G25 = 3465 (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 14 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: 2810…","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 FaultIntegrationEventConsumer GatewayCorrelationMiddleware JwtSettings.SigningAlgorithm EntityTypeConfigurationBase UseCommonMiddlewarePipeline TenantResolutionMiddleware ExportUserDataHandlerBase ISoftDeletedUserValidator ServiceInfoControllerBase"},{"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 ArgumentNullException.ThrowIfNull DomainInvariantViolationException MMCA.Common.Shared.Serialization MMCA.Common.Shared.Abstractions System.Text.Json.Utf8JsonReader GrpcResultExceptionInterceptor System.Text.Json.Serialization MMCA.Common.Shared.Exceptions System.Buffers.Text.Base64Url Base64Url.TryDecodeFromChars ValidationFailureExtensions"},{"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":"QuerySpecification 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":"PublicSessionStatusSpecification.StatusCriteria GetPublicSessionFilterHandler PublishedEventSpecification CrossSourceSpecification OwnedByUserSpecification SpecificationExtensions SpecificationComposer dependent.ForeignKey InvocationExpression Enumerable.Contains InlineSpecification s.Event.IsPublished"},{"u":"/docs/onboarding/group-03-querying-specifications.html#queryspecification-a-whole-read-in-one-object","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"QuerySpecification, a whole read in one object","x":"A plain specification is only a predicate, which leaves includes, ordering, paging, and tracking to be threaded through every layer as loose arguments. QuerySpecification…","i":"IgnoreQueryFilters QuerySpecification EFReadRepository LambdaExpression OrderExpression TIdentifierType WithSoftDeleted specification Specification BaseQueryFor IncludePaths WithTracking"},{"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:155)…","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-entity-paths-plus-projection-pushdown","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The pipeline: two entity paths plus projection pushdown","x":"IEntityQueryPipeline (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/IEntityQueryPipeline.cs:10) is the execution contract, implemented by the sealed…","i":"ApplyIncludesCriteriaAndFilters inavigationmetadataprovider NavigationMetadataProvider MaxUnboundedResultLimit NavigationPropertyInfo CountUnpaginatedAsync EntityQueryParameters ExecuteProjectedAsync IEntityQueryPipeline INavigationPopulator entityquerypipeline IEntityDTOProjector"},{"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":"SpeakerEntityQueryService BuildPaginationMetadata MaxUnboundedResultLimit TryGetByIdFastPathAsync DTOToEntityPropertyMap TryGetFastPathIncludes EntityQueryParameters ExecuteProjectedAsync 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":"IEntityQueryService.GetAllAsync EntityQueryParameters PagedCollectionResult EntityControllerBase IApplicationSettings EntityQueryPipeline TIdentifierType MaxPageSize PagingMath TEntityDTO requested TEntity"},{"u":"/docs/onboarding/group-03-querying-specifications.html#also-filed-here-the-best-effort-side-effect-helper","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Also filed here: the best-effort side-effect helper","x":"Three types in this group are not part of the read path at all; they are co-located in MMCA.Common.Application/Services and are grouped by that folder. BestEffort…","i":"Filtering.DynamicQueryConfig.Parameterized IEntityQueryPipeline.ExecuteProjectedAsync MMCA.Common.Application.Services.Filtering SpecificationsDoNotNavigateToOtherEntities ArgumentException.ThrowIfNullOrWhiteSpace QueryFilterService.ResolveFilterValueType Microsoft.Extensions.DependencyInjection NavigationMetadataProvider.BuildIncludes CrossSourceSpecification.BuildCriteria MMCA.Common.Application.Services.Query MMCA.Common.Application.Specifications QueryFieldService.ApplyFieldSelection"},{"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 Microsoft.Extensions.Hosting.IHostedService OutboxProcessor.ProcessPendingMessagesAsync UserSessionBookmarkCacheEvictionHandler services.AddOutputCacheEvictionHandler Microsoft.Extensions.Logging.ILogger MMCA.Common.Application.DomainEvents MMCA.Common.Domain.IntegrationEvents ApplicationDbContext.ConfigureInbox domainEventDispatcher.DispatchAsync MMCA.Common.Infrastructure.Services"},{"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":"AuthorizationCommandDecorator TransactionalCommandDecorator AuthorizationQueryDecorator FeatureGateCommandDecorator ValidatingCommandDecorator FeatureGateQueryDecorator ProfilingCommandDecorator CachingCommandDecorator LoggingCommandDecorator ProfilingQueryDecorator TimeoutCommandDecorator CachingQueryDecorator"},{"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":"DecoratorPipelineOrderTestsBase ScanModuleApplicationServices ProfilingCommandDecorator AddApplicationDecorators DependencyInjectionTests AddApplicationProfiling ProfilingQueryDecorator DependencyInjection.cs EntityQueryPipeline IServiceCollection ServiceCollection ICommandHandler"},{"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:76-98): - Feature-gating is outermost so a…","i":"TransactionCommitAmbiguousException ICacheService.RemoveByPrefixAsync Authorization.PermissionDenied IFeatureManager.IsEnabledAsync AuthorizationCommandDecorator TransactionalCommandDecorator AuthorizationQueryDecorator FeatureGateCommandDecorator OperationCanceledException ValidatingCommandDecorator CqrsMetrics.QueryDuration ExecuteInTransactionAsync"},{"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 GetProductByIdQuery IRequiresPermission GetTicketByIdQuery ICacheInvalidating FeatureManagement GetOrderByIdQuery GetNowNextQuery IQueryCacheable ITransactional CacheDuration IFeatureGated"},{"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 IsResolved tenantId TenantId TResult"},{"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":"cqrs.authorization.denied.count AuditableAggregateRootEntity TypeInitializationException InvalidOperationException RecordAuthorizationDenied DeleteSessionCommand DeleteSpeakerCommand ResultFailureFactory DeleteEntityCommand DeleteEntityHandler cqrs.timeout.count ICacheInvalidating"},{"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":"Five 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 IEntityDTOProjector EntityQueryService ScheduledJobRunner cancellationToken IAuditTrailReader AuditTrailReader IAsyncDisposable"},{"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 Microsoft.Extensions.DependencyInjection CqrsMetrics.RecordAuthorizationDenied QueryCacheKeyLocks.Locks.AcquireAsync MMCA.Common.Application.Extensions MMCA.Common.Application.Interfaces ICacheService.RemoveByPrefixAsync correlationContext.CorrelationId MMCA.Common.Application.UseCases System.Diagnostics.Metrics.Meter ConferenceCategoryCreateRequest"},{"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 PushNotificationDTOProjection.ProjectToDTO PushNotificationProjectionTranslationTests MMCA.Common.API.Controllers.Notifications NotificationHub.ReceiveNotificationMethod PushNotificationInvariants.TitleMaxLength Microsoft.Extensions.DependencyInjection PushNotificationInvariants.BodyMaxLength"},{"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 System.Threading.RateLimiting.RateLimitLease IDbContextFactory.HasPendingMigrationsAsync AuthorizationPolicies.RequireAuthenticated Microsoft.AspNetCore.Authentication.Google StackExchange.Redis.IConnectionMultiplexer ArgumentException.ThrowIfNullOrWhiteSpace System.Threading.RateLimiting.RateLimiter"},{"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.Gateway 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-resilience-numbers","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"One source of truth for resilience numbers","x":"The numbers that pipeline uses are not literals in the Aspire package. They live in HttpResilienceDefaults (Level 0,…","i":"MMCA.Common.Infrastructure MMCA.Common.Grpc Continuity properties Resilience RetryCount including Business Concerns lifetime sampling attempt"},{"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:336) 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 MMCA.Common.OutputCache ConfigureOpenTelemetry MMCA.Common.BestEffort TryGetTraceSampleRatio MMCA.Common.Scheduler"},{"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 next 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#the-gateway-edge-kit-correlation-rate-limiting-downstream-readiness","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The gateway edge kit: correlation, rate limiting, downstream readiness","x":"A YARP gateway is the one process every client request passes through, and it is also the one host that has no application container: no DbContext, no module loader, no…","i":"AddServiceDiscoveryDestinationResolver AddGatewayDownstreamHealthChecks Connection.RemoteIpAddress MMCA.Common.Aspire.Gateway Validator.ValidateObject HttpResponse.OnStarting ValidateDataAnnotations AddGatewayRateLimiting GlobalConcurrencyLimit UseGatewayRateLimiting ConfigureClusterAsync RequestVersionOrLower"},{"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 E2E helpers, and the…","i":"Azure.Extensions.AspNetCore.Configuration.Secrets Azure.Extensions.AspNetCore.DataProtection.Blobs identityService.WithE2eRegistrationThrottleLift AddGatewayDownstreamHealthChecks_IsIdempotent LoginProtection__MaxRegistrationsPerIpPerHour ConnectionStrings__SQLServerConnectionString database.Resource.ConnectionStringExpression Readiness_IncludesADownstreamCheckPerService ResilienceCircuitBreakerFaultInjectionTests WarmupReadinessHealthCheck.CheckHealthAsync HttpKeepAlivePingPolicy.WithActiveRequests cancellationToken.IsCancellationRequested"},{"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 Microsoft.Extensions.DependencyInjection MMCA.ADC.Migrations.SqlServer.Conference ApplyConfigurationsForEntitiesInContext SessionInvariants.AnswerValueMaxLength SpeakerInvariants.AnswerValueMaxLength QuestionInvariants.ManualIdRangeStart SpeakerQuestionAnswerConfiguration.cs EventInvariants.AnswerValueMaxLength"},{"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 UserSessionBookmarkCacheEvictionHandlerTests assemblyProvider.GetConfigurationAssemblies AuthorizationPolicies.RequireAuthenticated CheckInsController.GetAttendanceStatsAsync"},{"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 chapter covers. One Blazor component library in MMCA.Common.UI renders on three very different heads: Blazor Server (server-side prerender plus interactive Server…","i":"MauiBackNavigationBridge.HandleBackPressedAsync MMCA.Common.UI.Services.Capabilities.Fallbacks MMCA.Common.UI.Services.Capabilities.Browser builder.Services.AddMauiDeviceCapabilities MauiLocalNotificationService.ScheduleAsync WebAuthenticator.Default.AuthenticateAsync ArgumentException.ThrowIfNullOrWhiteSpace Battery.Default.EnergySaverStatusChanged CommunityToolkit.Maui.Media.SpeechToText Connectivity.Current.ConnectivityChanged CultureInfo.DefaultThreadCurrentCulture ILocalNotificationService.ScheduleAsync"},{"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":"CancellationTokenConventionTestsBase ConstructorDependencyCountTestsBase IntegrationEventContractTestsBase MMCA.Common.Testing.Architecture ObservabilityConventionTestsBase AggregateRootsHaveResultFactory MicroserviceExtractionTestsBase RawQueryableConventionTestsBase IdempotencyConventionTestsBase ArchitectureRules.Entities.cs AggregateConventionTestsBase CrossEntityNavigationFinder"},{"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 MMCA.Common.Architecture.Tests.CycleFixtures.Left Module_ShouldDeclare_ExpectedRequiresDependencies PiiRedactor_LeaksNoClearTextPii_ToLogsOrTelemetry CultureSwitch_ToSpanish_ShouldLocalizeAndPersist EveryRunbookAlertSection_MapsToAProvisionedAlert MobileViewport_CultureAndTheme_ShouldBeReachable ModuleShared_ShouldNotDependOn_OwnInternalLayers OpenApiDocument_DescribesEveryCorePublicResource Register_WithMismatchedPasswords_ShouldShowError"},{"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 UserSessionBookmarkCacheEvictionHandlerTests PushNotificationProjectionTranslationTests SpecificationsDoNotNavigateToOtherEntities CachingDecoratorConstructorSelectionTests CurrentUserTargetingContextAccessorTests Microsoft.Extensions.DependencyInjection MMCA.ADC.Conference.Infrastructure.Tests MMCA.ADC.Engagement.Infrastructure.Tests MMCA.ADC.Notification.Application.Tests EntityServiceBaseIdempotencyRetryTests MMCA.ADC.CrossService.IntegrationTests"},{"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,890 individually-sectioned types are missing from their group chapter, every one appears as a heading or in a sibling-family…","i":"SpecificationsDoNotNavigateToOtherEntities UserSessionBookmarkCacheEvictionHandler MMCA.Common.Infrastructure.Redis.Tests DatabaseInitializationExtensionsTests HttpContextExternalLoginEmailVerifier MMCA.ADC.Conference.Application.Tests MMCA.ADC.Engagement.Application.Tests ObservabilityConventionTestsBaseTests OwnSessionQuestionAnswerSpecification AnonymousAuthenticationStateProvider AzureNotificationHubNativePushSender CancellationTokenConventionTestsBase"},{"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,465 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 Event.OrganizerContactEmail PseudoLocalizationTests"},{"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…","i":"OrganizerContactEmail"},{"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-seventh-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 Event.OrganizerContactEmail"},{"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 PublicSessionList.razor.cs needs.changes.outputs.ui PrimitivesSnapshotTests MMCA.Common.Testing.UI OrganizerContactEmail TreatWarningsAsErrors DeviceSettings.razor EventDetail.razor.cs"},{"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 OrganizerContactEmail BrandColorTokenTests DeviceSettings.razor skip_freshness_gates"},{"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 SoftDeleteUniqueIndexConvention EventVersioningConventionTests ListPageQueryStateServiceTests PermissionAuthorizationHandler"},{"u":"/docs/governance/common-ArchitectureScorecard.html#indices","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Indices","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 316 ÷ 324 = 97.5% (up from 96.9% (314/324) on the targeted 2026-08-22 update: §9 API & Contract Design Maturity 3→4 on…","i":"ServiceContract"},{"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 ServiceContractPurityTestsBase OutboxPollFilterProcessor NavigationContractTests required_status_checks PiiConventionTests CONTRIBUTING.md ServiceContract DEPLOYMENT.md IAnonymizable PiiRedactor COST.md"},{"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 97.5% / Implementation 84.8%, framework v1.160.0. Twenty-eighth-wave full re-score, 2026-08-23 (git…","i":"ServiceContractPurityTestsBase ArchitectureScorecard.md required_status_checks RedisDistributedLock IDistributedLock BenchmarkDotNet IsDirtyAccessor ServiceContract Performance baseline c911480 d12cc4d"},{"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#progress---security-invariants-wave-11-hardening-2026-08-22","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - security invariants wave (§11 hardening, 2026-08-22)","x":"Closes the two §11 gaps surfaced by the Article 16 (JWKS dual-fetch) review: the insecure dev defaults that no two-axis entry named as scheduled work, and the absent security…","i":"AnonymousEndpointTestsBase AddForwardedJwtBearer requireHttpsMetadata RequireHttpsMetadata RsaJwksProvider AllowAnonymous configuration environment authority audience string false"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---9-contract-surface-gates-2026-08-22","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - §9 contract-surface gates (2026-08-22)","x":"Closes both halves of 9, the last weight-2 Maturity-3 item that had a named in-repo lever. Landed via MMCA.Common PR 271 (squash 8a6c603, merged 2026-08-22). - ✅ OpenAPI…","i":"ServiceContractsDoNotDependOnServiceInternals OpenApiBaselineTests AddCommonOpenApi MapCommonOpenApi ServiceContract ProblemDetails FACTS.md"},{"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 IsDeleted IsFailure preview TResult CS1591"},{"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":"ServiceContractsDoNotDependOnServiceInternals EntityQueryPipeline.MaxUnboundedResultLimit ApplicationSettings.MaxPageSize MessageBusSettings.EnableInbox ServiceContractPurityTestsBase ArchitectureRules.Slices.cs MobileInfiniteScrollList OpenApiContractTestsBase ServiceContractAttribute Directory.Build.targets AddCommonApiVersioning required_status_checks"},{"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 ServiceContract EditorRequired Performance baseline navbar"},{"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 IdentityModuleDbSeederBase.ShouldSeed SqlServerIntegrationTestFixtureBase CultureInfo.InvariantCulture MobileInfiniteScrollList ProductVariantChanged NotifyStateChanged workflow_dispatch EmailExistsAsync CatalogBrowse GetPagedAsync InventoryItem"},{"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, the two password-reset pages, 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. UC-34 (Request password reset) and UC-35 (Reset password) in the…","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 PasswordReset__ResetUrl PasswordResetController Auth.InvalidResetToken MaxValidationAttempts RequestWindowMinutes Speaker.LinkedUserId TokenLifetimeMinutes User.LinkedSpeakerId MaxRequestsPerEmail UserPasswordChanged"},{"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, the two password-reset pages, and the public catalog. Add-to-cart on the product detail page sits inside an…","i":"AuthorizeView Authorize"},{"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 Auth.InvalidResetToken IProductVariantService"},{"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":"ForgotPasswordRequestValidator ProductVariantConfiguration.cs ResetPasswordRequestValidator InventoryItemInvariants.cs AdjustInventoryHandler.cs PasswordResetTokenService ShoppingCartInvariants.cs CategoryConfiguration.cs CheckOutDomainService.cs CustomerConfiguration.cs ForgotPasswordHandler.cs UserRegisteredHandler.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":"OrderPaymentFailedSagaHandler payment_intent.payment_failed EventUtility.ConstructEvent checkout.session.completed checkout.session.expired OrderPaidHandler Result.Failure StripeSettings WebhookSecret IEmailSender 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 POST 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: Proof & getting started · No. 50 The…"},{"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":"96 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/the-mmca-series-every-pattern-one-place-28cf2cee7be8","t":"The series index","d":"Article no. 50","k":"Proof & getting started","x":"The full series index and recommended reading order.","e":1},{"u":"https://medium.com/@ivanball76/undo-is-a-feature-saga-compensation-and-the-reconciliation-backstop-fa017f9591b8","t":"Saga compensation and the reconciliation backstop","d":"Article no. 49","k":"Core patterns","x":"Undo as a first-class event handler: give back stock a committed transaction already took, with a periodic sweep as the saga-timeout backstop.","e":1},{"u":"https://medium.com/@ivanball76/observability-by-default-opentelemetry-and-azure-monitor-in-mmca-673c1886e9e0","t":"Observability by default","d":"Article no. 48","k":"Run & extract","x":"A shared OpenTelemetry baseline with CQRS duration metrics, correlation IDs, and outbox-poll span filtering, exported to Azure Monitor.","e":1},{"u":"https://medium.com/@ivanball76/security-headers-and-csp-for-blazor-one-middleware-every-host-af82df95236e","t":"Security headers and CSP for Blazor","d":"Article no. 47","k":"Auth & the edge","x":"One middleware stamps hardened response headers on every host, with the Blazor CSP resolved through a pluggable provider.","e":1},{"u":"https://medium.com/@ivanball76/field-level-encryption-in-ef-core-aes-gcm-for-pii-columns-06ece340ea25","t":"Field-level encryption in EF Core","d":"Article no. 46","k":"Data & persistence","x":"An AES-256-GCM value converter that keeps a PII column ciphertext even for someone who can query the database.","e":1},{"u":"https://medium.com/@ivanball76/feature-flags-in-the-cqrs-pipeline-gate-commands-not-code-e58b9ea8d098","t":"Feature flags in the CQRS pipeline","d":"Article no. 45","k":"Core patterns","x":"Gate commands and queries at the outermost decorator, so a handler never checks a flag and a disabled feature is rejected before any work runs.","e":1},{"u":"https://medium.com/@ivanball76/http-api-versioning-proven-not-just-claimed-2b0381e4b533","t":"HTTP API versioning, proven not just claimed","d":"Article no. 44","k":"Auth & the edge","x":"Header-based versioning introduced without breaking a single caller, plus a fitness contract that proves two live versions coexist.","e":1},{"u":"https://medium.com/@ivanball76/managed-file-storage-uploads-you-dont-have-to-trust-8dfe8bf016bc","t":"Managed file storage: uploads you don't have to trust","d":"Article no. 43","k":"Data & persistence","x":"Attacker-controlled bytes become safe avatars: content sniffing, metadata stripping, re-encoding, and pluggable blob storage.","e":1},{"u":"https://medium.com/@ivanball76/one-blazor-ui-two-hosts-a-device-capability-layer-that-stays-resolvable-everywhere-b85444693161","t":"One Blazor UI, two hosts","d":"Article no. 42","k":"Proof & getting started","x":"The same Blazor components run in a browser and inside a MAUI hybrid app; small per-capability contracts reach native hardware without ever asking 'am I on mobile?'.","e":1},{"u":"https://medium.com/@ivanball76/two-real-apps-on-one-framework-a-conference-platform-and-a-store-12f694d2a361","t":"Two real apps on one framework","d":"Article no. 41","k":"Proof & getting started","x":"A case study: a conference platform and an e-commerce store built on the same kernel.","e":1},{"u":"https://medium.com/@ivanball76/write-your-first-architecture-fitness-test-d4e25e6a4741","t":"Write your first fitness test","d":"Article no. 40","k":"Proof & getting started","x":"Author your first architecture fitness test and watch it fail the build on a violation.","e":1},{"u":"https://medium.com/@ivanball76/scaffold-a-net-modular-monolith-in-one-command-then-build-your-first-module-b10aacd16d33","t":"Build your first module","d":"Article no. 39","k":"Proof & getting started","x":"A hands-on walkthrough of building a new module across all five layers.","e":1},{"u":"https://medium.com/@ivanball76/one-preference-two-switches-shipping-i18n-and-dark-mode-on-a-single-cookie-and-profile-pipeline-f97186038909","t":"i18n and theming on one preference pipeline","d":"Article no. 38","k":"Proof & getting started","x":"A culture choice and a theme choice ride the same cookie, profile column, and login reconciliation: one persistence path, two switches.","e":1},{"u":"https://medium.com/@ivanball76/a-list-page-in-a-few-lines-a-reusable-blazor-ui-framework-with-the-same-discipline-as-the-backend-c66fa16cd561","t":"A reusable Blazor UI framework","d":"Article no. 37","k":"Proof & getting started","x":"A shared Blazor and MudBlazor UI layer with accessibility enforced by axe in CI.","e":1},{"u":"https://medium.com/@ivanball76/soft-delete-vs-the-right-to-erasure-the-gdpr-conflict-and-the-erasure-pathway-e1d350007509","t":"Soft-delete vs the right to erasure","d":"Article no. 36","k":"Proof & getting started","x":"Soft-delete for lifecycle, anonymization plus outbox purge for GDPR/CCPA erasure, and why both exist.","e":1},{"u":"https://medium.com/@ivanball76/the-test-pyramid-not-the-ice-cream-cone-1-880-fast-tests-zero-docker-cb459fda73d9","t":"The test pyramid","d":"Article no. 35","k":"Proof & getting started","x":"How the framework's tests stack up: fast unit and architecture tests at the base, E2E at the tip.","e":1},{"u":"https://medium.com/@ivanball76/architecture-fitness-functions-rules-that-fail-the-build-not-a-wiki-page-6562940deceb","t":"Architecture fitness functions","d":"Article no. 34","k":"Proof & getting started","x":"Architecture rules that fail the build: a compile-time layer guard plus a shared NetArchTest rule library.","e":1},{"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":1340,"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 AsyncMethodsDeclareTrailingCancellationToken MessageBusSettings.EnableDelayedRedelivery PushNotificationSettings.ChannelKeyPattern ApplicationDbContext.ConfigureConventions Microsoft.CodeAnalysis.PublicApiAnalyzers RegisterUpcastedIntegrationEventConsumer ApiParameterDescriptorBackfillProvider IWriteRepository.SetOriginalRowVersion ServiceInfoVersioningContractTestsBase ApplicationDbContext.OnModelCreating MMCA.Common.LayerEnforcement.targets"},{"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 (31 DTO mappers across Store + ADC: 20 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. Revised 2026-08-23 (the [ServiceContract] marker now has a dedicated fitness rule behind it, ServiceContractPurityTestsBase, subclassed in all four repos; it is a…","i":"ServiceContractPurityTestsBase ServiceContract"},{"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 grpc"},{"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. Amended by ADR-089 (2026-08-18): the Gateway keeps the route-to-service map this record gave it, but stops expressing it as MapForwarder calls in code. YARP…","i":"MapForwarder ReverseProxy"},{"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). Amended by ADR-087 (2026-08-18): the resilience objective extends past outbound HTTP and gRPC clients for the first time, to the outbox's broker publish,…"},{"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/009-resilience-and-recovery-objectives.html#revision-2026-08-18","d":"ADR-009: Resilience Policies & Recovery Objectives","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"This record's first Decision point scoped resilience to \"every outbound HttpClient and gRPC client registered through the framework's extension methods\". That scope was accurate…","i":"BrokerResilienceDefaults BrokenCircuitException HttpResilienceDefaults CommandTimeoutSeconds EnableRetryOnFailure ResiliencePipeline DbContextFactory OutboxProcessor HttpClient"},{"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…","i":"OutputCacheEvictionRequested"},{"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). At…","i":"RegisterUpcastedIntegrationEventConsumer MMCA.Helpdesk.Architecture.Tests EventVersioningConventionTests ProductCreatedIntegrationEvent MMCA.Store.Architecture.Tests OutputCacheEvictionRequested TicketOpenedIntegrationEvent MMCA.ADC.Architecture.Tests OrderPlacedIntegrationEvent EventConventionTestsBase CommonArchitectureMap map.ModuleNames.Count"},{"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). Revised 2026-08-18 (the pipeline order…"},{"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#revision-2026-08-18","d":"ADR-014: CQRS Handlers with a Decorator Pipeline","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two decorators were added to both chains, so the order recorded in the Decision above is no longer the shipped one. The registration site is unchanged in kind:…","i":"CancellationTokenSource.CreateLinkedTokenSource cqrs.authorization.denied.count DecoratorPipelineOrderTestsBase AuthorizationCommandDecorator ExpectedCommandDecorators AddApplicationDecorators ExpectedQueryDecorators AuthorizationDenied ICurrentUserService IPermissionRegistry IRequiresPermission budget.CancelAfter"},{"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, and the Failure error type the timeout decorator reuses because the taxonomy has no timeout member), ADR-003…","i":"IPermissionRegistry MMCA.Common.Cqrs HasPermission SaveChanges Failure"},{"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. Revised 2026-08-18 (two new rule families, namespace dependency cycles and trailing CancellationToken declarations, plus a third enforcement layer: a compile-time…","i":"CancellationToken proto"},{"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 (a third joined them on 2026-08-18: see the Revision at the end). 1. Compile-time guard.…","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#revision-2026-08-18","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two new rule families joined the shared library, and a third enforcement layer joined the two the Decision above describes. The counts in MMCA.Common/FACTS.md move with them: 102…","i":"AsyncMethodsDeclareTrailingCancellationToken Microsoft.CodeAnalysis.PublicApiAnalyzers ArchitectureRules.CancellationTokens dotnet_analyzer_diagnostic.severity NamespacesHaveNoDependencyCycles MMCA.Common.Infrastructure Context.ConnectionAborted IHostedService.StartAsync ArchitectureRules.Cycles TenancySettingsValidator InternalAPI.Shipped.txt NamespaceCycleTestsBase"},{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-08-18-section-b-rule-families","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-18): Section B rule families","x":"A second entry on the same date, kept separate rather than folded into the one above because it lands with a different wave and changes a number that revision states. Two rule…","i":"IdempotencyConventionTestsBase ArchitectureRules.Protos ProtoContractTestsBase PublicAPI.Shipped.txt FrozenProtoContracts csharp_namespace SolutionFileName FactsGenerator justification NonIdempotent Idempotent ProtoFiles"},{"u":"/docs/adr/015-architecture-fitness-functions.html#revision-2026-08-23-superseded-counts-a-re-anchored-citation-and-the-real-test-floor","d":"ADR-015: Architecture Invariants Enforced as Fitness Functions","k":"Architecture Decision Records","t":"Revision (2026-08-23): superseded counts, a re-anchored citation, and the real test floor","x":"No rule family joined or left the library in this entry. It corrects three things the two 2026-08-18 revisions above state, and it is kept as its own entry rather than edited…","i":"Microsoft.CodeAnalysis.PublicApiAnalyzers SpecificationFitnessTests MMCA.Common.UI.E2E.Tests FrameworkSanityTests FactsGenerator FACTS.md"},{"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 the lockstep release cadence the public API baseline is pinned to), ADR-006/007/008…","i":"CancellationToken"},{"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":"IdempotencyConventionTestsBase IDistributedLock justification NonIdempotent ObjectResult Idempotent NoContent HttpPost"},{"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#revision-2026-08-18","d":"ADR-017: HTTP Request Idempotency via Client-Supplied Keys","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The last Trade-off above is the one this revision addresses, and it does so by changing what is required. Nothing here makes an endpoint idempotent. What it requires is that…","i":"PostActions_ShouldDeclare_IdempotencyIntent IdempotencyConventionTestsBase AttributeTargets.Method NonIdempotentAttribute GetCustomAttributes AuthControllerBase EnableRateLimiting MMCA.Common.API AttributeUsage justification Justification NonIdempotent"},{"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 NonIdempotent"},{"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…","i":"RateLimitingSettings UserPolicy"},{"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#revision-2026-08-18","d":"ADR-019: Layered Rate Limiting with an Authenticated-Only Global Limiter","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The layering above is unchanged: the global limiter is still authenticated-only, infrastructure and anonymous traffic are still exempt, auth-ip still covers login and register by…","i":"RateLimitAlgorithm.FixedWindow RedisFixedWindowRateLimiter IConnectionMultiplexer Interlocked.Exchange RateLimitingSettings StringIncrementAsync PerUserPermitLimit AuthIpPermitLimit GlobalPermitLimit SegmentsPerWindow allowDistributed SlidingWindow"},{"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…","i":"RateLimitingSettings IncrementAsync UseRateLimiter Distributed INCR"},{"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 and 2026-08-23)."},{"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). Revised 2026-08-18 (the inbox stays opt-in, but being off is no longer silent: a broker-connected host running NoOpInboxStore…","i":"NoOpInboxStore InboxMessages"},{"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 InProcess"},{"u":"/docs/adr/021-consumer-inbox-idempotency.html#revision-2026-08-18","d":"ADR-021: Consumer-Side Inbox for Integration-Event Idempotency","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"The decision is unchanged: the inbox is still opt-in and NoOpInboxStore is still the default. What changed is that the default is now loud. 1. A broker-connected host with no…","i":"ApplicationDbContext.OnModelCreating MessageBusProvider.InProcess InboxDisabledWarningService IX_InboxMessages_MessageId IEntityTypeConfiguration base.OnModelCreating AddBrokerMessaging SQLServerDbContext AddInboxMessages CosmosDbContext SqliteDbContext ConfigureInbox"},{"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":"OutputCacheEvictionRequested MMCA.Common.OutputCache HybridCacheService ICacheService remarks INCR"},{"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 MiddlewarePipelineStepNames.OutputCache AbsoluteExpirationRelativeToNow PublicEndpointOutputCachePolicy CacheOptions.DefaultExpiration CacheOptions.DefaultDuration DistributedCacheEntryOptions DistributedCacheService IConnectionMultiplexer LoginProtectionService MemoryDistributedCache"},{"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 remarks absexp"},{"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":"RegisterUpcastedIntegrationEventConsumer OutputCacheEvictionRequested LoginProtectionService HybridCacheService ICacheInvalidating IQueryCacheable IncrementAsync ICacheService WRONGTYPE TEvent"},{"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 WebApplicationExtensions.cs AddRedisDistributedCache DistributedCacheService HybridCacheEntryOptions TimeSpan.FromSeconds app.UseOutputCache HybridCacheService DefaultExpiration"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-18","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Every previous revision moved Tier 1. This one moves Tier 2, and it is the first change to the output-cache edge since ADR-040. Tier 2 as decided here is per-process by…","i":"RegisterUpcastedIntegrationEventConsumer RegisterOutputCacheEvictionConsumer RegisterIntegrationEventConsumer AddOutputCacheEvictionHandler OutputCacheEvictionRequested OperationCanceledException OutputCacheEvictionHandler BestEffort.ExecuteAsync MMCA.Common.OutputCache MMCA.Common.BestEffort cache.eviction.failed registerFaultConsumer"},{"u":"/docs/adr/026-caching-strategy.html#revision-2026-08-23","d":"ADR-026: Two-Tier Caching: a Swappable ICacheService Substrate plus an HTTP Output-Cache Edge","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"One correction of substance plus a line-anchor re-verification. No decision and no behavior changed. 1. The counter trade-off now names the contradiction a reader will hit.…","i":"RegisterUpcastedIntegrationEventConsumer MiddlewarePipelineStepNames.OutputCache RegisterOutputCacheEvictionConsumer BaseIntegrationEvent.SchemaVersion AddStackExchangeRedisOutputCache OutputCacheEvictionRequested DistributedCacheService app.UseOutputCache IncrementAsync UseOutputCache ICacheService AddCaching"},{"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). Revised 2026-08-18 (a targeting-context accessor is now registered, so the built-in Targeting and Percentage filters give consistent per-user bucketing…"},{"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#revision-2026-08-18","d":"ADR-031: Config-Driven Feature Flags with Dual-Surface Enforcement","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Progressive rollout is now usable, because the targeting context exists. The Decision above listed the Percentage / TimeWindow / Targeting filters as \"available\", and the last…","i":"CurrentUserTargetingContextAccessor FeatureGateCommandDecorator ITargetingContextAccessor featureGated.FeatureName AddHttpContextAccessor IHttpContextAccessor ICurrentUserService ClaimTypes.Role IFeatureManager IsEnabledAsync Identity.Name WithTargeting"},{"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, now with Authorization registered directly inside it so a disabled…","i":"FeatureGate Groups 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, call-site hoist recorded 2026-08-23)."},{"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 IdentityModuleDbSeederBase AuthenticationServiceBase ChangePasswordHandlerBase Rfc2898DeriveBytes.Pbkdf2 HashAlgorithmName.SHA512 IdentityModuleDbSeeder AuthenticationService ChangePasswordHandler LegacyHmacSaltSize"},{"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/032-password-hashing.html#revision-2026-08-23","d":"ADR-032: Password Hashing (PBKDF2-HMAC-SHA512) with Legacy-Hash Backward Compatibility","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision is unchanged: one framework-owned IPasswordHasher, PBKDF2-HMAC-SHA512 for new passwords, salt-length dispatch on verification. What changed is where the last two…","i":"IdentityModuleDbSeederBase ChangePasswordHandlerBase IdentityModuleDbSeeder AuthenticationService ChangePasswordHandler IPasswordHasher ChangePassword VerifyPassword HashPassword HandlerName CreateUser Accounts"},{"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:78 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). Revised 2026-08-18: the same token gains an HTTP-native…","i":"SetOriginalRowVersion IConcurrencyAware SupportsIfMatch GetByIdAsync RowVersion ETag"},{"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#revision-2026-08-18","d":"ADR-035: Optimistic Concurrency via RowVersion Round-Trip","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"This record chose a body round-trip: the client echoes RowVersion on the update request. HTTP has had a standard way to say the same thing since long before this framework, ETag…","i":"RewriteConflictToPreconditionFailed EntityControllerBase.GetByIdAsync UpdateRequestsAreConcurrencyAware DbUpdateConcurrencyException DbUpdateExceptionHandler SupportsIfMatchAttribute ServiceFilterAttribute SetOriginalRowVersion IAsyncActionFilter SetConcurrencyETag HttpContext.Items IConcurrencyAware"},{"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, and whose own 2026-08-18 revision adds…","i":"AuditableBaseEntity GetByIdAsync 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, 2026-08-18). Revised 2026-08-18: the versioned-envelope converter is no longer unpublished, it is included in…"},{"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 main 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":"OutputCacheEvictionRequested 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":"UserSessionBookmarkCacheEvictionHandler RegisterOutputCacheEvictionConsumer AddStackExchangeRedisOutputCache AddOutputCacheEvictionHandler OutputCacheEvictionRequested AddRedisDistributedCache AddPublicEndpointPolicy BookmarkCountsCache IDistributedCache EvictByTagAsync AddOutputCache NowNextCache"},{"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":"MMCA.Common.OutputCache MMCA.Common.BestEffort 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#revision-2026-08-18","d":"ADR-041: Observability and Telemetry Strategy","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Two meters and one hop. Two new failure counters, each on its own meter. cache.eviction.failed, tagged cachetag, on MMCA.Common.OutputCache…","i":"GatewayCorrelationMiddleware besteffort.dispatch.failed CorrelationIdMiddleware MMCA.Common.Idempotency MMCA.Common.OutputCache MMCA.Common.BestEffort cache.eviction.failed MMCA.Common.Scheduler MMCA.Common.Broker MMCA.Common.Outbox MMCA.Common.Cqrs Idempotency"},{"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). Revised 2026-08-07 (validator hoisted into a shared generic, the 30-second constant moved, the two apps revoke at different speeds). Revised 2026-08-23:…","i":"MiddlewarePipelineBuilder WebApplicationExtensions"},{"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 MiddlewarePipelineBuilder.CreateDefault SoftDeletedUserCache.MarkDeletedAsync SoftDeletedUserCache.MarkerDuration context.RequestServices.GetService MiddlewarePipelineBuilder.Build SoftDeletedUserMiddlewareTests AuditableAggregateRootEntity SoftDeletedUserCache.KeyFor UseCommonMiddlewarePipeline ICurrentUserService.UserId TenantResolutionMiddleware"},{"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…","i":"TenantResolutionMiddleware"},{"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/047-soft-deleted-user-session-revocation.html#revision-2026-08-23","d":"ADR-047: Runtime Revocation of Soft-Deleted Users' Active Sessions","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"Re-verified against current source. The decision, the cache design, the fail-open policy and the per-app asymmetry are all unchanged; what moved is where the middleware's…","i":"MiddlewarePipelineBuilder.CreateDefault app.UseCommonMiddlewarePipeline MiddlewarePipelineBuilder.Build UseCommonMiddlewarePipeline WebApplicationExtensions.cs TenantResolutionMiddleware ISoftDeletedUserValidator SoftDeletedUserMiddleware SoftDeletedUserFilter UseAuthentication TenantResolution UseAuthorization"},{"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":"ActivityIdentifierType 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/048-primitive-identifier-type-aliases.html#revision-2026-08-18","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"No decision, no behavior and no citation in this record changed. What changed is the standing of the deferral it records. The last Trade-offs entry above (\"Revisiting the trade…","i":"UserIdentifierType TIdentifierType CheckIn Source razor int"},{"u":"/docs/adr/048-primitive-identifier-type-aliases.html#revision-2026-08-23","d":"ADR-048: Primitive Identifier Type Aliases over Strongly-Typed ID Structs","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"No decision and no rationale changed. Two counts did, both because Conference gained an alias. Conference's alias file declares seventeen aliases, ActivityIdentifierType = int…","i":"ActivityIdentifierType SpeakerIdentifierType System.Guid Guid int"},{"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, 2026-08-14, 2026-08-18 and 2026-08-23)."},{"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; 767 gated sites as of the 2026-08-23…","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/049-library-configureawait-policy.html#revision-2026-08-18","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"A re-measurement only, in the same terms as the 2026-08-14 pass. The policy, the gate and the exemption are unchanged; two of the three counted figures moved. 1. Framework site…","i":"CodeAnalysisTreatWarningsAsErrors TreatWarningsAsErrors MMCA.Common.UI.Maui MMCA.Common.UI.Web ConfigureAwait MMCA.Common.UI analyzers warning CA2007 dotnet format await"},{"u":"/docs/adr/049-library-configureawait-policy.html#revision-2026-08-23","d":"ADR-049: Library-Scoped ConfigureAwait(false) Policy (CA2007)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"A re-measurement only, in the same terms as the 2026-08-18 pass. The policy, the gate and the exemption are unchanged; both counted figures moved. 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). Revised 2026-08-23 (post-commit enqueue is recorded as two patterns, not one: see the revision at the end)."},{"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 unitOfWork.SaveChangesAsync LiveChannelPublishQueue SessionScoringProcessor sp.GetRequiredService IServiceScopeFactory SessionScoringQueue BackgroundService SaveChangesAsync TryAddSingleton ITransactional"},{"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/052-background-job-execution.html#revision-2026-08-23","d":"ADR-052: Background Job Execution (Bounded Queue plus Hosted Drain)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision is unchanged: post-commit work is still enqueued only once the write is durable. What changed is the record of how. This ADR stated the domain-event handler as the…","i":"SessionQuestionUpvoteChangedHandler TransactionalCommandDecorator SessionQuestionUpvoteChanged unitOfWork.SaveChangesAsync LivePollVoteChangedHandler BestEffort.ExecuteAsync ModerateQuestionHandler EnqueueModeratedAsync EnqueueSubmittedAsync SubmitQuestionHandler CloseLivePollHandler IDomainEventHandler"},{"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 QuerySpecification SessionsController Expression.Invoke EFReadRepository IEntityQuerier IRepository.cs IEntityReader ListAsync"},{"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 IUnitOfWork.GetReadRepository OrdersByCustomerSpecification PublishedEventSpecification TableNoTrackingSingleQuery ParameterReplacer.Replace TableNoTrackingSplitQuery OwnedByUserSpecification publicSpecification.And EntityQueryService.cs ProductVariantService SpecificationComposer"},{"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. (Superseded by the Revision (2026-08-21) below: the split has real dependents shipped in both MMCA.ADC and MMCA.Store,…","i":"QuerySpecification Expression.Invoke ParameterReplacer Specification.cs ISpecification IEntityReader IUnitOfWork 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/055-repository-and-specification-contract.html#revision-2026-08-18","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-08-18)","x":"Five changes, four of them widening the contract and one of them fixing a correctness defect. The decision this record states is unchanged: data access is still repository plus…","i":"NavigationMetadata.UnsupportedIncludes QueryFieldService.ApplySorting PushNotificationDTOProjection PushNotificationDTOProjector KeysetQueryBuilder.Compare PaginationTieBreakProperty EFReadRepositoryDecorator CrossSourceSpecification Error.InvalidEntityField SpecificationExtensions KeysetCollectionResult ExecuteProjectedAsync"},{"u":"/docs/adr/055-repository-and-specification-contract.html#revision-2026-08-21","d":"ADR-055: Repository plus Specification as the Data-Access Contract","k":"Architecture Decision Records","t":"Revision (2026-08-21)","x":"Nothing in the contract changed; its consumers did. This revision records the first real adoption of the two surfaces this record had honestly flagged as unconsumed: the narrow…","i":"PublicConferenceVisibility SpecificationExtensions specification.Criteria ProductVariantService GetPageByCursorAsync GetProjectedAsync GetReadRepository AndSpecification IReadRepository IEntityQuerier IEntityReader specification"},{"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 App.razor MudTable ADCHome"},{"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, 2026-08-18, and 2026-08-23)."},{"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 MiddlewarePipelineOrderTestsBase MMCA.Common.Testing.Architecture ProductionHostApplicationFactory DecoratorPipelineOrderTestsBase MiddlewarePipelineOrderTests.cs ProblemDetailsContractTestsBase AssertProblemDetailsShapeAsync GracefulShutdownTestsBase AddApplicationDecorators ChangePreferencesCommand"},{"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; vault-backed configuration source recorded and citations re-anchored 2026-08-23)."},{"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; the same identity also lets a…","i":"AddCommonKeyVaultConfiguration azureADOnlyAuthentication USE_MANAGED_IDENTITY_SQL DefaultAzureCredential useManagedIdentitySql AZURE_CLIENT_ID hasSmtpPassword IConfiguration MMCA.Templates KeyVault__Uri keyVaultUrl claude.yml"},{"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":"AZURE_CLIENT_ID 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 (037-field-level-encryption-at-rest.md:108-110 directs a consumer to keep the field-encryption key in Key Vault but decides no delivery mechanism, and nothing wires that…"},{"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). Revised 2026-08-18: Store's two operational extras (the outbox-dead-letter scheduled query rule and the outside-in Gateway availability web test with its…","i":"OPERATIONS.md main.bicep main"},{"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 RunbookHeadingRegex alertEmailAddress MinimumAlertSpecs infra.main.bicep ResourceAssembly"},{"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":"environmentName sloAlertSpecs metricAlerts prefix env 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 PasswordResetTestsBase.cs MMCA.Common.Testing.E2E ProfileManagementTests AxeOptions.Wcag21Aa PrimaryContrastText WarningContrastText GalleryAxeTestBase ErrorContrastText AxeRunOptions"},{"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 PasswordResetTestsBase 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…","i":"Directory.Packages.props"},{"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 IntegrationEventContractTests 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 EnableDelayedRedelivery RetryMaxIntervalSeconds RetryMinIntervalSeconds builder.Configuration UseDelayedRedelivery UsingAzureServiceBus"},{"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).…","i":"IValidateOptions IOptions"},{"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":"CreateCheckoutSessionCommandValidator GatewayRateLimitingSettings ForgotPasswordHandlerBase IConnectionStringSettings IPushNotificationSettings CheckoutRedirectSettings ConnectionStringSettings PushNotificationSettings RecordRoomCheckInHandler AddCommonAuthentication LoginProtectionSettings SecurityHeadersSettings"},{"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":"TenancySettingsValidator ValidateDataAnnotations IDataSourceResolver IValidatableObject IValidateOptions IOptionsMonitor TenancySettings 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":"MiddlewarePipelineOrderTestsBase MiddlewarePipelineStepNames DesignTimeDbContextHelper MiddlewarePipelineBuilder ApplicationDbContext IgnoreQueryFilters EFReadRepository AddMultiTenancy configuration ITenantEntity OnConfiguring GetService"},{"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":"CrossTenantWriteException.ForUnresolvedTenant MiddlewarePipelineBuilder.CreateDefault IPhysicalDbContextFactory.Create MiddlewarePipelineOrderTestsBase CosmosDbContext.OnModelCreating TenantSaveChangesInterceptor UseCommonMiddlewarePipeline TenantResolutionMiddleware CrossTenantWriteException DesignTimeDbContextHelper ITenantContext.SetTenant CachingCommandDecorator"},{"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, 2026-08-18). 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 ScheduledJobOverrideSettings.Cron DesignTimeDbContextHelper PeriodicBackgroundService Directory.Packages.props EnsurePermissionRegistry ValidateDataAnnotations PollingIntervalSeconds SchedulerSettings.Jobs SyncRegistrationsAsync ResolveCronExpression AuditTrailCleanupJob"},{"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":"OutputCacheEvictionRequested DistributedCacheService MMCA.Common.OutputCache 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 IConnectionMultiplexer CachingQueryDecorator DisableLocalCacheRead"},{"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; revised 2026-08-18). The implementation lands in the MMCA.Common \"enterprise capability wave\" release. Unlike the wave's other features this one is NOT…","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 ExportRowLimitHeaderName OpenApiContractTestsBase MaxUnboundedResultLimit QueryFilterModelBinder IEntityControllerBase EntityControllerBase"},{"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). Revised 2026-08-19 (refreshed the WebApplicationBuilderExtensions.cs cross-reference anchor, which moved to :555). Revised 2026-08-21: the order became…","i":"MiddlewarePipelineBuilder.CreateDefault WebApplicationBuilderExtensions.cs MiddlewarePipelineOrderTestsBase"},{"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:46), and…","i":"MiddlewarePipelineBuilder.CreateDefault MiddlewarePipelineOrderTestsBase DecoratorPipelineOrderTestsBase UseCommonRequestLocalization MiddlewarePipelineStepNames UseCommonMiddlewarePipeline TenantResolutionMiddleware ISoftDeletedUserValidator MiddlewarePipelineBuilder SoftDeletedUserMiddleware MiddlewarePipelineStep OidcDiscoveryEndpoint"},{"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":"- Two of this record's original costs are retired (2026-08-21). As accepted, nothing froze the order (no test referenced the method; the adjacencies were protected by comments…","i":"MiddlewarePipelineOrderTestsBase MiddlewarePipelineStepNames MapOidcDiscoveryEndpoint UseCommonSecurityHeaders PreForwardedCapture HttpContext.Items KnownIPNetworks InsertBefore KnownProxies PreForwarded Controllers 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 AddDefaultPolicy AllowCredentials IHostEnvironment AllowAnyHeader AllowAnyMethod"},{"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). Revised 2026-08-23: the adopter counts were refreshed (ADC Conference's ActivityChanged joined the base-derived set) and three source citations were…","i":"ActivityChanged"},{"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 additionalPortMappings"},{"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/adr/085-identifier-type-aliases-revisited.html","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#status","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Revised 2026-08-23 (the alias count and the migration-surface census were recounted, the census gained a stated methodology, and the CheckIn and…","i":"CheckIn"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#context","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Context","x":"ADR-048 decided that every entity identity is a primitive named through a per-module global using {Entity}IdentifierType = ... alias, and recorded the cost in one line of…","i":"SpeakerIdentifierType UserIdentifierType StronglyTypedId IdentifierType Notification System.Guid Entity global Source using Vogen and"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#decision","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Decision","x":"Keep the aliases. The wrapper-struct alternative is evaluated in this record, priced, and deferred again, this time against explicit triggers. Inside a module an identifier is…","i":"SessionIdentifierType SponsorIdentifierType EventIdentifierType UserIdentifierType checkedInByUserId IEntityDTOMapper TIdentifierType IdentifierType ValueConverter JsonConverter CheckInScope BaseEntity"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#rationale","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Rationale","x":"- The cost is paid once and the benefit accrues per defect avoided, and the defect count is currently zero. No production incident in any of the four repos has been traced to a…","i":"System.Text.Json Guid int"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#trade-offs","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The exposure is unmitigated, not reduced. This record buys no safety whatsoever. Every transposition ADR-048 could not catch is still uncatchable today, and the CheckIn…","i":"CheckIn.Create CheckIn Create int"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#related","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Related","x":"ADR-048 (the decision this record revisits and upholds; its Status now points here), ADR-068 (the deliberate opposite case: domain values carry invariants and therefore do get…"},{"u":"/docs/adr/085-identifier-type-aliases-revisited.html#revision-2026-08-23","d":"ADR-085: Identifier Type Aliases Revisited (Wrapper Structs Deferred Again, With Triggers)","k":"Architecture Decision Records","t":"Revision (2026-08-23)","x":"The decision, the priced alternative, the three triggers and the trade-offs are unchanged. What changed is arithmetic and three citations. The alias count is 44 across 10 files,…","i":"ActivityIdentifierType SpeakerIdentifierType IEntityDTOMapper TIdentifierType IdentifierType CheckInScope System.Guid TEntityDTO sponsorId IBaseDTO CheckIn TEntity"},{"u":"/docs/adr/086-process-manager-deferred.html","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records"},{"u":"/docs/adr/086-process-manager-deferred.html#status","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18) as a documented deferral. Nothing ships with this record: no state machine, no correlation store, no new package. What ships is the shape the coordinator…"},{"u":"/docs/adr/086-process-manager-deferred.html#context","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Context","x":"ADR-054 decided how this workspace achieves cross-boundary consistency without two-phase commit: choreography. Each step of a workflow raises a domain event, each compensating…","i":"PaymentReconciliationService PeriodicBackgroundService SagaStateMachineInstance MassTransitStateMachine Order.InventoryRestored InMemorySagaRepository Order.Status SaveChanges Source ISaga"},{"u":"/docs/adr/086-process-manager-deferred.html#decision","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Decision","x":"Defer the process manager, and record its shape so the deferral is a design decision rather than an omission. A durable multi-step workflow coordinator in this workspace is a…","i":"MassTransit.Azure.ServiceBus.Core MassTransitStateMachine MassTransit.RabbitMQ CorrelationId MassTransit InProcess TInstance Result"},{"u":"/docs/adr/086-process-manager-deferred.html#rationale","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Rationale","x":"- Choreography is genuinely correct for the workflow that exists. This is not a case of the simpler option being tolerated. Checkout's saga state is two fields on Order, and an…","i":"Order"},{"u":"/docs/adr/086-process-manager-deferred.html#trade-offs","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The first workflow to hit the trigger pays the full cost at once, under whatever deadline made it appear. Deferral moves the work onto the critical path of the feature that…","i":"SQLServerDbContext"},{"u":"/docs/adr/086-process-manager-deferred.html#related","d":"ADR-086: A Process Manager Is Deferred, Not Absent (Relates to ADR-054)","k":"Architecture Decision Records","t":"Related","x":"ADR-054 (the accepted mechanism this record defers an alternative to: choreographed compensation, the persisted aggregate marker, and the reconciliation sweep that would remain…"},{"u":"/docs/adr/087-broker-poison-message-handling.html","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records"},{"u":"/docs/adr/087-broker-poison-message-handling.html#status","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Amends ADR-009: the outbox's broker publish gains a circuit breaker, which is the first resilience policy this workspace applies to something other than an…"},{"u":"/docs/adr/087-broker-poison-message-handling.html#context","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Context","x":"Delivery in this workspace has always been at-least-once with retries on both legs: the outbox retries a failed publish with jittered exponential backoff and eventually…","i":"rabbitmq_delayed_message_exchange DeadLetterRetentionDays"},{"u":"/docs/adr/087-broker-poison-message-handling.html#decision","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Decision","x":"Three changes, each scoped to one failure: second-level redelivery configured per transport, a fault consumer with its own meter, and a circuit breaker around the outbox's broker…","i":"RegisterIntegrationEventConsumer settings.EnableDelayedRedelivery FaultIntegrationEventConsumer OperationCanceledException RedeliveryIntervalsSeconds broker.circuit.open.count BuildRedeliveryIntervals cfg.UseDelayedRedelivery ConfigureBrokerTransport EnableDelayedRedelivery BrokenCircuitException fault.FaultedMessageId"},{"u":"/docs/adr/087-broker-poison-message-handling.html#rationale","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Rationale","x":"- The transport asymmetry follows a real capability difference, not a preference. RabbitMQ needs a plugin the dev container lacks; Service Bus does not. A single default would be…","i":"BrokenCircuitException true"},{"u":"/docs/adr/087-broker-poison-message-handling.html#trade-offs","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Trade-offs","x":"- Delayed redelivery is off where the plugin problem lives. RabbitMQ is the local transport and also a plausible self-hosted production transport; both get default-off, so the…","i":"RegisterIntegrationEventConsumer RedeliveryIntervalsSeconds broker.fault.count MMCA.Common.Aspire BrokerMetrics internal"},{"u":"/docs/adr/087-broker-poison-message-handling.html#related","d":"ADR-087: Broker Poison-Message Handling: Second-Level Redelivery and Fault Observability","k":"Architecture Decision Records","t":"Related","x":"ADR-003 (the outbox publish leg this breaker wraps, and the retry, jittered backoff and dead-lettering that BrokenCircuitException reuses unchanged), ADR-066 (the transport…","i":"RedeliveryIntervalsSeconds BrokenCircuitException MMCA.Common.Broker MMCA.Common.Outbox MMCA.Common.Cqrs"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#status","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18). Extends ADR-019 with a fourth, edge-tier layer whose posture is the deliberate opposite of the service tier's authenticated-only global limiter; nothing in…"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#context","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Context","x":"ADR-008 made the Gateway the only client entry point and gave it three jobs: the route-to-service map, CORS, and forwarding the caller's Authorization header. Nothing was added…","i":"CorrelationIdMiddleware AddCommonRateLimiting MMCA.Common.Aspire MMCA.Common.API Authorization"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#decision","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Decision","x":"Ship a gateway edge kit in a Gateway namespace inside MMCA.Common.Aspire, owning exactly three responsibilities, and record three more as deliberately declined. 1. Correlation is…","i":"PartitionedRateLimiter.CreateChained AddGatewayDownstreamHealthChecks RateLimitPartition.GetNoLimiter GatewayCorrelationMiddleware GatewayRateLimitingSettings HttpContext.TraceIdentifier Connection.RemoteIpAddress Validator.ValidateObject ValidateDataAnnotations AddGatewayRateLimiting GlobalConcurrencyLimit UseGatewayCorrelation"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#rationale","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Rationale","x":"- The edge is the only place that sees a request exactly once. That is what makes ensure-at-the-edge correct and mint-per-service wrong: not that the service version is broken,…","i":"Ready Live"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#trade-offs","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Trade-offs","x":"- The limiter closes over an eagerly-bound copy of the settings (GatewayRateLimitingExtensions.cs:154, consumed at :164-172), so an IOptionsMonitor reload never reaches it.…","i":"BypassPathPrefixes IOptionsMonitor PermitLimit IOptions"},{"u":"/docs/adr/088-gateway-edge-responsibilities.html#related","d":"ADR-088: Gateway Edge Responsibilities (and the Three It Declines)","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (the record that made the Gateway the only entry point and gave it routing, CORS and auth forwarding; this is the first record to add cross-cutting behavior to it),…"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#status","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-18; revised 2026-08-23: ADC's table gained a 27th route, /Activities, on 2026-08-19, and the bicep anchors below are corrected). Amends ADR-008: that record…","i":"MapForwarder ReverseProxy"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#context","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Context","x":"Before this record, both gateways built their route table by hand, in code. ADC made 26 MapForwarder calls and Store made 10. Neither host called AddReverseProxy or…","i":"HttpResilienceDefaults.TotalRequestTimeout AddServiceDiscoveryDestinationResolver ForwarderRequestConfig.ActivityTimeout AddHttpForwarderWithServiceDiscovery ForwarderRequestConfig HttpVersion.Version20 RequestVersionExact appsettings.json AddReverseProxy IHttpForwarder LoadFromConfig ForwardHttp2"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#decision","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Decision","x":"Make configuration the single source of the gateway route table, and pin it with a test. Each gateway calls…","i":"HttpResilienceDefaults.TotalRequestTimeout Http2ForwardingConfigFilter RequestVersionExact appsettings.json MapReverseProxy IHttpForwarder RouteMapTests ForwardHttp2 IProxyConfig MapForwarder ReverseProxy HttpRequest"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#rationale","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Rationale","x":"- The drift already happened, in the repository that has the most gateway tests. ADC is the careful consumer, and it still carried three unpinned routes, an off-by-one comment…"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#trade-offs","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Trade-offs","x":"- The compiler stopped helping. A misspelled cluster reference, a malformed path pattern or a route that shadows another is a runtime failure, discovered as a 404 or a 502, where…","i":"appsettings.json ForwardHttp2 IProxyConfig MapForwarder Order"},{"u":"/docs/adr/089-gateway-topology-owned-by-configuration.html#related","d":"ADR-089: Gateway Topology Owned by Configuration","k":"Architecture Decision Records","t":"Related","x":"ADR-008 (amended: the Gateway keeps the route-to-service map it was given, now expressed as configuration rather than as forwarder registrations), ADR-088 (the other half of this…","i":"HttpResilienceDefaults.TotalRequestTimeout"},{"u":"/docs/adr/090-event-upcaster-registration.html","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records"},{"u":"/docs/adr/090-event-upcaster-registration.html#status","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-21). Completes the follow-up named in ADR-010: that record established the versioning policy (a SchemaVersion signal plus a new-type-and-upcaster discipline for…","i":"SchemaVersion"},{"u":"/docs/adr/090-event-upcaster-registration.html#context","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Context","x":"ADR-010 splits event evolution into a signal and a discipline. The signal (SchemaVersion, a fitness-function-gated property on every integration event) shipped with ADR-010…","i":"InProcessMessageBus SchemaVersion"},{"u":"/docs/adr/090-event-upcaster-registration.html#decision","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Decision","x":"1. A typed upcaster abstraction, in the Application layer. IEventUpcaster (Source/Core/MMCA.Common.Application/Interfaces/IEventUpcaster.cs) is a pure payload mapping from a…","i":"RegisterUpcastedIntegrationEventConsumer EventUpcastersHaveUniqueSourceTypes EventUpcastersIncreaseSchemaVersion UpcastingIntegrationEventConsumer ArchitectureRules.Upcasters.cs services.AddEventUpcaster AddUserDataExportSection EventConventionTestsBase IIntegrationEventHandler IntegrationEventConsumer DomainEventDispatcher EventUpcasterRegistry"},{"u":"/docs/adr/090-event-upcaster-registration.html#rationale","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Rationale","x":"- The discipline becomes a mechanism. ADR-010's own framing (a thing that matters is a check, not a comment) now applies to the upcaster half: the transform has a first-class…","i":"SchemaVersion MessageId"},{"u":"/docs/adr/090-event-upcaster-registration.html#trade-offs","d":"ADR-090: Event Upcaster Registration Extension Point","k":"Architecture Decision Records","t":"Trade-offs","x":"- The upcast walk advances by declared target type, not runtime type. A misbehaving upcaster that returns an instance of some other type cannot send the walk into an unvalidated…","i":"UpcastingIntegrationEventConsumer OutboxMessage.DeserializeEvent OutputCacheEvictionRequested DomainEventDispatcher AddEventUpcaster"},{"u":"/docs/adr/091-cache-backed-password-reset.html","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records"},{"u":"/docs/adr/091-cache-backed-password-reset.html#status","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-22). Extends ADR-029 (the cache-backed login-protection idiom this record reuses) and ADR-032 (which decided how a password is stored, never how a user who has…"},{"u":"/docs/adr/091-cache-backed-password-reset.html#context","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Context","x":"Both consumer apps shipped authenticated password change (PUT /Auth/password) and nothing for a user who cannot sign in at all. The recorded fallback in MMCA.ADC's specification…","i":"ResetTokenExpiresAt ResetTokenHash ResetAttempts PUT"},{"u":"/docs/adr/091-cache-backed-password-reset.html#decision","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Decision","x":"1. The reset token is a cache record, not a schema change. IPasswordResetTokenService (Source/Core/MMCA.Common.Application/Auth/IPasswordResetTokenService.cs) is two methods,…","i":"CryptographicOperations.FixedTimeEquals PasswordResetAuthControllerBase IPasswordResetTokenService FindUntrackedByEmailAsync ForgotPasswordHandlerBase ResetPasswordHandlerBase PasswordReset__ResetUrl PasswordResetController ValidateAndConsumeAsync Auth.InvalidResetToken LoginProtectionService ForgotPasswordCommand"},{"u":"/docs/adr/091-cache-backed-password-reset.html#rationale","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Rationale","x":"- No migration is the whole point. A reset credential is short-lived by nature, and the expiry semantics a reset needs (a TTL, a single use, an attempt cap) are native to a cache…","i":"Result.Success"},{"u":"/docs/adr/091-cache-backed-password-reset.html#trade-offs","d":"ADR-091: Cache-Backed Password Reset","k":"Architecture Decision Records","t":"Trade-offs","x":"- Cache eviction invalidates outstanding tokens. A Redis restart, an eviction under memory pressure, or a fall back to the in-memory store on a different replica all silently…","i":"IncrementAsync"},{"u":"/docs/adr/092-web-vitals-budget-gate.html","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records"},{"u":"/docs/adr/092-web-vitals-budget-gate.html#status","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23)."},{"u":"/docs/adr/092-web-vitals-budget-gate.html#context","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Context","x":"Rubric section 23 asks for client-side performance that is measured rather than assumed, naming Core Web Vitals (LCP, INP, CLS) or an equivalent as the evidence…"},{"u":"/docs/adr/092-web-vitals-budget-gate.html#decision","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Decision","x":"Ship the measurement infrastructure and the assert mechanics in MMCA.Common.Testing.E2E, default the budget to the Core Web Vitals good band, and let the assertions ride the…","i":"MMCA.Common.Testing.E2E WEB_VITALS_OUTPUT_DIR WebVitalsBudgetTests BeLessThanOrEqualTo PerformanceObserver AssertWithinBudget WebVitalsCollector WriteArtifactAsync durationThreshold WebVitalsArtifact WebVitalsE2ETests E2E.Tests.csproj"},{"u":"/docs/adr/092-web-vitals-budget-gate.html#rationale","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Rationale","x":"- The good band is an external contract, which is what makes an absolute ceiling defensible here. ADR-060 refused absolute latency because a nanosecond count is a property of the…"},{"u":"/docs/adr/092-web-vitals-budget-gate.html#trade-offs","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Trade-offs","x":"- The gate is ui-scoped and may legitimately skip. Both apps gate e2e-gate on a ui change filter (ADC deploy.yml:538, Store :544) and deploy accepts skipped for it (ADC :896,…","i":"WEB_VITALS_OUTPUT_DIR InteractiveServer InteractiveAuto skipped deploy"},{"u":"/docs/adr/092-web-vitals-budget-gate.html#related","d":"ADR-092: Core Web Vitals Budget as a Shipped Test Contract and Deploy Gate","k":"Architecture Decision Records","t":"Related","x":"ADR-063 (the structural sibling: the same package, the same Playwright suite and the same deploy gate, applied to WCAG 2.1 AA instead of load performance), ADR-060 (the backend…"},{"u":"/docs/adr/093-container-image-posture.html","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records"},{"u":"/docs/adr/093-container-image-posture.html#status","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23) for the three build decisions below. The two runtime postures in \"Open postures\" are recorded as undecided: they describe what the images do today and the…"},{"u":"/docs/adr/093-container-image-posture.html#context","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Context","x":"Eleven Dockerfiles produce every deployable container in the two Azure-hosted applications: six in MMCA.ADC (four services, the Gateway, the Blazor web host) and five in…","i":"publish latest build final base COPY"},{"u":"/docs/adr/093-container-image-posture.html#decision","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Decision","x":"1. The GitHub Packages credential is a BuildKit secret, never an ARG or ENV. Both applications' nuget.config source-maps MMCA. to GitHub Packages, so every restore inside an…","i":"Directory.Packages.props TreatWarningsAsErrors GITHUB_TOKEN nuget.config ENTRYPOINT history publish secrets docker dotnet build final"},{"u":"/docs/adr/093-container-image-posture.html#open-postures-undecided","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Open postures (undecided)","x":"The base image is a floating tag, not a digest. All eleven images start from mcr.microsoft.com/dotnet/aspnet:10.0 with no digest pin (.../MMCA.ADC.Conference.Service/Dockerfile:1…","i":"aspnet latest final USER app"},{"u":"/docs/adr/093-container-image-posture.html#rationale","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Rationale","x":"- A secret that is never a layer cannot leak from a layer. BuildKit secret mounts are the only mechanism that keeps the credential out of the image, the build cache and docker…","i":"history docker ARG ENV RUN"},{"u":"/docs/adr/093-container-image-posture.html#trade-offs","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Trade-offs","x":"- Eleven copies drift independently. There is no shared base Dockerfile and no test that compares them, so a fix applied to one image is applied to one image. The ReadyToRun…","i":"csproj"},{"u":"/docs/adr/093-container-image-posture.html#related","d":"ADR-093: Container Image Build and Runtime Posture","k":"Architecture Decision Records","t":"Related","x":"ADR-038 (supply-chain provenance: it gates the package graph with lock files, a vulnerability audit and an SBOM, and stops at the repository boundary, so the image layers this…"},{"u":"/docs/adr/094-client-entity-data-access.html","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/094-client-entity-data-access.html#status","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23)."},{"u":"/docs/adr/094-client-entity-data-access.html#context","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Context","x":"ADR-034 decided the server half of entity data access: a generic controller base with a dynamic query contract, where filters arrive as filters[Property].operator /…","i":"QueryFilterModelBinder MMCA.Common.UI operator Property filters value"},{"u":"/docs/adr/094-client-entity-data-access.html#decision","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Decision","x":"Client-side entity data access goes through one hand-written base hierarchy in MMCA.Common.UI. - One HTTP root: AuthenticatedServiceBase…","i":"DomainInvariantViolationException IdempotencyHeaders.IdempotencyKey EntityServiceBase.GetPagedAsync CreateAuthenticatedClientAsync ResetCancellationTokenAsync ListPageQueryStateService AuthenticatedServiceBase CultureDelegatingHandler Directory.Packages.props PersistentComponentState EnsureSuccessStatusCode ObjectDisposedException"},{"u":"/docs/adr/094-client-entity-data-access.html#rationale","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Rationale","x":"- A hand-written typed base beats a generated client here because the surface is already generic. ADR-034 collapsed N entity endpoints into one shape, so there is exactly one…","i":"EnsureSuccessStatusCode"},{"u":"/docs/adr/094-client-entity-data-access.html#trade-offs","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- No generated client means drift is caught at runtime, not at build time. A server-side rename of a query parameter or a DTO property does not fail the UI build; it fails the…","i":"ChildEntityServiceBase.PostAsync ConfigureHttpClientDefaults EntityServiceBaseTests.cs AuthenticatedServiceBase ChildEntityServiceBase AddServiceDefaults CartStateService RetryPolicy protected AddAsync readonly static"},{"u":"/docs/adr/094-client-entity-data-access.html#related","d":"ADR-094: Client-Side Entity Data-Access Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-034 (the server surface this contract calls, and the filter grammar the client constructs), ADR-017 (the server-side filter whose client half is specified here: who mints the…","i":"DataGridListPageBase TDto"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#status","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23)."},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#context","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Context","x":"ADR-005 makes deletion soft: an IAuditableEntity sets IsDeleted = true (MMCA.Common/Source/Core/MMCA.Common.Domain/Interfaces/IAuditableEntity.cs:11) and a named global query…","i":"IAuditableEntity HasFilter IsDeleted true"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#decision","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Decision","x":"Make the filter a convention: every unique index on a soft-deletable entity excludes deleted rows, automatically, in every context of every consumer. - A model-finalizing…","i":"ApplicationDbContext.ConfigureConventions SoftDeleteUniqueIndexConvention SoftDeleteFilterSql.Build DataSource.CosmosDB HasSoftDeleteFilter additionalFilter IAuditableEntity HasColumnName filter null AND"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#rationale","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Rationale","x":"- The database should agree with what the application shows. The query filter already says a soft-deleted row does not exist; a unique index that disagrees is the one place the…","i":"DedupKey Build NULL NOT"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#trade-offs","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Trade-offs","x":"- It moves schema in consumers, invisibly from the entity configuration. Adopting the convention is a database-contract change: nothing in an entity configuration changed, but…","i":"IX_CategoryItem_CategoryId_Name ignoreQueryFilters builder.HasIndex index.GetFilter IX_User_Email IsDeleted IsUnique x.Email false true"},{"u":"/docs/adr/095-soft-delete-unique-indexes.html#related","d":"ADR-095: Uniqueness Under Soft Delete (Filtered Unique Indexes)","k":"Architecture Decision Records","t":"Related","x":"ADR-005 (decides soft-delete over erasure and owns the query filter that hides the row, but says nothing about uniqueness: this ADR closes that gap), ADR-057 (the expand/contract…","i":"ApplicationDbContext"},{"u":"/docs/adr/096-best-effort-side-effects.html","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records"},{"u":"/docs/adr/096-best-effort-side-effects.html#status","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Status","x":"Accepted (2026-08-23)."},{"u":"/docs/adr/096-best-effort-side-effects.html#context","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Context","x":"A command that has already committed often has follow-up work attached to it: evict the output-cache entries the write invalidated, broadcast the new state to a live channel,…","i":"MarkAsFailed Exception catch"},{"u":"/docs/adr/096-best-effort-side-effects.html#decision","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Decision","x":"One framework helper defines the contract, and a swallow that does not go through it is a deliberate, documented exception. - BestEffort.ExecuteAsync(operation, logger, action,…","i":"BestEffortLog.DispatchFailed besteffort.dispatch.failed OperationCanceledException OutputCacheEvictionHandler BestEffort.ExecuteAsync MMCA.Common.OutputCache CancellationToken.None MMCA.Common.BestEffort ArgumentNullException cache.eviction.failed ProductVariantChanged TryEvictByTagAsync"},{"u":"/docs/adr/096-best-effort-side-effects.html#rationale","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Rationale","x":"- One policy beats five local leniencies. Each feature record is still right about its own degradation; what they could not each decide is the shape of the swallow. A single…","i":"AddVariantHandler"},{"u":"/docs/adr/096-best-effort-side-effects.html#trade-offs","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Trade-offs","x":"- Nothing gates use of the helper. There is no fitness rule, analyzer or architecture test that fails a build for a hand-rolled catch (Exception) that should have been a…","i":"besteffort.dispatch.failed cache.eviction.failed SubmitQuestionHandler MMCA.Common.Aspire BestEffort Exception catch"},{"u":"/docs/adr/096-best-effort-side-effects.html#related","d":"ADR-096: Best-Effort Side-Effect Contract","k":"Architecture Decision Records","t":"Related","x":"ADR-024 (push delivery failure is non-fatal and recorded rather than raised, one of the local leniencies this policy generalizes), ADR-026 (eviction is best-effort, and its…","i":"besteffort.dispatch.failed OutputCacheEvictionHandler"},{"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 UserSessionBookmarkCacheEvictionHandlerTests AddEventQuestionAnswerCommandValidatorTests AddSessionCategoryItemCommandValidatorTests"},{"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,465 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: 1685 production types across 26 groups + 1780 test/testing types in G25 = 3465 (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 14 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: 2810…","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 FaultIntegrationEventConsumer GatewayCorrelationMiddleware JwtSettings.SigningAlgorithm EntityTypeConfigurationBase UseCommonMiddlewarePipeline TenantResolutionMiddleware ExportUserDataHandlerBase ISoftDeletedUserValidator ServiceInfoControllerBase"},{"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 ArgumentNullException.ThrowIfNull DomainInvariantViolationException MMCA.Common.Shared.Serialization MMCA.Common.Shared.Abstractions System.Text.Json.Utf8JsonReader GrpcResultExceptionInterceptor System.Text.Json.Serialization MMCA.Common.Shared.Exceptions System.Buffers.Text.Base64Url Base64Url.TryDecodeFromChars ValidationFailureExtensions"},{"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":"QuerySpecification 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":"PublicSessionStatusSpecification.StatusCriteria GetPublicSessionFilterHandler PublishedEventSpecification CrossSourceSpecification OwnedByUserSpecification SpecificationExtensions SpecificationComposer dependent.ForeignKey InvocationExpression Enumerable.Contains InlineSpecification s.Event.IsPublished"},{"u":"/docs/onboarding/group-03-querying-specifications.html#queryspecification-a-whole-read-in-one-object","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"QuerySpecification, a whole read in one object","x":"A plain specification is only a predicate, which leaves includes, ordering, paging, and tracking to be threaded through every layer as loose arguments. QuerySpecification…","i":"IgnoreQueryFilters QuerySpecification EFReadRepository LambdaExpression OrderExpression TIdentifierType WithSoftDeleted specification Specification BaseQueryFor IncludePaths WithTracking"},{"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:155)…","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-entity-paths-plus-projection-pushdown","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"The pipeline: two entity paths plus projection pushdown","x":"IEntityQueryPipeline (MMCA.Common/Source/Core/MMCA.Common.Application/Services/Query/IEntityQueryPipeline.cs:10) is the execution contract, implemented by the sealed…","i":"ApplyIncludesCriteriaAndFilters inavigationmetadataprovider NavigationMetadataProvider MaxUnboundedResultLimit NavigationPropertyInfo CountUnpaginatedAsync EntityQueryParameters ExecuteProjectedAsync IEntityQueryPipeline INavigationPopulator entityquerypipeline IEntityDTOProjector"},{"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":"SpeakerEntityQueryService BuildPaginationMetadata MaxUnboundedResultLimit TryGetByIdFastPathAsync DTOToEntityPropertyMap TryGetFastPathIncludes EntityQueryParameters ExecuteProjectedAsync 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":"IEntityQueryService.GetAllAsync EntityQueryParameters PagedCollectionResult EntityControllerBase IApplicationSettings EntityQueryPipeline TIdentifierType MaxPageSize PagingMath TEntityDTO requested TEntity"},{"u":"/docs/onboarding/group-03-querying-specifications.html#also-filed-here-the-best-effort-side-effect-helper","d":"3. Querying: Specifications, Filtering & the Entity Query Service","k":"Onboarding Guide","t":"Also filed here: the best-effort side-effect helper","x":"Three types in this group are not part of the read path at all; they are co-located in MMCA.Common.Application/Services and are grouped by that folder. BestEffort…","i":"Filtering.DynamicQueryConfig.Parameterized IEntityQueryPipeline.ExecuteProjectedAsync MMCA.Common.Application.Services.Filtering SpecificationsDoNotNavigateToOtherEntities ArgumentException.ThrowIfNullOrWhiteSpace QueryFilterService.ResolveFilterValueType Microsoft.Extensions.DependencyInjection NavigationMetadataProvider.BuildIncludes CrossSourceSpecification.BuildCriteria MMCA.Common.Application.Services.Query MMCA.Common.Application.Specifications QueryFieldService.ApplyFieldSelection"},{"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 Microsoft.Extensions.Hosting.IHostedService OutboxProcessor.ProcessPendingMessagesAsync UserSessionBookmarkCacheEvictionHandler services.AddOutputCacheEvictionHandler Microsoft.Extensions.Logging.ILogger MMCA.Common.Application.DomainEvents MMCA.Common.Domain.IntegrationEvents ApplicationDbContext.ConfigureInbox domainEventDispatcher.DispatchAsync MMCA.Common.Infrastructure.Services"},{"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":"AuthorizationCommandDecorator TransactionalCommandDecorator AuthorizationQueryDecorator FeatureGateCommandDecorator ValidatingCommandDecorator FeatureGateQueryDecorator ProfilingCommandDecorator CachingCommandDecorator LoggingCommandDecorator ProfilingQueryDecorator TimeoutCommandDecorator CachingQueryDecorator"},{"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":"DecoratorPipelineOrderTestsBase ScanModuleApplicationServices ProfilingCommandDecorator AddApplicationDecorators DependencyInjectionTests AddApplicationProfiling ProfilingQueryDecorator DependencyInjection.cs EntityQueryPipeline IServiceCollection ServiceCollection ICommandHandler"},{"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:76-98): - Feature-gating is outermost so a…","i":"TransactionCommitAmbiguousException ICacheService.RemoveByPrefixAsync Authorization.PermissionDenied IFeatureManager.IsEnabledAsync AuthorizationCommandDecorator TransactionalCommandDecorator AuthorizationQueryDecorator FeatureGateCommandDecorator OperationCanceledException ValidatingCommandDecorator CqrsMetrics.QueryDuration ExecuteInTransactionAsync"},{"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 GetProductByIdQuery IRequiresPermission GetTicketByIdQuery ICacheInvalidating FeatureManagement GetOrderByIdQuery GetNowNextQuery IQueryCacheable ITransactional CacheDuration IFeatureGated"},{"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 IsResolved tenantId TenantId TResult"},{"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":"cqrs.authorization.denied.count AuditableAggregateRootEntity TypeInitializationException InvalidOperationException RecordAuthorizationDenied DeleteSessionCommand DeleteSpeakerCommand ResultFailureFactory DeleteEntityCommand DeleteEntityHandler cqrs.timeout.count ICacheInvalidating"},{"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":"Five 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 IEntityDTOProjector EntityQueryService ScheduledJobRunner cancellationToken IAuditTrailReader AuditTrailReader IAsyncDisposable"},{"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 Microsoft.Extensions.DependencyInjection CqrsMetrics.RecordAuthorizationDenied QueryCacheKeyLocks.Locks.AcquireAsync MMCA.Common.Application.Extensions MMCA.Common.Application.Interfaces ICacheService.RemoveByPrefixAsync correlationContext.CorrelationId MMCA.Common.Application.UseCases System.Diagnostics.Metrics.Meter ConferenceCategoryCreateRequest"},{"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 PushNotificationDTOProjection.ProjectToDTO PushNotificationProjectionTranslationTests MMCA.Common.API.Controllers.Notifications NotificationHub.ReceiveNotificationMethod PushNotificationInvariants.TitleMaxLength Microsoft.Extensions.DependencyInjection PushNotificationInvariants.BodyMaxLength"},{"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 System.Threading.RateLimiting.RateLimitLease IDbContextFactory.HasPendingMigrationsAsync AuthorizationPolicies.RequireAuthenticated Microsoft.AspNetCore.Authentication.Google StackExchange.Redis.IConnectionMultiplexer ArgumentException.ThrowIfNullOrWhiteSpace System.Threading.RateLimiting.RateLimiter"},{"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.Gateway 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-resilience-numbers","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"One source of truth for resilience numbers","x":"The numbers that pipeline uses are not literals in the Aspire package. They live in HttpResilienceDefaults (Level 0,…","i":"MMCA.Common.Infrastructure MMCA.Common.Grpc Continuity properties Resilience RetryCount including Business Concerns lifetime sampling attempt"},{"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:336) 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 MMCA.Common.OutputCache ConfigureOpenTelemetry MMCA.Common.BestEffort TryGetTraceSampleRatio MMCA.Common.Scheduler"},{"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 next 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#the-gateway-edge-kit-correlation-rate-limiting-downstream-readiness","d":"16. Aspire Orchestration & Service Defaults","k":"Onboarding Guide","t":"The gateway edge kit: correlation, rate limiting, downstream readiness","x":"A YARP gateway is the one process every client request passes through, and it is also the one host that has no application container: no DbContext, no module loader, no…","i":"AddServiceDiscoveryDestinationResolver AddGatewayDownstreamHealthChecks Connection.RemoteIpAddress MMCA.Common.Aspire.Gateway Validator.ValidateObject HttpResponse.OnStarting ValidateDataAnnotations AddGatewayRateLimiting GlobalConcurrencyLimit UseGatewayRateLimiting ConfigureClusterAsync RequestVersionOrLower"},{"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 E2E helpers, and the…","i":"Azure.Extensions.AspNetCore.Configuration.Secrets Azure.Extensions.AspNetCore.DataProtection.Blobs identityService.WithE2eRegistrationThrottleLift AddGatewayDownstreamHealthChecks_IsIdempotent LoginProtection__MaxRegistrationsPerIpPerHour ConnectionStrings__SQLServerConnectionString database.Resource.ConnectionStringExpression Readiness_IncludesADownstreamCheckPerService ResilienceCircuitBreakerFaultInjectionTests WarmupReadinessHealthCheck.CheckHealthAsync HttpKeepAlivePingPolicy.WithActiveRequests cancellationToken.IsCancellationRequested"},{"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 Microsoft.Extensions.DependencyInjection MMCA.ADC.Migrations.SqlServer.Conference ApplyConfigurationsForEntitiesInContext SessionInvariants.AnswerValueMaxLength SpeakerInvariants.AnswerValueMaxLength QuestionInvariants.ManualIdRangeStart SpeakerQuestionAnswerConfiguration.cs EventInvariants.AnswerValueMaxLength"},{"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 UserSessionBookmarkCacheEvictionHandlerTests assemblyProvider.GetConfigurationAssemblies AuthorizationPolicies.RequireAuthenticated CheckInsController.GetAttendanceStatsAsync"},{"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 chapter covers. One Blazor component library in MMCA.Common.UI renders on three very different heads: Blazor Server (server-side prerender plus interactive Server…","i":"MauiBackNavigationBridge.HandleBackPressedAsync MMCA.Common.UI.Services.Capabilities.Fallbacks MMCA.Common.UI.Services.Capabilities.Browser builder.Services.AddMauiDeviceCapabilities MauiLocalNotificationService.ScheduleAsync WebAuthenticator.Default.AuthenticateAsync ArgumentException.ThrowIfNullOrWhiteSpace Battery.Default.EnergySaverStatusChanged CommunityToolkit.Maui.Media.SpeechToText Connectivity.Current.ConnectivityChanged CultureInfo.DefaultThreadCurrentCulture ILocalNotificationService.ScheduleAsync"},{"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":"CancellationTokenConventionTestsBase ConstructorDependencyCountTestsBase IntegrationEventContractTestsBase MMCA.Common.Testing.Architecture ObservabilityConventionTestsBase AggregateRootsHaveResultFactory MicroserviceExtractionTestsBase RawQueryableConventionTestsBase IdempotencyConventionTestsBase ArchitectureRules.Entities.cs AggregateConventionTestsBase CrossEntityNavigationFinder"},{"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 MMCA.Common.Architecture.Tests.CycleFixtures.Left Module_ShouldDeclare_ExpectedRequiresDependencies PiiRedactor_LeaksNoClearTextPii_ToLogsOrTelemetry CultureSwitch_ToSpanish_ShouldLocalizeAndPersist EveryRunbookAlertSection_MapsToAProvisionedAlert MobileViewport_CultureAndTheme_ShouldBeReachable ModuleShared_ShouldNotDependOn_OwnInternalLayers OpenApiDocument_DescribesEveryCorePublicResource Register_WithMismatchedPasswords_ShouldShowError"},{"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 UserSessionBookmarkCacheEvictionHandlerTests PushNotificationProjectionTranslationTests SpecificationsDoNotNavigateToOtherEntities CachingDecoratorConstructorSelectionTests CurrentUserTargetingContextAccessorTests Microsoft.Extensions.DependencyInjection MMCA.ADC.Conference.Infrastructure.Tests MMCA.ADC.Engagement.Infrastructure.Tests MMCA.ADC.Notification.Application.Tests EntityServiceBaseIdempotencyRetryTests MMCA.ADC.CrossService.IntegrationTests"},{"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,890 individually-sectioned types are missing from their group chapter, every one appears as a heading or in a sibling-family…","i":"SpecificationsDoNotNavigateToOtherEntities UserSessionBookmarkCacheEvictionHandler MMCA.Common.Infrastructure.Redis.Tests DatabaseInitializationExtensionsTests HttpContextExternalLoginEmailVerifier MMCA.ADC.Conference.Application.Tests MMCA.ADC.Engagement.Application.Tests ObservabilityConventionTestsBaseTests OwnSessionQuestionAnswerSpecification AnonymousAuthenticationStateProvider AzureNotificationHubNativePushSender CancellationTokenConventionTestsBase"},{"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,465 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 Event.OrganizerContactEmail PseudoLocalizationTests"},{"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…","i":"OrganizerContactEmail"},{"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-seventh-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 Event.OrganizerContactEmail"},{"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 PublicSessionList.razor.cs needs.changes.outputs.ui PrimitivesSnapshotTests MMCA.Common.Testing.UI OrganizerContactEmail TreatWarningsAsErrors DeviceSettings.razor EventDetail.razor.cs"},{"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 OrganizerContactEmail BrandColorTokenTests DeviceSettings.razor skip_freshness_gates"},{"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 SoftDeleteUniqueIndexConvention EventVersioningConventionTests ListPageQueryStateServiceTests PermissionAuthorizationHandler"},{"u":"/docs/governance/common-ArchitectureScorecard.html#indices","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Indices","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 316 ÷ 324 = 97.5% (up from 96.9% (314/324) on the targeted 2026-08-22 update: §9 API & Contract Design Maturity 3→4 on…","i":"ServiceContract"},{"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 ServiceContractPurityTestsBase OutboxPollFilterProcessor NavigationContractTests required_status_checks PiiConventionTests CONTRIBUTING.md ServiceContract DEPLOYMENT.md IAnonymizable PiiRedactor COST.md"},{"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 97.5% / Implementation 84.8%, framework v1.160.0. Twenty-eighth-wave full re-score, 2026-08-23 (git…","i":"ServiceContractPurityTestsBase ArchitectureScorecard.md required_status_checks RedisDistributedLock IDistributedLock BenchmarkDotNet IsDirtyAccessor ServiceContract Performance baseline c911480 d12cc4d"},{"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#progress---security-invariants-wave-11-hardening-2026-08-22","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - security invariants wave (§11 hardening, 2026-08-22)","x":"Closes the two §11 gaps surfaced by the Article 16 (JWKS dual-fetch) review: the insecure dev defaults that no two-axis entry named as scheduled work, and the absent security…","i":"AnonymousEndpointTestsBase AddForwardedJwtBearer requireHttpsMetadata RequireHttpsMetadata RsaJwksProvider AllowAnonymous configuration environment authority audience string false"},{"u":"/docs/governance/common-RemediationBacklog.html#progress---9-contract-surface-gates-2026-08-22","d":"MMCA.Common: Architecture Remediation Backlog","k":"Architecture Governance","t":"Progress - §9 contract-surface gates (2026-08-22)","x":"Closes both halves of 9, the last weight-2 Maturity-3 item that had a named in-repo lever. Landed via MMCA.Common PR 271 (squash 8a6c603, merged 2026-08-22). - ✅ OpenAPI…","i":"ServiceContractsDoNotDependOnServiceInternals OpenApiBaselineTests AddCommonOpenApi MapCommonOpenApi ServiceContract ProblemDetails FACTS.md"},{"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 IsDeleted IsFailure preview TResult CS1591"},{"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":"ServiceContractsDoNotDependOnServiceInternals EntityQueryPipeline.MaxUnboundedResultLimit ApplicationSettings.MaxPageSize MessageBusSettings.EnableInbox ServiceContractPurityTestsBase ArchitectureRules.Slices.cs MobileInfiniteScrollList OpenApiContractTestsBase ServiceContractAttribute Directory.Build.targets AddCommonApiVersioning required_status_checks"},{"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 ServiceContract EditorRequired Performance baseline navbar"},{"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 MMCA.Store.Identity.UI.Tests ObservabilityConventionTests MobileInfiniteScrollList PseudoLocalizationTests GracefulShutdownTests Money.ToDisplayString RemediationBacklog.md"},{"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-23 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 workflow_dispatch 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 and…","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":"needs.changes.outputs.ui github.event_name matrix.browser workflow_call deploy.needs deploy.yml browsers d057afc e2e.yml skipped success always"},{"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 IdentityModuleDbSeederBase.ShouldSeed SqlServerIntegrationTestFixtureBase CultureInfo.InvariantCulture MobileInfiniteScrollList ProductVariantChanged NotifyStateChanged workflow_dispatch EmailExistsAsync CatalogBrowse GetPagedAsync InventoryItem"},{"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 workflow_dispatch OrderLinesPanel OPERATIONS.md"},{"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, the two password-reset pages, 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. UC-34 (Request password reset) and UC-35 (Reset password) in the…","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 PasswordReset__ResetUrl PasswordResetController Auth.InvalidResetToken MaxValidationAttempts RequestWindowMinutes Speaker.LinkedUserId TokenLifetimeMinutes User.LinkedSpeakerId MaxRequestsPerEmail UserPasswordChanged"},{"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, the two password-reset pages, and the public catalog. Add-to-cart on the product detail page sits inside an…","i":"AuthorizeView Authorize"},{"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 Auth.InvalidResetToken IProductVariantService"},{"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":"ForgotPasswordRequestValidator ProductVariantConfiguration.cs ResetPasswordRequestValidator InventoryItemInvariants.cs AdjustInventoryHandler.cs PasswordResetTokenService ShoppingCartInvariants.cs CategoryConfiguration.cs CheckOutDomainService.cs CustomerConfiguration.cs ForgotPasswordHandler.cs UserRegisteredHandler.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":"OrderPaymentFailedSagaHandler payment_intent.payment_failed EventUtility.ConstructEvent checkout.session.completed checkout.session.expired OrderPaidHandler Result.Failure StripeSettings WebhookSecret IEmailSender 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 POST 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: Proof & getting started · No. 50 The…"},{"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":"96 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/the-mmca-series-every-pattern-one-place-28cf2cee7be8","t":"The series index","d":"Article no. 50","k":"Proof & getting started","x":"The full series index and recommended reading order.","e":1},{"u":"https://medium.com/@ivanball76/undo-is-a-feature-saga-compensation-and-the-reconciliation-backstop-fa017f9591b8","t":"Saga compensation and the reconciliation backstop","d":"Article no. 49","k":"Core patterns","x":"Undo as a first-class event handler: give back stock a committed transaction already took, with a periodic sweep as the saga-timeout backstop.","e":1},{"u":"https://medium.com/@ivanball76/observability-by-default-opentelemetry-and-azure-monitor-in-mmca-673c1886e9e0","t":"Observability by default","d":"Article no. 48","k":"Run & extract","x":"A shared OpenTelemetry baseline with CQRS duration metrics, correlation IDs, and outbox-poll span filtering, exported to Azure Monitor.","e":1},{"u":"https://medium.com/@ivanball76/security-headers-and-csp-for-blazor-one-middleware-every-host-af82df95236e","t":"Security headers and CSP for Blazor","d":"Article no. 47","k":"Auth & the edge","x":"One middleware stamps hardened response headers on every host, with the Blazor CSP resolved through a pluggable provider.","e":1},{"u":"https://medium.com/@ivanball76/field-level-encryption-in-ef-core-aes-gcm-for-pii-columns-06ece340ea25","t":"Field-level encryption in EF Core","d":"Article no. 46","k":"Data & persistence","x":"An AES-256-GCM value converter that keeps a PII column ciphertext even for someone who can query the database.","e":1},{"u":"https://medium.com/@ivanball76/feature-flags-in-the-cqrs-pipeline-gate-commands-not-code-e58b9ea8d098","t":"Feature flags in the CQRS pipeline","d":"Article no. 45","k":"Core patterns","x":"Gate commands and queries at the outermost decorator, so a handler never checks a flag and a disabled feature is rejected before any work runs.","e":1},{"u":"https://medium.com/@ivanball76/http-api-versioning-proven-not-just-claimed-2b0381e4b533","t":"HTTP API versioning, proven not just claimed","d":"Article no. 44","k":"Auth & the edge","x":"Header-based versioning introduced without breaking a single caller, plus a fitness contract that proves two live versions coexist.","e":1},{"u":"https://medium.com/@ivanball76/managed-file-storage-uploads-you-dont-have-to-trust-8dfe8bf016bc","t":"Managed file storage: uploads you don't have to trust","d":"Article no. 43","k":"Data & persistence","x":"Attacker-controlled bytes become safe avatars: content sniffing, metadata stripping, re-encoding, and pluggable blob storage.","e":1},{"u":"https://medium.com/@ivanball76/one-blazor-ui-two-hosts-a-device-capability-layer-that-stays-resolvable-everywhere-b85444693161","t":"One Blazor UI, two hosts","d":"Article no. 42","k":"Proof & getting started","x":"The same Blazor components run in a browser and inside a MAUI hybrid app; small per-capability contracts reach native hardware without ever asking 'am I on mobile?'.","e":1},{"u":"https://medium.com/@ivanball76/two-real-apps-on-one-framework-a-conference-platform-and-a-store-12f694d2a361","t":"Two real apps on one framework","d":"Article no. 41","k":"Proof & getting started","x":"A case study: a conference platform and an e-commerce store built on the same kernel.","e":1},{"u":"https://medium.com/@ivanball76/write-your-first-architecture-fitness-test-d4e25e6a4741","t":"Write your first fitness test","d":"Article no. 40","k":"Proof & getting started","x":"Author your first architecture fitness test and watch it fail the build on a violation.","e":1},{"u":"https://medium.com/@ivanball76/scaffold-a-net-modular-monolith-in-one-command-then-build-your-first-module-b10aacd16d33","t":"Build your first module","d":"Article no. 39","k":"Proof & getting started","x":"A hands-on walkthrough of building a new module across all five layers.","e":1},{"u":"https://medium.com/@ivanball76/one-preference-two-switches-shipping-i18n-and-dark-mode-on-a-single-cookie-and-profile-pipeline-f97186038909","t":"i18n and theming on one preference pipeline","d":"Article no. 38","k":"Proof & getting started","x":"A culture choice and a theme choice ride the same cookie, profile column, and login reconciliation: one persistence path, two switches.","e":1},{"u":"https://medium.com/@ivanball76/a-list-page-in-a-few-lines-a-reusable-blazor-ui-framework-with-the-same-discipline-as-the-backend-c66fa16cd561","t":"A reusable Blazor UI framework","d":"Article no. 37","k":"Proof & getting started","x":"A shared Blazor and MudBlazor UI layer with accessibility enforced by axe in CI.","e":1},{"u":"https://medium.com/@ivanball76/soft-delete-vs-the-right-to-erasure-the-gdpr-conflict-and-the-erasure-pathway-e1d350007509","t":"Soft-delete vs the right to erasure","d":"Article no. 36","k":"Proof & getting started","x":"Soft-delete for lifecycle, anonymization plus outbox purge for GDPR/CCPA erasure, and why both exist.","e":1},{"u":"https://medium.com/@ivanball76/the-test-pyramid-not-the-ice-cream-cone-1-880-fast-tests-zero-docker-cb459fda73d9","t":"The test pyramid","d":"Article no. 35","k":"Proof & getting started","x":"How the framework's tests stack up: fast unit and architecture tests at the base, E2E at the tip.","e":1},{"u":"https://medium.com/@ivanball76/architecture-fitness-functions-rules-that-fail-the-build-not-a-wiki-page-6562940deceb","t":"Architecture fitness functions","d":"Article no. 34","k":"Proof & getting started","x":"Architecture rules that fail the build: a compile-time layer guard plus a shared NetArchTest rule library.","e":1},{"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/governance/store-ArchitectureScorecard.md b/docs-src/governance/store-ArchitectureScorecard.md index 50e36c2..35234b4 100644 --- a/docs-src/governance/store-ArchitectureScorecard.md +++ b/docs-src/governance/store-ArchitectureScorecard.md @@ -2,11 +2,11 @@ > **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** (previously its posture lived only in the workspace docs + memory). Scored against the rubric at [`ArchitectureEvaluationCriteria.md`](ArchitectureEvaluationCriteria.md); framework-wide facts in [`../MMCA.Common/FACTS.md`](https://github.com/ivanball/MMCA.Common/blob/main/FACTS.md). Remediation lives in [`RemediationBacklog.md`](../governance/store-RemediationBacklog.md); the cross-repo comparison in the workspace-internal `Docs/Architecture/CrossRepoComparison.md` (not published). -_Rubric: [`ArchitectureEvaluationCriteria.md`](ArchitectureEvaluationCriteria.md) • Date: 2026-08-14 • Two axes per category: **Maturity** (0-4, process/governance) and **Implementation** (0-10, substance/execution). Indices computed deterministically from the scores below. Verified against current source at HEAD `9571a963` (clean tree); framework dependency pinned at **MMCA.Common.* v1.152.0** (all 15 packages, lockstep, confirmed no divergence; ADRs are canonical in `Website/docs-src/adr/`, count/range owned by its `README.md`). **What moved this cycle (2026-08-14 full re-score, 34 categories, two-pass with adversarial verification, pin v1.152.0): no score moves.** All 34 categories were re-scored from evidence read at HEAD `9571a963`; 28 came back CONFIRMED at their prior values and six (§5, §15, §17, §19, §20, §21) came back FLAG, every one an adversarial rejection of a proposed first-pass uplift rather than a found regression: §5 held 8 (horizontal technical folders persist inside module Application layers; generic-CRUD slices dispatch to shared framework handlers), §17 held 9 (no Bicep validate/what-if before the prod run; SQL `publicNetworkAccess: Enabled`, the identical caveat holding ADC at 9; single prod-only environment), §19 held 8 for the second consecutive cycle (`IsDrawerOpen` is still publicly settable and mutated outside the notify path, `CartDrawer.razor:4`, `CartDrawer.razor.cs:145`), §20 held 7 (the ProductList remediation converted 3 attributes while 31 identical `Style=`/`CellStyle=` occurrences remain across 14 razor files, five byte-identical to the new classes), and §21 held 3/8 (the screen-reader results log still holds only the placeholder row; the one delta since the prior pin is a single added dark-palette home axe scan, now 23 scans total). §15's verify pass proposed a correction to Implementation 7 on three suppression-hygiene gaps (the expired GHSA-2m69-gcr7-jv3q audit suppression at `Directory.Build.props:54` whose own removal condition is met under the v1.152.0 pin, three undocumented global `NoWarn` codes at `:26`, and the MAUI head outside all CI enforcement); the user adjudicated a hold at the prior 8 with the three gaps recorded as §15's named backlog lever. Indices unchanged: Maturity 97.8%, Implementation 83.9%. Anchor refreshes only (no substance change): §16's stale narrated pin corrected to v1.152.0, §20/§21/§22 evidence re-anchored, and §22's nightly cadence note updated (since 2026-07-29 the scheduled matrix runs one alternating engine per week, widening the per-engine blind window to 7 days). **Prior cycle, retained (2026-07-28 full re-score, 34 categories, two-pass with adversarial verification, pin v1.131.0):** three scores moved. §8 Data Architecture I8→9 on substance that landed after the prior cycle: the atomic conditional-UPDATE stock decrement (`SET qty = qty - n WHERE qty >= n`) with deterministic variant-id lock ordering closes the oversell read-modify-write race (`InventoryAllocationService.cs:70`), backed by a `CK_InventoryItem_AvailableQuantity_NonNegative` schema CHECK constraint (`InventoryItemConfiguration.cs:27`), an explicit single-transaction checkout write phase with the cross-service gRPC price fetch deliberately outside the lock window (`CheckOutHandler.cs:91`), and a fail-closed expand/contract destructive-migration guard in the required `build-and-test` job (`deploy.yml:190`); held at 9, not 10, because Identity has no concurrency round-trip test. §22 Responsive M4→3, the reopen the 2026-07-23 drift note predicted: the deploy-gating `e2e-gate` passes `browsers: '["chromium"]'` only (`deploy.yml:494`) and firefox/webkit run solely on the Mon/Thu schedule where they stay `continue-on-error` (`e2e.yml:124,131`), with no cross-browser freshness job in `deploy.needs`, so cross-engine verification is convention-enforced (Consistent=3), not automatic; the proposed Implementation 8→7 was adversarially REJECTED as a CI-cadence change mis-posted to the substance axis, matching ADC's M3/I8 on the identical mechanism. §27 i18n I9→7→**8**, a corrected over-grant rather than a regression (no i18n file changed since 2026-07-17): every price renders through `Money.ToDisplayString()`, which hard-codes a `$` glyph and formats with `CultureInfo.InvariantCulture` (`MMCA.Common .../MoneyExtensions.cs:20,41`, consumed at `CatalogBrowse.razor.cs:302`), the rubric's explicit "manual number formatting ignoring culture" red flag, and pluralization is the `"{0} item(s)"` workaround rather than the i18n mechanism (`CartDrawer.resx:20`); the scorer proposed 7 and the user adjudicated 8, the conservative half of the band the verifier called defensible, since the gates and coverage behind the original grant are all intact. Three further first-pass proposals were adversarially REJECTED and held at prior: §12 M3→4 and I8→9 (no new merge-path perf gate exists; `load-test.yml:17-18` is still monthly cron plus dispatch, and the `load-freshness` gate actually GAINED a break-glass skip at `deploy.yml:577-592`, a weakening), §19 I8→9 (no new state-management substance since the prior pin; `IsDrawerOpen` is still publicly settable outside the notify path), and §30 M4/I8→M3/I7 (every cited mechanism re-read live at HEAD, no gap found). Indices Maturity 98.4%→97.8%, Implementation 83.6%→83.9%. **Earlier cycles, retained below, oldest first (2026-07-03 drift-plan execution, D1/D4/D5/D8/D9/D10):** §21 Accessibility M3→4 and §28 Front-End Testing M3→4 (the Playwright + axe suite now **gates the deploy**: `e2e-gate` joined `deploy.yml`'s `needs` after two consecutive fully green E2E runs, 28682334766 chromium 83/83 with firefox + webkit also green, confirmed by 28683063228), §12 Performance I7→8 (client Web Vitals are now measured in CI: `WebVitalsTests` writes LCP/CLS/TTFB/FCP artifacts per run), §23 Front-End Performance I6→8 (the public `CatalogBrowse` moved to server-side paging via `GetPagedAsync` + bounded `MobileInfiniteScrollList`, and cart enrichment now uses a targeted by-variant-id batch lookup instead of fetching the whole product list), and §32 Supply-Chain I7→8 (all three CI restores run `--locked-mode` and the suppress-aware vulnerability audit is now gating, D8/D9). The prior cycle's moves (2026-07-02 docs sweep: §16/§25/§20 M3→4, §27 scored M4/I7, §14 I6→9, §34 I7→9) are retained in the rows below. **A same-day i18n completion sweep (2026-07-03, ADR-027 Decision 9) then lifted §27 Implementation 7→8** (zero residual literals incl. the cart/checkout/Stripe snackbars, dual CI gates, MudBlazor chrome + nav localized; indices Implementation 80.3%→80.4%). **A subsequent 2026-07-11 drift-convergence cycle (drift plan D1-D13, pin v1.113.0) moved six scores:** §1 SOLID Implementation 8→9 (the ctor-dependency-ceiling gate `ConstructorDependencyCountTests` + `TimeProvider` injection, D9), §9 API Implementation 8→9 (the v2 `ServiceInfoController` + two deploy-gating Contract tests, D12), §24 Forms Maturity 3→4 (the CI-gated `FormsConventionTests`, D11), §28 Front-End Testing Implementation 6→8 (bUnit breadth grown to 214 facts across 40 files, D7), §29 Resilience Implementation 8→9 (the `dr-freshness` deploy gate + weekly `dr-drill` cron + `GracefulShutdownTests`, D3), and §21 Accessibility Maturity 4→3 with Implementation 7→8 (honest reconciliation to ADC's M3: 22 axe scans + the new screen-reader runbook, but no dated SR pass yet, D6). D2 (MI-SQL activation wiring) and D4 (cost-guard deploy gate) also landed, with no §17/§31 score move. Indices Maturity 94.4%→94.1%, Implementation 80.4%→82.5%. **A 2026-07-16 full re-score (34 categories, two-pass with adversarial verification) moved three scores:** §13 Observability Implementation 8→9 (both prior deductions closed: the SLO workbook is provisioned in IaC at `infra/main.bicep:274` and the per-alert `infra/OPERATIONS.md` runbook is in-repo; Maturity holds 3 because dashboards/runbooks are IaC/review-enforced, not CI-gated), and §18 UI Architecture + §19 State Management Maturity 3→4 (the sealed `UIArchitectureConventionTests` and `StateManagementConventionTests` subclasses of the shared v1.116.0 fitness bases run non-vacuously in the deploy-gating `MMCA.Store.CI.slnf` on every push and PR, the same mechanism that earned ADC its M4; their proposed Implementation bumps were adversarially rejected as enforcement gains mis-posted to the substance axis). The same re-score DECLINED the recorded maturity candidacies on §12 (k6 stays monthly/on-demand, not a merge gate) and §22 (firefox/webkit are still `continue-on-error` in `e2e.yml:71`, contrary to the backlog's promotion claim), and held the §20/§24/§27 impl candidacies. §17 Implementation 8→9 additionally banked on directly verified evidence: MI-SQL is active in production (repo variable `USE_MANAGED_IDENTITY_SQL=true` since 2026-07-12, activation deploy 29192048197 green), correcting the row's stale inert claim. Indices Maturity 94.1%→95.9%, Implementation 82.5%→83.0%. **A same-day drift-analysis fold (2026-07-16, cross-repo ADC-vs-Store comparison, each move adversarially verified) moved two more scores:** §23 Maturity 3→4 (the CWV budgets are hard assertions in the deploy-gating chromium `e2e-gate`, the identical evidence ADC's twentieth cycle credited; the earlier same-day hold at M3 had wrongly imported §12's k6-cadence reasoning) and §32 Implementation 8→9 (capability-level parity with ADC's I9: identical `--locked-mode`/audit/SBOM gating; the earlier FLAG reasoned from stale scorecard text, not capability). Doc corrections in the same fold: §16's narrated pin 1.113.0→1.116.0, §32's lock-file count 49→55, and the README gained the ADC-parity broker note (§33). Indices Maturity 95.9%→96.6%, Implementation 83.0%→83.3%. **A 2026-07-17 full re-score (34 categories, two-pass with adversarial verification, pin v1.117.0) moved four scores:** §5 Vertical Slice M3→4 and I7→8 (the sealed `SliceCohesionTests` subclass runs non-vacuously in the deploy-gating `MMCA.Store.CI.slnf`, the identical gate ADC credits at M4/I8; the layered-by-project hybrid stays a deliberate implementation-axis cap, no longer a maturity deduction), §13 Observability M3→4 (`ObservabilityConventionTests` machine-enforces the `sloAlertSpecs`-to-`OPERATIONS.md` pairing in the CI merge gate, closing exactly the "not CI-gated" reasoning that held M3), §22 Responsive M3→4 (the 2026-07-16 gate flip verified live: `continue-on-error` in `e2e.yml` is scoped to scheduled non-chromium runs only, so all three engines the `e2e-gate` invokes can fail a deploy; **note 2026-07-23: this basis drifted on 2026-07-18 when the gate was cut to chromium-only, see the §22 row**), and §27 i18n I8→9 (the `PseudoLocalizationTests` candidacy granted: pseudo-loc sentinel, no-overflow, and en-US leak probes run over Store's own `/`, `/catalog`, and `/login` pages in the deploy-gating chromium `e2e-gate`). The narrated framework pin refreshed 1.116.0→1.117.0 throughout. Indices Maturity 96.6%→98.4%, Implementation 83.3%→83.6%._ +_Rubric: [`ArchitectureEvaluationCriteria.md`](ArchitectureEvaluationCriteria.md) • Date: 2026-08-23 • Two axes per category: **Maturity** (0-4, process/governance) and **Implementation** (0-10, substance/execution). Indices computed deterministically from the scores below. Verified against current source at HEAD `063c90dd` (clean tree); framework dependency pinned at **MMCA.Common.* v1.160.0** (all 15 packages, lockstep, confirmed no divergence; ADRs are canonical in `Website/docs-src/adr/`, count/range owned by its `README.md`). **What moved this cycle (2026-08-23 full re-score, 34 categories, two-pass with adversarial verification, pin v1.160.0): no score moves.** 27 categories came back CONFIRMED at their prior values and seven (§5, §6, §7, §9, §12, §20, §31) came back FLAG, every one an adversarial rejection of a proposed first-pass move rather than a found regression: §6 held 8 (the only Source-side delta on the event path since the prior pin is a comment; the cross-service flow still carries its self-documented non-atomic publish window), §7 held 8 (the new evidence is a CI fitness gate, an enforcement gain on an already-M4 category, plus a second test that is vacuous in Store; the lockstep all-services deploy cap is confirmed live), §9 held 9 against a proposed DOWNGRADE (the contract-guard evidence base grew from the narrated two files to seven across all three services plus the frozen gRPC proto contract; only the row's narration and anchors were stale), §12 held M3 (the workflow files backing the axis are byte-unchanged since the cycle that rejected the identical uplift), §20 held 7 (zero `.razor`/`.css`/`.razor.cs` files changed since the last scored commit; the residual count corrects 31 to 30 as a counting fix, and the StoreHome stylesheets carry an additional previously un-cited hard-coded hex palette with `!important`), and §31 held 8 (the reversible-scale-events criterion is still unmet in its exact terms, the identical basis on which ADC's §31 uplift was rejected on 2026-08-01; the 300s metric-export interval and tighter ACR purge refine criteria already credited inside the 8). §5 is the one special case: its first-pass scorer returned no numbers, so M4/I8 is carried forward unverified this cycle and owes a fresh read at the next re-score. Citation repairs only (no substance change): `MMCA.Store.CI.slnf:53` corrected to `:53` throughout (line 52 is now `MMCA.Store.Identity.UI.Tests`), §8's migration-gate anchors re-based (model-drift `deploy.yml:216-230`, expand/contract `:232-274`), §9's guard narration and anchors corrected, §12's controller anchor re-based, and §27's `MoneyExtensions` anchor extended to `:54-59`. Indices unchanged: Maturity 97.8%, Implementation 83.9%. **Prior cycle, retained (2026-08-14 full re-score, 34 categories, two-pass with adversarial verification, pin v1.152.0): no score moves.** All 34 categories were re-scored from evidence read at HEAD `9571a963`; 28 came back CONFIRMED at their prior values and six (§5, §15, §17, §19, §20, §21) came back FLAG, every one an adversarial rejection of a proposed first-pass uplift rather than a found regression: §5 held 8 (horizontal technical folders persist inside module Application layers; generic-CRUD slices dispatch to shared framework handlers), §17 held 9 (no Bicep validate/what-if before the prod run; SQL `publicNetworkAccess: Enabled`, the identical caveat holding ADC at 9; single prod-only environment), §19 held 8 for the second consecutive cycle (`IsDrawerOpen` is still publicly settable and mutated outside the notify path, `CartDrawer.razor:4`, `CartDrawer.razor.cs:145`), §20 held 7 (the ProductList remediation converted 3 attributes while 31 identical `Style=`/`CellStyle=` occurrences remain across 14 razor files, five byte-identical to the new classes), and §21 held 3/8 (the screen-reader results log still holds only the placeholder row; the one delta since the prior pin is a single added dark-palette home axe scan, now 23 scans total). §15's verify pass proposed a correction to Implementation 7 on three suppression-hygiene gaps (the expired GHSA-2m69-gcr7-jv3q audit suppression at `Directory.Build.props:54` whose own removal condition is met under the v1.152.0 pin, three undocumented global `NoWarn` codes at `:26`, and the MAUI head outside all CI enforcement); the user adjudicated a hold at the prior 8 with the three gaps recorded as §15's named backlog lever. Indices unchanged: Maturity 97.8%, Implementation 83.9%. Anchor refreshes only (no substance change): §16's stale narrated pin corrected to v1.152.0, §20/§21/§22 evidence re-anchored, and §22's nightly cadence note updated (since 2026-07-29 the scheduled matrix runs one alternating engine per week, widening the per-engine blind window to 7 days). **Earlier cycle, retained (2026-07-28 full re-score, 34 categories, two-pass with adversarial verification, pin v1.131.0):** three scores moved. §8 Data Architecture I8→9 on substance that landed after the prior cycle: the atomic conditional-UPDATE stock decrement (`SET qty = qty - n WHERE qty >= n`) with deterministic variant-id lock ordering closes the oversell read-modify-write race (`InventoryAllocationService.cs:70`), backed by a `CK_InventoryItem_AvailableQuantity_NonNegative` schema CHECK constraint (`InventoryItemConfiguration.cs:27`), an explicit single-transaction checkout write phase with the cross-service gRPC price fetch deliberately outside the lock window (`CheckOutHandler.cs:91`), and a fail-closed expand/contract destructive-migration guard in the required `build-and-test` job (`deploy.yml:190`); held at 9, not 10, because Identity has no concurrency round-trip test. §22 Responsive M4→3, the reopen the 2026-07-23 drift note predicted: the deploy-gating `e2e-gate` passes `browsers: '["chromium"]'` only (`deploy.yml:494`) and firefox/webkit run solely on the Mon/Thu schedule where they stay `continue-on-error` (`e2e.yml:124,131`), with no cross-browser freshness job in `deploy.needs`, so cross-engine verification is convention-enforced (Consistent=3), not automatic; the proposed Implementation 8→7 was adversarially REJECTED as a CI-cadence change mis-posted to the substance axis, matching ADC's M3/I8 on the identical mechanism. §27 i18n I9→7→**8**, a corrected over-grant rather than a regression (no i18n file changed since 2026-07-17): every price renders through `Money.ToDisplayString()`, which hard-codes a `$` glyph and formats with `CultureInfo.InvariantCulture` (`MMCA.Common .../MoneyExtensions.cs:20,41`, consumed at `CatalogBrowse.razor.cs:302`), the rubric's explicit "manual number formatting ignoring culture" red flag, and pluralization is the `"{0} item(s)"` workaround rather than the i18n mechanism (`CartDrawer.resx:20`); the scorer proposed 7 and the user adjudicated 8, the conservative half of the band the verifier called defensible, since the gates and coverage behind the original grant are all intact. Three further first-pass proposals were adversarially REJECTED and held at prior: §12 M3→4 and I8→9 (no new merge-path perf gate exists; `load-test.yml:17-18` is still monthly cron plus dispatch, and the `load-freshness` gate actually GAINED a break-glass skip at `deploy.yml:577-592`, a weakening), §19 I8→9 (no new state-management substance since the prior pin; `IsDrawerOpen` is still publicly settable outside the notify path), and §30 M4/I8→M3/I7 (every cited mechanism re-read live at HEAD, no gap found). Indices Maturity 98.4%→97.8%, Implementation 83.6%→83.9%. **Earlier cycles, retained below, oldest first (2026-07-03 drift-plan execution, D1/D4/D5/D8/D9/D10):** §21 Accessibility M3→4 and §28 Front-End Testing M3→4 (the Playwright + axe suite now **gates the deploy**: `e2e-gate` joined `deploy.yml`'s `needs` after two consecutive fully green E2E runs, 28682334766 chromium 83/83 with firefox + webkit also green, confirmed by 28683063228), §12 Performance I7→8 (client Web Vitals are now measured in CI: `WebVitalsTests` writes LCP/CLS/TTFB/FCP artifacts per run), §23 Front-End Performance I6→8 (the public `CatalogBrowse` moved to server-side paging via `GetPagedAsync` + bounded `MobileInfiniteScrollList`, and cart enrichment now uses a targeted by-variant-id batch lookup instead of fetching the whole product list), and §32 Supply-Chain I7→8 (all three CI restores run `--locked-mode` and the suppress-aware vulnerability audit is now gating, D8/D9). The prior cycle's moves (2026-07-02 docs sweep: §16/§25/§20 M3→4, §27 scored M4/I7, §14 I6→9, §34 I7→9) are retained in the rows below. **A same-day i18n completion sweep (2026-07-03, ADR-027 Decision 9) then lifted §27 Implementation 7→8** (zero residual literals incl. the cart/checkout/Stripe snackbars, dual CI gates, MudBlazor chrome + nav localized; indices Implementation 80.3%→80.4%). **A subsequent 2026-07-11 drift-convergence cycle (drift plan D1-D13, pin v1.113.0) moved six scores:** §1 SOLID Implementation 8→9 (the ctor-dependency-ceiling gate `ConstructorDependencyCountTests` + `TimeProvider` injection, D9), §9 API Implementation 8→9 (the v2 `ServiceInfoController` + two deploy-gating Contract tests, D12), §24 Forms Maturity 3→4 (the CI-gated `FormsConventionTests`, D11), §28 Front-End Testing Implementation 6→8 (bUnit breadth grown to 214 facts across 40 files, D7), §29 Resilience Implementation 8→9 (the `dr-freshness` deploy gate + weekly `dr-drill` cron + `GracefulShutdownTests`, D3), and §21 Accessibility Maturity 4→3 with Implementation 7→8 (honest reconciliation to ADC's M3: 22 axe scans + the new screen-reader runbook, but no dated SR pass yet, D6). D2 (MI-SQL activation wiring) and D4 (cost-guard deploy gate) also landed, with no §17/§31 score move. Indices Maturity 94.4%→94.1%, Implementation 80.4%→82.5%. **A 2026-07-16 full re-score (34 categories, two-pass with adversarial verification) moved three scores:** §13 Observability Implementation 8→9 (both prior deductions closed: the SLO workbook is provisioned in IaC at `infra/main.bicep:274` and the per-alert `infra/OPERATIONS.md` runbook is in-repo; Maturity holds 3 because dashboards/runbooks are IaC/review-enforced, not CI-gated), and §18 UI Architecture + §19 State Management Maturity 3→4 (the sealed `UIArchitectureConventionTests` and `StateManagementConventionTests` subclasses of the shared v1.116.0 fitness bases run non-vacuously in the deploy-gating `MMCA.Store.CI.slnf` on every push and PR, the same mechanism that earned ADC its M4; their proposed Implementation bumps were adversarially rejected as enforcement gains mis-posted to the substance axis). The same re-score DECLINED the recorded maturity candidacies on §12 (k6 stays monthly/on-demand, not a merge gate) and §22 (firefox/webkit are still `continue-on-error` in `e2e.yml:71`, contrary to the backlog's promotion claim), and held the §20/§24/§27 impl candidacies. §17 Implementation 8→9 additionally banked on directly verified evidence: MI-SQL is active in production (repo variable `USE_MANAGED_IDENTITY_SQL=true` since 2026-07-12, activation deploy 29192048197 green), correcting the row's stale inert claim. Indices Maturity 94.1%→95.9%, Implementation 82.5%→83.0%. **A same-day drift-analysis fold (2026-07-16, cross-repo ADC-vs-Store comparison, each move adversarially verified) moved two more scores:** §23 Maturity 3→4 (the CWV budgets are hard assertions in the deploy-gating chromium `e2e-gate`, the identical evidence ADC's twentieth cycle credited; the earlier same-day hold at M3 had wrongly imported §12's k6-cadence reasoning) and §32 Implementation 8→9 (capability-level parity with ADC's I9: identical `--locked-mode`/audit/SBOM gating; the earlier FLAG reasoned from stale scorecard text, not capability). Doc corrections in the same fold: §16's narrated pin 1.113.0→1.116.0, §32's lock-file count 49→55, and the README gained the ADC-parity broker note (§33). Indices Maturity 95.9%→96.6%, Implementation 83.0%→83.3%. **A 2026-07-17 full re-score (34 categories, two-pass with adversarial verification, pin v1.117.0) moved four scores:** §5 Vertical Slice M3→4 and I7→8 (the sealed `SliceCohesionTests` subclass runs non-vacuously in the deploy-gating `MMCA.Store.CI.slnf`, the identical gate ADC credits at M4/I8; the layered-by-project hybrid stays a deliberate implementation-axis cap, no longer a maturity deduction), §13 Observability M3→4 (`ObservabilityConventionTests` machine-enforces the `sloAlertSpecs`-to-`OPERATIONS.md` pairing in the CI merge gate, closing exactly the "not CI-gated" reasoning that held M3), §22 Responsive M3→4 (the 2026-07-16 gate flip verified live: `continue-on-error` in `e2e.yml` is scoped to scheduled non-chromium runs only, so all three engines the `e2e-gate` invokes can fail a deploy; **note 2026-07-23: this basis drifted on 2026-07-18 when the gate was cut to chromium-only, see the §22 row**), and §27 i18n I8→9 (the `PseudoLocalizationTests` candidacy granted: pseudo-loc sentinel, no-overflow, and en-US leak probes run over Store's own `/`, `/catalog`, and `/login` pages in the deploy-gating chromium `e2e-gate`). The narrated framework pin refreshed 1.116.0→1.117.0 throughout. Indices Maturity 96.6%→98.4%, Implementation 83.3%→83.6%._ ## Executive summary -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 services behind a YARP gateway, collaborating via Result-over-the-wire gRPC and MassTransit integration events on the outbox pattern. It consumes the shared `MMCA.Common.*` framework at **v1.152.0** in lockstep with MMCA.ADC. **Architecturally it is at ADC parity**, and one prior assumption is corrected here: Store **runs database-per-service** (`Store_Catalog`/`Store_Sales`/`Store_Identity`, each with its own `dbo.OutboxMessages`; the legacy single `MMCAStore` DB is retained read-only as an archive/rollback only), not a single shared database. Its tactical depth (Clean Architecture, DDD, CQRS, the decorator pipeline, soft-delete/audit, RowVersion concurrency) is inherited framework substance, enforced by 23 NetArchTest fitness-test classes (shared `*TestsBase` subclasses from `MMCA.Common.Testing.Architecture` plus Store-local guards such as `DataResidencyTests`, `PiiConventionTests`, and `IntegrationEventContractTests`, ADR-015; the compile-time layer-guard MSBuild target is MMCA.Common-internal and does not run here). +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 services behind a YARP gateway, collaborating via Result-over-the-wire gRPC and MassTransit integration events on the outbox pattern. It consumes the shared `MMCA.Common.*` framework at **v1.160.0** in lockstep with MMCA.ADC. **Architecturally it is at ADC parity**, and one prior assumption is corrected here: Store **runs database-per-service** (`Store_Catalog`/`Store_Sales`/`Store_Identity`, each with its own `dbo.OutboxMessages`; the legacy single `MMCAStore` DB is retained read-only as an archive/rollback only), not a single shared database. Its tactical depth (Clean Architecture, DDD, CQRS, the decorator pipeline, soft-delete/audit, RowVersion concurrency) is inherited framework substance, enforced by 23 NetArchTest fitness-test classes (shared `*TestsBase` subclasses from `MMCA.Common.Testing.Architecture` plus Store-local guards such as `DataResidencyTests`, `PiiConventionTests`, and `IntegrationEventContractTests`, ADR-015; the compile-time layer-guard MSBuild target is MMCA.Common-internal and does not run here). The two axes are asymmetric: **Maturity 97.8%** vs **Implementation 83.9%**. Implementation is the weaker axis by ~14 points, a wider gap than ADC's. The maturity is high because Store inherits the framework's governed mechanisms and adds a strong operational floor (two-phase Bicep IaC, OIDC + Key Vault managed identity, a post-deploy smoke gate with auto-rollback, a drilled DR restore, a `cost-guard` surge-drift check, a k6 load test, and now a chromium E2E/axe deploy gate at ADC parity). The former §21/§28 gate gap and the §23 catalog fetch-all are closed this cycle, and the §27 residual unlocalized strings were closed by the same-day i18n completion sweep (I7→8; zero literals, dual CI gates); the former §18/§19 review-only maturity gap closed on 2026-07-16 (both are now CI-enforced by the shared convention fitness gates at M4), and the same-day drift-fold closed §23 (the deploy-gated CWV budget assertions credited at M4, ADC parity); the 2026-07-17 re-score then closed §5 (slice cohesion CI-gated, the hybrid kept as an implementation cap), §13 (the alert-to-runbook pairing gate), and §22 (the three-engine gate flip verified live). **§22 reopened to maturity 3 on 2026-07-28** when the 2026-07-18 Actions-minute reduction was scored rather than frozen: the deploy gate runs chromium only, so §12 (k6 not a merge gate), §21 (screen-reader pass pending), and §22 (cross-engine verification convention-enforced) are the three below-4 maturity categories. §14's former coverage gap is closed: the deploy-gating floor is 51.6 measured on Store's own code (`+MMCA.Store.*;-*.Tests`, ~54% actual). Supply-chain (§32): the vulnerability gate is NuGetAudit + `TreatWarningsAsErrors` at restore plus the gating suppress-aware audit, the SBOM is a hard gate, and all three CI restores run `--locked-mode`. @@ -20,29 +20,29 @@ Front-end security is the standout (§26, impl 9): access token in-memory, refre | 2 | Design Patterns | 2 | 4 | 9 | 8/18 | Factory→Result, payment State machine, Saga (Stripe compensation), Specification, Decorator, Outbox, Repository/UoW: idiomatic, named. Evidence: `Order.cs:76-106,43-53`; `Orders/Saga/OrderCancelledSagaHandler.cs`; `Orders/Specifications/OrdersByCustomerSpecification.cs` | | 3 | Clean Architecture | 3 | 4 | 9 | 12/27 | NetArchTest-enforced layer rules; domain framework-pure. (The compile-time layer-guard MSBuild target is MMCA.Common-internal; it is not a Store-side gate.) Evidence: `StoreArchitectureMap.cs:14-43` + `LayerDependencyTests`/`DomainPurityTests`; `Order.cs:1-13` (imports only Common.Domain/Shared) | | 4 | Domain-Driven Design | 3 | 4 | 9 | 12/27 | Aggregate root, `Money` VO, by-id cross-aggregate refs, domain events, invariants, factory→Result, rich state behavior; identifier aliases. Evidence: `Order.cs` (`Money` :32, `OrderInvariants`, `OrderIdentifierType`) | -| 5 | Vertical Slice Architecture | 2 | 4 | 8 | 8/16 | Cohesive command+handler+request+validator+mapper per operation; deliberate layered-by-project hybrid (cross-cutting in the pipeline). **↑ Maturity 3→4 + Implementation 7→8 (2026-07-17):** slice cohesion is machine-enforced pre-merge by `SliceCohesionTests` (sealed subclass of the shared non-vacuous `SliceCohesionTestsBase`, two real rule facts) in the deploy-gating `MMCA.Store.CI.slnf`, the identical gate ADC credits at M4/I8; the hybrid stays a deliberate implementation-axis cap (holds impl at 8, not a maturity deduction). Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/SliceCohesionTests.cs:9`; `MMCA.Store.CI.slnf:52`; `Application/Orders/UseCases/Cancel/{CancelOrderCommand,CancelOrderHandler}.cs` | +| 5 | Vertical Slice Architecture | 2 | 4 | 8 | 8/16 | Cohesive command+handler+request+validator+mapper per operation; deliberate layered-by-project hybrid (cross-cutting in the pipeline). **↑ Maturity 3→4 + Implementation 7→8 (2026-07-17):** slice cohesion is machine-enforced pre-merge by `SliceCohesionTests` (sealed subclass of the shared `SliceCohesionTestsBase`, two real rule facts; the base carries no minimum-scanned-types floor, and the scan is non-vacuous in practice because `StoreArchitectureMap` anchors real Application assemblies) in the deploy-gating `MMCA.Store.CI.slnf`, the identical gate ADC credits at M4/I8; the hybrid stays a deliberate implementation-axis cap (holds impl at 8, not a maturity deduction). **2026-08-23: the proposed 8→9 was adversarially rejected a second consecutive cycle**: horizontal technical folders persist inside the module Application layers, gate-invisible where the validated type is cross-assembly (the co-location rule exempts them, `MMCA.Common .../ArchitectureRules.Slices.cs:48`; instance: `Identity.Application/Users/Validation/ChangePasswordRequestValidator.cs:10` over the Shared `ChangePasswordRequest` while its command+handler live in `Users/UseCases/ChangePassword/`), and generic-CRUD operations still dispatch to shared framework handlers (`Catalog.Application/DependencyInjection.cs:41`, `Identity.Application/DependencyInjection.cs:47`) with reads served by the generic `IEntityQueryService` (`CategoriesController.cs:33`). The first-pass scorer returned no numbers this cycle, so M4/I8 is carried forward unverified and owes a fresh read at the next re-score. Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/SliceCohesionTests.cs:9`; `MMCA.Store.CI.slnf:53`; `Application/Orders/UseCases/Cancel/{CancelOrderCommand,CancelOrderHandler}.cs` | | 6 | CQRS & Event-Driven | 2 | 4 | 8 | 8/16 | Command/query split, outbox persist-then-publish, idempotent inbox (`AddInboxMessages`), Stripe saga; one cross-service event flow (`ProductVariantChanged`) + a deliberate in-process `UserRegistered` domain event; the genuine outbox-to-broker-to-consumer round-trip is now covered by the non-gating nightly `MMCA.Store.CrossService.IntegrationTests` (Testcontainers RabbitMQ+SQL, D5). Evidence: `Sales.Service/Program.cs:168-169`; `IntegrationEventContractTests.cs`; `Tests/Integration/MMCA.Store.CrossService.IntegrationTests/` | | 7 | Microservices Readiness | 3 | 4 | 8 | 12/24 | **Database-per-service** (corrected from the old single-DB assumption) + per-source outbox, async events + versioned gRPC, Polly resilience, extractable modules, 5 independently-built images. Minor: all per-service DBs share one physical SQL *server*. Evidence: `infra/main.bicep:396-418` (per-service DBs) + `:445-474` (Service Bus broker); `Sales.Service/Program.cs:156-157,168` | -| 8 | Data Architecture | 3 | 4 | 9 | 12/27 | Per-aggregate tx, soft-delete + filtered indexes, central audit (Common), RowVersion concurrency, versioned per-service migrations with a model-drift CI gate, LTR backups. **↑ Implementation 8→9 (2026-07-28):** the write path is now race-safe by construction, not by convention. Stock decrements are an atomic conditional UPDATE (`SET qty = qty - n WHERE qty >= n`) with deterministic variant-id lock ordering, enlisted in the ambient transaction and stamping the audit columns explicitly because `ExecuteUpdate` bypasses the audit interceptor (`InventoryAllocationService.cs:70`); a `CK_InventoryItem_AvailableQuantity_NonNegative` CHECK constraint backstops it at the schema so no future path can drive stock negative (`InventoryItemConfiguration.cs:27`); the checkout write phase (decrements + order insert + cart transition) is one `ExecuteInTransactionAsync` with the cross-service gRPC price fetch deliberately outside it, so remote latency never extends lock hold time (`CheckOutHandler.cs:91`); and an expand/contract guard fails any PR whose new migration `Up()` drops a column/table/index without an `EXPAND-CONTRACT-OVERRIDE` marker, failing closed when the base diff is unresolvable (`deploy.yml:190`, policy at `CONTRIBUTING.md:55`). Both migration gates sit in the required `build-and-test` check. Held at 9, not 10: Identity carries `IConcurrencyAware` mutation requests but has no concurrency round-trip test, so the 409 proof covers 2 of 3 modules; note also that the shared `ConcurrencyConventionTests` rule scans Application types named `*UpdateRequest`, of which Store has none, so that particular gate is vacuous here (the substance below stands without it). Evidence: `InventoryAllocationService.cs:70`; `InventoryItemConfiguration.cs:27`; `CheckOutHandler.cs:91`; API-level round-trip proof incl. the child-entity token path in `Tests/Integration/MMCA.Store.Catalog.IntegrationTests/Concurrency/StaleRowVersionConflictTests.cs:45` and `Tests/Integration/MMCA.Store.Sales.IntegrationTests/Concurrency/OrderTransitionConcurrencyTests.cs:23`; raw-`IQueryable` ban with an EMPTY allowlist in `Tests/Architecture/MMCA.Store.Architecture.Tests/RawQueryableConventionTests.cs:14` (in `MMCA.Store.CI.slnf:52`); per-module model-drift gate `deploy.yml:174-188` (corrected from the stale `:86-99`); `main.bicep:383-394` | -| 9 | API & Contract Design | 2 | 4 | 9 | 8/18 | RFC 9457 Problem Details, header versioning, pagination, DTO decoupling (ADR-001), gRPC `.proto`, OpenAPI served non-prod, and a demonstrated v2 contract: `ServiceInfoController` carries `[ApiVersion("1.0", Deprecated)]` + `[ApiVersion("2.0")]`, backed by two deploy-gating contract guards (D12). Below 10: OpenAPI not exposed in prod (internal behind gateway); v2 demonstrated on one endpoint. Evidence: `Catalog.API/Controllers/ServiceInfoController.cs:18`; `Tests/Integration/MMCA.Store.Catalog.IntegrationTests/Contract/{ApiVersioningTests,OpenApiContractTests}.cs`; `OrdersController.cs:77,34,114`; `Sales.Service/Program.cs:102,193` | +| 8 | Data Architecture | 3 | 4 | 9 | 12/27 | Per-aggregate tx, soft-delete + filtered indexes, central audit (Common), RowVersion concurrency, versioned per-service migrations with a model-drift CI gate, LTR backups. **↑ Implementation 8→9 (2026-07-28):** the write path is now race-safe by construction, not by convention. Stock decrements are an atomic conditional UPDATE (`SET qty = qty - n WHERE qty >= n`) with deterministic variant-id lock ordering, enlisted in the ambient transaction and stamping the audit columns explicitly because `ExecuteUpdate` bypasses the audit interceptor (`InventoryAllocationService.cs:70`); a `CK_InventoryItem_AvailableQuantity_NonNegative` CHECK constraint backstops it at the schema so no future path can drive stock negative (`InventoryItemConfiguration.cs:27`); the checkout write phase (decrements + order insert + cart transition) is one `ExecuteInTransactionAsync` with the cross-service gRPC price fetch deliberately outside it, so remote latency never extends lock hold time (`CheckOutHandler.cs:91`); and an expand/contract guard fails any PR whose new migration `Up()` drops a column/table/index without an `EXPAND-CONTRACT-OVERRIDE` marker, failing closed when the base diff is unresolvable (`deploy.yml:232-274`, re-anchored 2026-08-23; policy at `CONTRIBUTING.md:55`). Both migration gates sit in the required `build-and-test` check. Held at 9, not 10: Identity carries `IConcurrencyAware` mutation requests but has no concurrency round-trip test, so the 409 proof covers 2 of 3 modules; note also that the shared `ConcurrencyConventionTests` rule scans Application types named `*UpdateRequest`, of which Store has none, so that particular gate is vacuous here (the substance below stands without it). Evidence: `InventoryAllocationService.cs:70`; `InventoryItemConfiguration.cs:27`; `CheckOutHandler.cs:91`; API-level round-trip proof incl. the child-entity token path in `Tests/Integration/MMCA.Store.Catalog.IntegrationTests/Concurrency/StaleRowVersionConflictTests.cs:45` and `Tests/Integration/MMCA.Store.Sales.IntegrationTests/Concurrency/OrderTransitionConcurrencyTests.cs:23`; raw-`IQueryable` ban with an EMPTY allowlist in `Tests/Architecture/MMCA.Store.Architecture.Tests/RawQueryableConventionTests.cs:14` (in `MMCA.Store.CI.slnf:53`); per-module model-drift gate `deploy.yml:216-230` (re-anchored 2026-08-23); `main.bicep:383-394` | +| 9 | API & Contract Design | 2 | 4 | 9 | 8/18 | RFC 9457 Problem Details, header versioning, pagination, DTO decoupling (ADR-001), gRPC `.proto`, OpenAPI served non-prod, and a demonstrated v2 contract: `ServiceInfoController` carries `[ApiVersion("1.0", Deprecated)]` + `[ApiVersion("2.0")]`. **Narration corrected 2026-08-23 (no score impact; a proposed downgrade to 8 was adversarially REJECTED because the evidence base grew, only the row's text was stale):** the guards are seven contract-guard files across all three services (OpenAPI shape/path-floor + RFC 9457 Problem Details for Catalog/Sales/Identity, incl. the Store-specific 409 stale-RowVersion probe, plus ServiceInfo API-versioning on Catalog), running in `integration-tests`, which is PR-only (`deploy.yml:394`, absent from `deploy.needs` at `:862`) but a server-side REQUIRED status check on `main` with strict=true (`CONTRIBUTING.md:83`), and merging to `main` is the prod deploy, so nothing ships without them green; `ProtoContractTests` additionally freezes the full cross-service gRPC wire contract in the merge-gating architecture tier (`ProtoContractTests.cs:9`). Below 10: OpenAPI not exposed in prod (internal behind gateway); v2 demonstrated on one anonymous diagnostic endpoint in one service (all 14 business controllers are v1.0-only). Evidence: `Catalog.API/Controllers/ServiceInfoController.cs:18`; `Tests/Integration/MMCA.Store.Catalog.IntegrationTests/Contract/{ApiVersioningTests,OpenApiContractTests,ProblemDetailsContractTests}.cs` + Sales/Identity siblings; `OrdersController.cs:46,91,120,127-129` (re-anchored 2026-08-23); `Sales.Service/Program.cs:148,284,307` (re-anchored 2026-08-23) | | 10 | Cross-Cutting Concerns | 2 | 4 | 8 | 8/16 | Pipeline decorators, typed `ValidateOnStart` options, CORS/versioning/rate-limit/output-cache/compression, shared Polly: none copy-pasted. Evidence: `Sales.Service/Program.cs`; the same IsFailure-guarded cache-eviction pattern ADR-001 describes and the ADC scorecard credits is present here too (`Catalog.API/Controllers/ProductsController.cs:151,172`) | | 11 | Security | 3 | 4 | 8 | 12/24 | RS256/JWKS (no shared secret), server-side resource authz (404-not-403), KV secrets via UAMI, EF parameterized, Stripe webhook-secret handling. Evidence: `main.bicep:475-528,457-460`; `Sales.Service/Program.cs:116-118`; `OrdersController.cs:35,143,190,291-317` | -| 12 | Performance & Scalability | 2 | 3 | 8 | 6/16 | Async throughout, projections/AsNoTracking/paging on hot paths, tiered cache (output + Redis), stateless scale-out, a real k6 load test, and client Web Vitals measured per E2E run (LCP/CLS/TTFB/FCP written as CI artifacts, D10). Evidence: `Tests/Load/k6/catalog-read-load.js` + `load-test.yml`; `Tests/E2E/MMCA.Store.E2E.Tests/Workflows/WebVitalsTests.cs` + `e2e.yml` (`WEB_VITALS_OUTPUT_DIR`); `OrdersController.cs:73,108` | -| 13 | Observability & Operability | 2 | 4 | 9 | 8/18 | OTel logs/traces/RED metrics via ServiceDefaults (incl. `MMCA.Common.Outbox`), `/health`+`/alive`+`/health/ready`, App Insights + 3 SLO alerts + action group + saved SLO workbook provisioned in IaC, per-alert operations runbook in-repo, correlation, poll-span noise control, CI-gated graceful shutdown. **↑ Maturity 3→4 (2026-07-17):** the alert-to-runbook pairing is now a CI-gated fitness function: `ObservabilityConventionTests` parses the embedded `infra/main.bicep` `sloAlertSpecs` and fails the merge gate on any alert without a severity-correct `OPERATIONS.md` section (3-spec non-vacuity floor), closing exactly the "IaC/review-enforced, not CI-gated" reasoning that held M3. Implementation 9 (2026-07-16: workbook + runbook deductions closed). Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/ObservabilityConventionTests.cs (a sealed subclass since the 2026-07-28 extraction wave; the three [Fact]s now live in `MMCA.Common.Testing.Architecture/Bases/ObservabilityConventionTestsBase.cs`),34`; `MMCA.Store.CI.slnf:52`; `main.bicep:204` (`sloAlertSpecs`), `:274` (workbook); `infra/OPERATIONS.md:16`; `Tests/Hosts/MMCA.Store.Gateway.Tests/GracefulShutdownTests.cs` (a sealed subclass since the 2026-07-28 extraction wave; the assertion body now lives in `MMCA.Common.Testing/GracefulShutdownTestsBase.cs`) | +| 12 | Performance & Scalability | 2 | 3 | 8 | 6/16 | Async throughout, projections/AsNoTracking/paging on hot paths, tiered cache (output + Redis), stateless scale-out, a real k6 load test, and client Web Vitals measured per E2E run (LCP/CLS/TTFB/FCP written as CI artifacts, D10). Evidence: `Tests/Load/k6/catalog-read-load.js` + `load-test.yml`; `Tests/E2E/MMCA.Store.E2E.Tests/Workflows/WebVitalsTests.cs` + `e2e.yml` (`WEB_VITALS_OUTPUT_DIR`); `OrdersController.cs:102-112` (bounded pageSize, `asTracking: false`, field projection; re-anchored 2026-08-23) | +| 13 | Observability & Operability | 2 | 4 | 9 | 8/18 | OTel logs/traces/RED metrics via ServiceDefaults (incl. `MMCA.Common.Outbox`), `/health`+`/alive`+`/health/ready`, App Insights + 3 SLO alerts + action group + saved SLO workbook provisioned in IaC, per-alert operations runbook in-repo, correlation, poll-span noise control, CI-gated graceful shutdown. **↑ Maturity 3→4 (2026-07-17):** the alert-to-runbook pairing is now a CI-gated fitness function: `ObservabilityConventionTests` parses the embedded `infra/main.bicep` `sloAlertSpecs` and fails the merge gate on any alert without a severity-correct `OPERATIONS.md` section (3-spec non-vacuity floor), closing exactly the "IaC/review-enforced, not CI-gated" reasoning that held M3. Implementation 9 (2026-07-16: workbook + runbook deductions closed). Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/ObservabilityConventionTests.cs (a sealed subclass since the 2026-07-28 extraction wave; the three [Fact]s now live in `MMCA.Common.Testing.Architecture/Bases/ObservabilityConventionTestsBase.cs`),34`; `MMCA.Store.CI.slnf:53`; `main.bicep:204` (`sloAlertSpecs`), `:274` (workbook); `infra/OPERATIONS.md:16`; `Tests/Hosts/MMCA.Store.Gateway.Tests/GracefulShutdownTests.cs` (a sealed subclass since the 2026-07-28 extraction wave; the assertion body now lives in `MMCA.Common.Testing/GracefulShutdownTestsBase.cs`) | | 14 | Testability & Test Strategy | 3 | 4 | 9 | 12/27 | Non-inverted pyramid + arch fitness + integration tier gating deploy; the unit-tier coverage floor is 51.6 measured on Store's own code (reportgenerator `+MMCA.Store.*;-*.Tests`, ~54% actual), the same self-filtered gate shape as ADC. A non-gating nightly real-broker round-trip tier (`MMCA.Store.CrossService.IntegrationTests`, Testcontainers RabbitMQ+SQL, `cross-service-tests.yml`) now covers the outbox-to-broker-to-consumer flow the in-process tests only approximate (D5). Held at 9 (not 10): the broker round-trip is non-gating nightly and the SQL integration suite runs only in CI. Evidence: `deploy.yml:69-99` (floor 51.6 at :86, self-filter at :84), `:147-215`; arch tests; `Common.Testing.E2E` | | 15 | Best Practices & Code Quality | 2 | 4 | 8 | 8/16 | Five analyzers at error + TWAE + CPM; targeted vuln pin; consistent Result pattern; zero TODO/HACK/FIXME in `Source/`, every hand-written pragma carries an inline reason. **2026-08-14 verify pass: the adversarial pass proposed Implementation 7 on three suppression-hygiene gaps, user-adjudicated to hold at 8 with the gaps recorded as the backlog lever:** (1) the GHSA-2m69-gcr7-jv3q audit suppression (`Directory.Build.props:54`) is expired by its own removal-condition comment (`:45-52`): Store pins v1.152.0 and Common ships the patched `SQLitePCLRaw.bundle_e_sqlite3` 3.0.5 directly, so the entry suppresses nothing in the audited graph; (2) three of the four global `NoWarn` codes (`:26`, CS1591;RMG020;EXTEXP0001) are undocumented, with uncommented duplicates across five test csprojs despite the centralization intent at `:35-37`; (3) the MAUI head is outside `MMCA.Store.CI.slnf`, so its analyzers/TWAE/audit are review-only. The same three-gap evidence set adjudicated ADC's §15 to Implementation 7 on 2026-07-28. Evidence: `Directory.Build.props:16-20,57-78`; `Directory.Packages.props:57-60,69` (the five analyzers, citation refreshed from the stale `:85-88,97`), `:111` (targeted OpenTelemetry.Api vuln pin, refreshed from `:130`); `.editorconfig:312`; `deploy.yml:153` | -| 16 | Maintainability & Evolvability | 2 | 4 | 8 | 8/16 | Versioned framework contracts (Common 1.152.0, lockstep, matching this document's own header pin; the 2026-08-13 stale-pin note is resolved, re-verified 2026-08-14 at `Directory.Packages.props:8-21`) with the lockstep invariant executable: `FrameworkVersionConsistencyTests` asserts every `MMCA.Common.*` pin shares one version and fails the build on a partial sweep, running in the CI merge gate. Consumes the shared arch-test package, current CLAUDE.md, extractable modules. Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/FrameworkVersionConsistencyTests.cs`; `MMCA.Store.CI.slnf:52`; `Directory.Packages.props:8-19` | +| 16 | Maintainability & Evolvability | 2 | 4 | 8 | 8/16 | Versioned framework contracts (Common 1.160.0, lockstep, matching this document's own header pin; re-verified 2026-08-23 at `Directory.Packages.props:8-21`) with the lockstep invariant executable: `FrameworkVersionConsistencyTests` asserts every `MMCA.Common.*` pin shares one version and fails the build on a partial sweep, running in the CI merge gate. Consumes the shared arch-test package, current CLAUDE.md, extractable modules. Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/FrameworkVersionConsistencyTests.cs`; `MMCA.Store.CI.slnf:53`; `Directory.Packages.props:8-19` | | 17 | DevOps & Deployment | 2 | 4 | 9 | 8/18 | CI gates (build/analyzers/tests/coverage-floor/model-drift) then an integration gate then deploy with **post-deploy smoke + auto-rollback**; two-phase Bicep; OIDC managed identity; cost-guard/dr-drill/load-test workflows. **↑ Implementation 8→9 (2026-07-16):** MI-SQL is ACTIVE in production, not inert: the full `infra/SQL-MANAGED-IDENTITY.md` sequence completed 2026-07-12 (repo variable `USE_MANAGED_IDENTITY_SQL=true` set 2026-07-12, activation deploy run 29192048197 green with the full gate chain + smoke), so all three services authenticate passwordless via managed identity; the SQL password path remains only as the documented dual-auth rollback. **2026-08-14 verify: a proposed 9→10 was adversarially REJECTED** (no Bicep validate/what-if runs before the prod deploy, so an infra-only PR merges with the template unexecuted; SQL `publicNetworkAccess: Enabled` with the AllowAzureServices rule at `infra/main.bicep:550,571-577`, the identical caveat holding ADC at 9; single prod-only environment, `ENVIRONMENT_NAME: prod` hardcoded at `deploy.yml:28`). Evidence: `deploy.yml:922-925,1030-1033` (MI-SQL wiring, re-anchored 2026-08-14 from the stale `:452-454,541-553`), `:862` (deploy gate chain), `:1059,1109-1132` (smoke + auto-rollback); `infra/SQL-MANAGED-IDENTITY.md`; repo vars `USE_MANAGED_IDENTITY_SQL`/`SQL_AAD_ADMIN_*` (re-verified 2026-08-14 via `gh variable list`) | -| 18 | UI Architecture & Components | 3 | 4 | 8 | 12/24 | Code-behind split, `@inherits DataGridListPageBase`, scoped cart service owns data/behavior, reuse of Common.UI primitives. **↑ Maturity 3→4 (2026-07-16):** the container/presentational conventions are machine-enforced pre-merge by `UIArchitectureConventionTests` (sealed subclass of the shared base, 400-line code-behind cap + 120-line inline `@code` cap, non-vacuous `MinimumCodeBehindFiles` guard; largest code-behind is 368 lines), running in the deploy-gating `MMCA.Store.CI.slnf` on push and PR, the same mechanism as ADC's M4. Implementation holds 8: minor inline-style logic in markup remains. Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/UIArchitectureConventionTests.cs:11`; `StoreArchitectureMap.cs:29,37,45`; `MMCA.Store.CI.slnf:52`; `deploy.yml:59,429`; `ProductList.razor.cs:14`; `CartDrawer.razor:50` | -| 19 | State Management & Data Flow | 3 | 4 | 8 | 12/24 | Single source of truth, scoped (no static cross-user state), unidirectional flow with `OnChange`+`InvokeAsync(StateHasChanged)`+`Dispose`, single-flight token hydrate. **↑ Maturity 3→4 (2026-07-16):** both §19 red flags are machine-enforced pre-merge by `StateManagementConventionTests` (mutable-static-state reflection scan over the `Layer.Ui` assemblies with a non-vacuous guard, plus the singleton-`*StateService` source scan), in the deploy-gating `MMCA.Store.CI.slnf`; `CartStateService` is registered `TryAddScoped`, proving the rule the gate enforces. Implementation holds 8. Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/StateManagementConventionTests.cs:12`; `Sales.UI/DependencyInjection.cs:35`; `MMCA.Store.CI.slnf:52`; `CartStateService.cs:38`; `CartButton.razor:33`; `ServerTokenStorageService.cs:44` | -| 20 | Design System & UI Consistency | 2 | 4 | 7 | 8/14 | MudBlazor + Common.UI theme/tokens used consistently, shared grid-paging wrapper; the brand-color token convention is now CI-enforced by `BrandColorTokenTests` (shipped `5fbd003`, guards both UI hosts' home CSS against hard-coded brand hex). **2026-08-14 verify: a proposed 7→8 was adversarially REJECTED.** Commit `a1de5a89` converted the three previously cited `ProductList.razor` attributes to semantic classes (`.list-search-field`, `.grid-cell-count`, `.grid-cell-actions`, rules in `store.css:28-40`), but 31 `Style=`/`CellStyle=` occurrences remain across 14 razor files, five byte-identical to the classes just created; the shared classes existing while 3 of the 4 sibling admin list pages do not use them is itself the rubric's fought-page-by-page red flag. Evidence: `App.razor:11`; `Tests/Architecture/MMCA.Store.Architecture.Tests/BrandColorTokenTests.cs`; residuals re-anchored 2026-08-14: `CategoryList.razor:23` (`Style=`), `:79`/`:88` (`CellStyle=`), `OrderList.razor:22`, `CustomerList.razor:81`, plus `CatalogBrowse.razor`/`CatalogProductDetail.razor`/`OrderLinesPanel.razor`/`CustomerDetail.razor` | +| 18 | UI Architecture & Components | 3 | 4 | 8 | 12/24 | Code-behind split, `@inherits DataGridListPageBase`, scoped cart service owns data/behavior, reuse of Common.UI primitives. **↑ Maturity 3→4 (2026-07-16):** the container/presentational conventions are machine-enforced pre-merge by `UIArchitectureConventionTests` (sealed subclass of the shared base, 400-line code-behind cap + 120-line inline `@code` cap, non-vacuous `MinimumCodeBehindFiles` guard; largest code-behind is 368 lines), running in the deploy-gating `MMCA.Store.CI.slnf` on push and PR, the same mechanism as ADC's M4. Implementation holds 8: minor inline-style logic in markup remains. Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/UIArchitectureConventionTests.cs:11`; `StoreArchitectureMap.cs:29,37,45`; `MMCA.Store.CI.slnf:53`; `deploy.yml:59,429`; `ProductList.razor.cs:14`; `CartDrawer.razor:50` | +| 19 | State Management & Data Flow | 3 | 4 | 8 | 12/24 | Single source of truth, scoped (no static cross-user state), unidirectional flow with `OnChange`+`InvokeAsync(StateHasChanged)`+`Dispose`, single-flight token hydrate. **↑ Maturity 3→4 (2026-07-16):** both §19 red flags are machine-enforced pre-merge by `StateManagementConventionTests` (mutable-static-state reflection scan over the `Layer.Ui` assemblies with a non-vacuous guard, plus the singleton-`*StateService` source scan), in the deploy-gating `MMCA.Store.CI.slnf`; `CartStateService` is registered `TryAddScoped`, proving the rule the gate enforces. Implementation holds 8. Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/StateManagementConventionTests.cs:12`; `Sales.UI/DependencyInjection.cs:35`; `MMCA.Store.CI.slnf:53`; `CartStateService.cs:38`; `CartButton.razor:33`; `ServerTokenStorageService.cs:44` | +| 20 | Design System & UI Consistency | 2 | 4 | 7 | 8/14 | MudBlazor + Common.UI theme/tokens used consistently, shared grid-paging wrapper; the brand-color token convention is now CI-enforced by `BrandColorTokenTests` (shipped `5fbd003`, guards both UI hosts' home CSS against hard-coded brand hex). **2026-08-14 verify: a proposed 7→8 was adversarially REJECTED; re-rejected 2026-08-23** (zero `.razor`/`.css`/`.razor.cs` files changed since the last scored commit, so the identical evidence set stands). Commit `a1de5a89` converted the three previously cited `ProductList.razor` attributes to semantic classes (`.list-search-field`, `.grid-cell-count`, `.grid-cell-actions`, rules in `store.css:28-40`), but 30 `Style=`/`CellStyle=` occurrences remain across 14 razor files (count corrected from 31 on 2026-08-23, a counting fix, not a conversion), five byte-identical to the classes just created; the shared classes existing while 3 of the 4 sibling admin list pages do not use them is itself the rubric's fought-page-by-page red flag. A further previously un-cited red flag surfaced 2026-08-23: the StoreHome landing stylesheet hard-codes a hex palette alongside `!important` overrides with only `--mmca-primary` tokenized, duplicated byte-for-byte in both UI hosts (`UI.Web.Client/Pages/StoreHome.razor.css:203` and the MAUI head's `StoreHome.razor.css:203,271`). Evidence: `App.razor:11`; `Tests/Architecture/MMCA.Store.Architecture.Tests/BrandColorTokenTests.cs`; residuals re-anchored 2026-08-14, re-verified byte-identical 2026-08-23: `CategoryList.razor:23` (`Style=`), `:79`/`:88` (`CellStyle=`), `OrderList.razor:22`, `CustomerList.razor:81`, plus `CatalogBrowse.razor`/`CatalogProductDetail.razor`/`OrderLinesPanel.razor`/`CustomerDetail.razor` | | 21 | Accessibility (a11y) | 3 | 3 | 8 | 9/24 | Strong semantics/ARIA, stated WCAG 2.1 AA, **23 axe scans** over the public, shopper, and Catalog/Sales/Identity admin surfaces plus home in both palettes (a dark-palette home scan added since the prior cycle, `AccessibilityTests.cs:327-340`; count refreshed 2026-08-14 from the stale 22), pinned to real WCAG 2.1 AA tags (`AxeOptions.cs:17-24`), and the axe suite **gates the deploy** (the chromium `e2e-gate` in `deploy.yml`'s `needs`; nightly keeps the full matrix). Maturity is honestly 3, not 4 (corrected D6, re-confirmed 2026-08-14): the rubric pairs axe-in-CI with a recorded manual screen-reader pass, and the `ACCESSIBILITY-SCREENREADER-PASS.md` results log still holds only the placeholder row; the e2e-gate is also chromium-only, UI-change-conditioned, and a SKIPPED gate is accepted by `deploy` (`deploy.yml:544,547,893`). **2026-08-14 verify: proposed M3→4 and I8→9 both adversarially REJECTED**: keyboard operability/focus order have zero automated coverage, every grid scan disables `aria-input-field-name` for the MudBlazor pager combobox (accepted, `AxeOptions.cs:35-46`), and dark-palette contrast is scanned on one page. Evidence: `Tests/E2E/MMCA.Store.E2E.Tests/Workflows/AccessibilityTests.cs:25-356` (23 scans); `store-ACCESSIBILITY-SCREENREADER-PASS.md:68` (Website `docs-src/guides/`; placeholder row only); `CartDrawer.razor:10,27,34,77-100` (re-anchored 2026-08-14 from the stale `:9,20,67`); `deploy.yml` (`e2e-gate` in deploy `needs`) | | 22 | Responsive & Cross-Browser | 2 | 3 | 8 | 6/16 | Fluid layouts, grid→mobile-card reflow, defined+Playwright-verified chromium/firefox/webkit matrix. **↓ Maturity 4→3 (2026-07-28), the reopen the 2026-07-23 drift note predicted:** the 2026-07-18 Actions-minute reduction (commit `777348ec`) cut the deploy-gating `e2e-gate` to chromium only (`deploy.yml:494`, `browsers: '["chromium"]'`, rationale at `:483-488`), and firefox/webkit now run solely on the Mon/Thu schedule where they stay `continue-on-error` (`e2e.yml:124,131`), with no cross-browser freshness job in `deploy.needs`. Cross-engine verification is therefore convention-enforced (Consistent=3), not automatic (Optimized=4), matching ADC's M3 on the identical mechanism. The trade-off is deliberate and is recorded in the backlog's Deliberate / accepted section; the score reflects what CI enforces, which is what the rubric measures. **Implementation holds 8:** the proposed 8→7 was adversarially REJECTED as a CI-cadence change mis-posted to the substance axis, with no responsive-implementation regression found. **Cadence update (verified 2026-08-14):** since 2026-07-29 the scheduled matrix runs ONE alternating engine per week (Monday firefox, Thursday webkit), not both engines twice weekly, so the per-engine blind window is now 7 days. Evidence (re-anchored 2026-08-14): `deploy.yml:537,547` (`e2e-gate` job, `browsers: '["chromium"]'`), `:862` (`e2e-gate` in deploy `needs`); `e2e.yml:37-47` (crons), `:133-135` (alternating engine selection), `:143` (`continue-on-error`); `ProductList.razor:24`; `CatalogBrowse.razor:86` | | 23 | Front-End Performance | 2 | 4 | 8 | 8/16 | Server paging/debounce/`@key`/output-cache on admin grids; the public `CatalogBrowse` uses server-side paging (`GetPagedAsync` + `MudPagination`, D4) and cart enrichment resolves names via the targeted by-variant-id batch lookup. **↑ Maturity 3→4 (2026-07-16 drift-fold, adversarially verified):** the Core Web Vitals budgets are HARD assertions (`WebVitalsTests.cs:76-79`, `Assert.True` per metric, no soft mode) running unfiltered inside the deploy-gating chromium `e2e-gate` (`deploy.yml:429` `needs`), the identical evidence shape ADC's twentieth cycle credited M4 for; the prior M3 wrongly imported §12's k6-cadence reasoning into a category whose own enforcement is per-deploy. Evidence: `Tests/E2E/MMCA.Store.E2E.Tests/Workflows/WebVitalsTests.cs:76-79`; `deploy.yml:311-315,429`; `CatalogBrowse.razor.cs`; `CartStateService.cs` | -| 24 | Forms, Validation & UX Safety | 2 | 4 | 8 | 8/16 | Unsaved-changes guard with current-state accessor (9 pages), double-submit blocked, all states designed, destructive confirm, abandoned-payment recovery. **↑ Maturity 3→4 (D11):** the four admin create forms' guard/dirty/validated-`MudForm`/`Required` markers are machine-enforced by the CI-gated `FormsConventionTests` (`MinimumCreateForms=4`), matching ADC on the same evidence. Client validation is MudForm-level (not full FluentValidation parity), the impl 8→9 lever. Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/FormsConventionTests.cs`; `MMCA.Store.CI.slnf:52`; `ProductCreate.razor:8,58`; `CartStateService.cs:232` | +| 24 | Forms, Validation & UX Safety | 2 | 4 | 8 | 8/16 | Unsaved-changes guard with current-state accessor (9 pages), double-submit blocked, all states designed, destructive confirm, abandoned-payment recovery. **↑ Maturity 3→4 (D11):** the four admin create forms' guard/dirty/validated-`MudForm`/`Required` markers are machine-enforced by the CI-gated `FormsConventionTests` (`MinimumCreateForms=4`), matching ADC on the same evidence. Client validation is MudForm-level (not full FluentValidation parity), the impl 8→9 lever. Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/FormsConventionTests.cs`; `MMCA.Store.CI.slnf:53`; `ProductCreate.razor:8,58`; `CartStateService.cs:232` | | 25 | Navigation & Information Arch | 2 | 4 | 8 | 8/16 | Bookmarkable typed routes, server-enforced `[Authorize]`/`AuthorizeView` (not UI-only), 404 handled, breadcrumbs, SSR session-cookie so deep-links/F5 don't bounce; role-guard regression is CI-gated by the three per-module `*RouteAuthorizationTests` (commit `c4adff2`), and per-actor navigation is documented in `NavigationFlow.md`. Evidence: `ProductList.razor:3`; `CatalogRoutePaths`; `Tests/Modules/{Catalog,Sales,Identity}/MMCA.Store.*.UI.Tests/*RouteAuthorizationTests.cs` in `MMCA.Store.CI.slnf:39,45,51` | | 26 | Front-End Security | 3 | 4 | 9 | 12/27 | Access token in-memory, refresh token in HttpOnly cookie (no localStorage), hardened origin-pinned CSP (no `unsafe-inline` script-src in prod, frame-ancestors none), runtime `/client-config` fetch (no secrets in bundle), `UseAuthenticatedNoStore`. Evidence (implementation hoisted to Common in the v1.96.0 Move-to-Common wave, consumed byte-identically here): Common `MMCA.Common.UI.Web/Services/ServerTokenStorageService.cs`; Common `MMCA.Common.UI/Services/Auth/WasmTokenStorageService.cs`; Common `MMCA.Common.UI.Web/Security/BlazorCspPolicyProvider.cs` | -| 27 | Internationalization (i18n) | 1 | 4 | 8 | 4/8 | **↓ Implementation 9→8 (2026-07-28), a corrected over-grant, not a regression.** No i18n file has changed since the 2026-07-17 polish wave (last touching commit `103580f7`), and every gate behind the original grant is intact and un-skipped, but two of the rubric's five criteria are demonstrably unmet in current code. (1) **Culture-aware formatting (half-closed as of v1.152.0, re-verified 2026-08-14):** `Money.ToDisplayString()` no longer hard-codes a `$` glyph; it resolves the symbol from the price's own currency (USD/EUR map, unknown codes render symbol-less; `MMCA.Common .../MMCA.Common.UI/Extensions/MoneyExtensions.cs:18-20,54-58`, Common change 2026-08-05, inside the v1.152.0 pin Store consumes). The remaining half stands: amounts still format with `CultureInfo.InvariantCulture` (`:69-70`, consumed at `CatalogBrowse.razor.cs:302`), the rubric's explicit "manual number formatting ignoring culture" red flag. This is a bypass, not missing plumbing: `UseCommonRequestLocalization` registers both supported cultures and supported UI cultures (`MMCA.Common .../WebApplicationExtensions.cs:141`). (2) **Pluralization** is the `"(s)"` workaround, not handled by the i18n mechanism (`CartDrawer.resx:20` `"{0} item(s)"`, `ShoppingCartList.es.resx:11` `"{0} articulo(s)"`). Layout tolerance is also proven on 3 public pages only, with no RTL locale. The first-pass score was 7; 8 is the adjudicated value, the conservative half of the band the adversarial pass called defensible. **The root cause is shared framework code** (`MoneyExtensions`), so the same deduction may apply to MMCA.Common and MMCA.ADC at their next re-scores. Maturity 4 unchanged: both arch gates run in the required `build-and-test` check via `MMCA.Store.CI.slnf:52` + `deploy.yml:138`, and the pseudo-loc E2E suite rides the deploy-gating `e2e-gate` (`deploy.yml:489,786`; `e2e.yml:347` runs the project unfiltered). Citation drift corrected: `UseCommonRequestLocalization` is at `UI.Web/Program.cs:140` and `MapCultureEndpoint` at `:173` (row previously cited `:107,140`); `AddErrorResources` is at `Identity.Service/Program.cs:174` (previously `:150`). **Prior (2026-07-17), retained: the layout-tolerance lever is realized on Store's own pages: `PseudoLocalizationTests` activates `qps-Ploc` via the production `/culture/set` cookie mechanism and asserts the `[!!` sentinel, the no-horizontal-overflow expression, and a per-page en-US leak probe over `/`, `/catalog`, and `/login`, riding the deploy-gating chromium `e2e-gate` (`Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PseudoLocalizationTests.cs:57`). Prior: Implementation 7→8 (2026-07-03 i18n completion sweep, ADR-027 Decision 9); Maturity 4 holds with a second gate.** Full ADR-027 adoption (supersedes ADR-011): en-US + es `.resx` pairs across all three module UIs, the API error resources, both StoreHome hosts, and the three UI modules' nav items (30 base / 30 `.es` siblings; ~130 new key pairs on the sweep), `UseCommonRequestLocalization` + `MapCultureEndpoint` (`UI.Web/Program.cs:107,140`), per-module `AddErrorResources` (`Identity.Service/Program.cs:150`), `User.PreferredCulture` + `AddUserPreferences` migration. The 2026-07-03 sweep's own deductions are closed (scoped to hard-coded literals; this claim never covered the culture-formatting and pluralization criteria corrected above on 2026-07-28): zero hard-coded snackbars (35 sites to whole-sentence page keys, including the cart/checkout/Stripe strings; raw `{ex.Message}` never surfaces), the `ErrorMessages.Success` concatenation is gone (obsoleted upstream, all 28 sites swept), 33 literal breadcrumb labels localize from `Breadcrumb.*` keys built in `OnInitialized`, nav menus localize via `NavItem.TitleResource` + new module resx pairs, and MudBlazor built-in chrome localizes via the framework's `ResxMudLocalizer` (inherited). Maturity 4, now doubly gated in CI.slnf: `TranslationCompletenessTests` (floor raised 20→25) + the NEW `LocalizedTextConventionTests` (no hard-coded snackbar/title/``/breadcrumb/`NavItem` literal can ship; `MinimumScannedFiles=40`). Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/{TranslationCompletenessTests.cs,LocalizedTextConventionTests.cs}`; `Sales.UI/.../CartDrawer.razor.cs` + `OrderDetail.razor.cs` (whole-sentence `Snackbar.*` keys); `CatalogUIModule.cs` (`TitleResource` + resx pair); verification: Store CI suite 1120/1120 green post-sweep | +| 27 | Internationalization (i18n) | 1 | 4 | 8 | 4/8 | **↓ Implementation 9→8 (2026-07-28), a corrected over-grant, not a regression.** No i18n file has changed since the 2026-07-17 polish wave (last touching commit `103580f7`), and every gate behind the original grant is intact and un-skipped, but two of the rubric's five criteria are demonstrably unmet in current code. (1) **Culture-aware formatting (half-closed as of v1.152.0, re-verified 2026-08-14):** `Money.ToDisplayString()` no longer hard-codes a `$` glyph; it resolves the symbol from the price's own currency (USD/EUR map, unknown codes render symbol-less; `MMCA.Common .../MMCA.Common.UI/Extensions/MoneyExtensions.cs:18-20,54-59` (re-anchored 2026-08-23), Common change 2026-08-05, inside the v1.160.0 pin Store consumes). The remaining half stands: amounts still format with `CultureInfo.InvariantCulture` (`:69-70`, consumed at `CatalogBrowse.razor.cs:302`), the rubric's explicit "manual number formatting ignoring culture" red flag. This is a bypass, not missing plumbing: `UseCommonRequestLocalization` registers both supported cultures and supported UI cultures (`MMCA.Common .../WebApplicationExtensions.cs:141`). (2) **Pluralization** is the `"(s)"` workaround, not handled by the i18n mechanism (`CartDrawer.resx:20` `"{0} item(s)"`, `ShoppingCartList.es.resx:11` `"{0} articulo(s)"`). Layout tolerance is also proven on 3 public pages only, with no RTL locale. The first-pass score was 7; 8 is the adjudicated value, the conservative half of the band the adversarial pass called defensible. **The root cause is shared framework code** (`MoneyExtensions`), so the same deduction may apply to MMCA.Common and MMCA.ADC at their next re-scores. Maturity 4 unchanged: both arch gates run in the required `build-and-test` check via `MMCA.Store.CI.slnf:53` + `deploy.yml:138`, and the pseudo-loc E2E suite rides the deploy-gating `e2e-gate` (`deploy.yml:489,786`; `e2e.yml:347` runs the project unfiltered). Citation drift corrected: `UseCommonRequestLocalization` is at `UI.Web/Program.cs:140` and `MapCultureEndpoint` at `:173` (row previously cited `:107,140`); `AddErrorResources` is at `Identity.Service/Program.cs:174` (previously `:150`). **Prior (2026-07-17), retained: the layout-tolerance lever is realized on Store's own pages: `PseudoLocalizationTests` activates `qps-Ploc` via the production `/culture/set` cookie mechanism and asserts the `[!!` sentinel, the no-horizontal-overflow expression, and a per-page en-US leak probe over `/`, `/catalog`, and `/login`, riding the deploy-gating chromium `e2e-gate` (`Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PseudoLocalizationTests.cs:57`). Prior: Implementation 7→8 (2026-07-03 i18n completion sweep, ADR-027 Decision 9); Maturity 4 holds with a second gate.** Full ADR-027 adoption (supersedes ADR-011): en-US + es `.resx` pairs across all three module UIs, the API error resources, both StoreHome hosts, and the three UI modules' nav items (30 base / 30 `.es` siblings; ~130 new key pairs on the sweep), `UseCommonRequestLocalization` + `MapCultureEndpoint` (`UI.Web/Program.cs:107,140`), per-module `AddErrorResources` (`Identity.Service/Program.cs:150`), `User.PreferredCulture` + `AddUserPreferences` migration. The 2026-07-03 sweep's own deductions are closed (scoped to hard-coded literals; this claim never covered the culture-formatting and pluralization criteria corrected above on 2026-07-28): zero hard-coded snackbars (35 sites to whole-sentence page keys, including the cart/checkout/Stripe strings; raw `{ex.Message}` never surfaces), the `ErrorMessages.Success` concatenation is gone (obsoleted upstream, all 28 sites swept), 33 literal breadcrumb labels localize from `Breadcrumb.*` keys built in `OnInitialized`, nav menus localize via `NavItem.TitleResource` + new module resx pairs, and MudBlazor built-in chrome localizes via the framework's `ResxMudLocalizer` (inherited). Maturity 4, now doubly gated in CI.slnf: `TranslationCompletenessTests` (floor raised 20→25) + the NEW `LocalizedTextConventionTests` (no hard-coded snackbar/title/``/breadcrumb/`NavItem` literal can ship; `MinimumScannedFiles=40`). Evidence: `Tests/Architecture/MMCA.Store.Architecture.Tests/{TranslationCompletenessTests.cs,LocalizedTextConventionTests.cs}`; `Sales.UI/.../CartDrawer.razor.cs` + `OrderDetail.razor.cs` (whole-sentence `Snackbar.*` keys); `CatalogUIModule.cs` (`TitleResource` + resx pair); verification: Store CI suite 1120/1120 green post-sweep | | 28 | Front-End Testing & Quality | 3 | 4 | 8 | 12/24 | bUnit `.UI.Tests` gate the deploy (CI.slnf), broad state-coverage component tests (loading/empty/error/edge), page-level render/parameter/event tests, shared `Common.Testing.E2E`, and the Playwright suite **gates the deploy** (chromium `e2e-gate`; firefox/webkit stay advisory on the nightly matrix). **↑ Implementation 6→8 (D7):** bUnit breadth grown to **214 `[Fact]`/`[Theory]` across 40 files** (Catalog 63 / Sales 126 / Identity 25), the full CI gate green at 1393/1393. Evidence: `Tests/Modules/{Catalog,Sales,Identity}/MMCA.Store.*.UI.Tests` (214 facts / 40 files); `deploy.yml` (`e2e-gate` in deploy `needs`); `e2e.yml` (`workflow_call` + browsers input) | | 29 | Resilience & Business Continuity | 3 | 4 | 9 | 12/27 | RTO/RPO per scenario, PITR(7d geo-redundant)+LTR, a **drilled** restore (recorded PASS 28.9 min), SLO alerts, post-deploy smoke then rollback, single-region risk explicitly accepted. **Maturity 4 legitimately held (D3):** a `dr-freshness` job is in `deploy.needs` and fails the deploy when the last successful `dr-drill` is stale, backed by a weekly `dr-drill.yml` cron; **Implementation 8→9** on the CI-gated `GracefulShutdownTests` in `MMCA.Store.Gateway.Tests` (closing the graceful-shutdown gap vs ADC). Honest note: the recorded drill narrative in `DISASTER-RECOVERY.md` dates to 2026-06-22, but the weekly `dr-drill.yml` cron has since run green (most recently 2026-07-13), so `dr-freshness` is satisfied. Evidence: `.github/workflows/deploy.yml` (`dr-freshness` in `deploy.needs`); `.github/workflows/dr-drill.yml:29` (weekly cron); `Tests/Hosts/MMCA.Store.Gateway.Tests/GracefulShutdownTests.cs`; `infra/DISASTER-RECOVERY.md:146-148` | | 30 | Compliance, Privacy & Governance | 2 | 4 | 8 | 8/16 | Real erasure path (Delete+Anonymize in one UoW, ADR-005), data export, residency + PII enforced by fitness functions, auto-stamped audit fields. Evidence: `DeleteUserHandler.cs:43-69`; `Customer.cs:20`/`User.cs:17` (`IAnonymizable`); `DataResidencyTests.cs`; `PRIVACY.md:57-85` | @@ -54,15 +54,15 @@ Front-end security is the standout (§26, impl 9): access token in-memory, refre > **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 pending a recorded manual screen-reader pass, matching ADC, D6), and §22 joined it at M3/I8 on 2026-07-28 for the mirror-image reason: strong substance, an enforcement mechanism that no longer covers what it certifies. Both are the healthy direction of the gap. The widest remaining M-over-I gap is §20 (M4/I7, residual inline styles), followed by §27 (M4/I8) where the enforcement is fully automatic but two rubric criteria are unmet in code. ## Indices -- **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 maturity 3) -- **Implementation index** = Σ(impl×weight) ÷ Σ(weight×10) = 671 ÷ 800 = **83.9%** (re-confirmed with no moves on the 2026-08-14 re-score; §15's proposed correction to 7 was user-adjudicated to hold at 8) +- **Maturity index** = Σ(maturity×weight) ÷ Σ(weight×4) = 313 ÷ 320 = **97.8%** (re-confirmed with no moves on the 2026-08-23 re-score; §12, §21, and §22 are the three categories at maturity 3) +- **Implementation index** = Σ(impl×weight) ÷ Σ(weight×10) = 671 ÷ 800 = **83.9%** (re-confirmed with no moves on the 2026-08-23 re-score; the seven FLAG categories keep their prior scores under the merged-prior rule, §5 carried unverified this cycle) - The implementation index reads directly against 100% (recalibration 2026-08-01: a 10 is awardable for an almost perfect implementation, so the former "attainable ceiling" line is retired). The denominators stay ×4 and ×10 so the trend line remains comparable to every prior cycle. - **Weaker axis:** Implementation (execution quality), by ~14 points. - **No N/A categories:** §27 joined the denominators on the 2026-07-02 cycle (ADR-027 superseded ADR-011) and remains scored. **§32 weight = 2** (default; raised to 3 only for the published framework MMCA.Common). ## Top 5 strengths 1. **Clean Architecture + DDD + CQRS depth, fitness-enforced**: §3/§4 (impl 9): inherited framework substance, guarded by the shared NetArchTest suite (23 test classes, `StoreArchitectureMap.cs:14-43`). -2. **Full microservices parity on a race-safe data layer**, §7 (impl 8) / §8 (impl 9): database-per-service + per-service outbox + versioned gRPC + MassTransit broker + RS256/JWKS, extractable modules (`infra/main.bicep:396-418` per-service DBs, `:445-474` Service Bus), with the oversell race closed by an atomic conditional UPDATE under deterministic lock ordering plus a schema CHECK backstop (`InventoryAllocationService.cs:70`, `InventoryItemConfiguration.cs:27`) and destructive migrations blocked at the merge gate (`deploy.yml:190`). _(Corrects the prior "single shared DB" assumption.)_ +2. **Full microservices parity on a race-safe data layer**, §7 (impl 8) / §8 (impl 9): database-per-service + per-service outbox + versioned gRPC + MassTransit broker + RS256/JWKS, extractable modules (`infra/main.bicep:396-418` per-service DBs, `:445-474` Service Bus), with the oversell race closed by an atomic conditional UPDATE under deterministic lock ordering plus a schema CHECK backstop (`InventoryAllocationService.cs:70`, `InventoryItemConfiguration.cs:27`) and destructive migrations blocked at the merge gate (`deploy.yml:232-274`). _(Corrects the prior "single shared DB" assumption.)_ 3. **Exemplary DevOps / operational floor**: §17 (impl 9) / §31 / §12: two-phase Bicep, OIDC + Key Vault MI, passwordless MI-SQL active in prod (2026-07-12), post-deploy smoke gate with auto-rollback (`deploy.yml:461-518`), `cost-guard`/`dr-drill`/`load-test` workflows. 4. **Reference front-end security**, §26 (impl 9): in-memory access token + HttpOnly refresh cookie (no localStorage), hardened origin-pinned CSP, runtime-config fetch (`ServerTokenStorageService.cs:10-13`, `BlazorCspPolicyProvider.cs:71`). 5. **Drilled DR + real erasure path** (§29 impl 9 / §30 impl 8): recorded 28.9-min restore (`DISASTER-RECOVERY.md:146-148`), the `dr-freshness` deploy gate + weekly cron + CI-gated `GracefulShutdownTests`, `IAnonymizable` anonymize-in-place + export + residency/PII fitness functions. @@ -70,9 +70,9 @@ Front-end security is the standout (§26, impl 9): access token in-memory, refre ## Top 5 risks 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 manual screen-reader pass and the `ACCESSIBILITY-SCREENREADER-PASS.md` results log is still empty (matching ADC's M3; re-verified 2026-08-14, placeholder row only). - _Remediation:_ record a dated NVDA/VoiceOver pass in the runbook against the running app (needs a human; cannot be done headless). _Expected:_ §21 mat 3→4. -2. **Load evidence is capacity-planning cadence, not a merge gate**: §12 (mat 3): the k6 suite is real with pass/fail thresholds and a `load-freshness` deploy gate, but the test itself runs monthly/on-demand (`load-test.yml:17-18`), so a latency regression can merge and deploy inside the freshness window. The window loosened further on 2026-07-28: `load-freshness` gained a break-glass skip (`deploy.yml:626-643`, job at `:613`, re-anchored 2026-08-14), justified but a weakening of the only deploy-chain hook §12 has. +2. **Load evidence is capacity-planning cadence, not a merge gate**: §12 (mat 3): the k6 suite is real with pass/fail thresholds and a `load-freshness` deploy gate, but the test itself runs monthly/on-demand (`load-test.yml:18` cron, `workflow_dispatch` at `:9`; re-anchored 2026-08-23), so a latency regression can merge and deploy inside the freshness window. The window loosened further on 2026-07-28: `load-freshness` gained a break-glass skip (`deploy.yml:626-643`, job at `:613`, re-anchored 2026-08-14), justified but a weakening of the only deploy-chain hook §12 has. - _Remediation:_ right-size deliberately: either accept the monthly cadence as the recorded posture (matching ADC's accepted §12 M3) or add a cheap latency-regression smoke to the merge path. _Expected:_ decision recorded either way; §12 mat 3→4 only with a gate. -3. **Design-system residual inline styles**: §20 (impl 7, the widest M-over-I gap): the token convention is CI-enforced (`BrandColorTokenTests`), and the semantic-class pattern now exists (commit `a1de5a89` converted ProductList's three attributes to `.list-search-field`/`.grid-cell-count`/`.grid-cell-actions`, `store.css:28-40`), but 31 `Style=`/`CellStyle=` occurrences remain across 14 razor files, five byte-identical to those classes (`CategoryList.razor:23,79,88`; `OrderList.razor:22`; `CustomerList.razor:81`; re-anchored 2026-08-14). +3. **Design-system residual inline styles**: §20 (impl 7, the widest M-over-I gap): the token convention is CI-enforced (`BrandColorTokenTests`), and the semantic-class pattern now exists (commit `a1de5a89` converted ProductList's three attributes to `.list-search-field`/`.grid-cell-count`/`.grid-cell-actions`, `store.css:28-40`), but 30 `Style=`/`CellStyle=` occurrences remain across 14 razor files (count corrected from 31 on 2026-08-23), five byte-identical to those classes (`CategoryList.razor:23,79,88`; `OrderList.razor:22`; `CustomerList.razor:81`; re-verified byte-identical 2026-08-23). - _Remediation:_ sweep the remaining occurrences onto the now-existing semantic classes (the sibling admin list pages first, where the classes are byte-identical drop-ins). _Expected:_ §20 impl 7→8. 4. **Client validation short of server parity**: §24 (impl 8): the four create forms' guard/dirty/validated-`MudForm` markers are CI-gated (`FormsConventionTests`, M4), but client validation stays MudForm-level rather than full FluentValidation parity with the server rules. - _Remediation:_ mirror the remaining server-only rules client-side where they do not need the DB. _Expected:_ §24 impl 8→9. diff --git a/docs-src/governance/store-RemediationBacklog.md b/docs-src/governance/store-RemediationBacklog.md index 62beff7..ebaa031 100644 --- a/docs-src/governance/store-RemediationBacklog.md +++ b/docs-src/governance/store-RemediationBacklog.md @@ -1,6 +1,6 @@ # MMCA.Store: Architecture Remediation Backlog -Derived from [`ArchitectureScorecard.md`](../governance/store-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 re-score, framework pin v1.152.0). +Derived from [`ArchitectureScorecard.md`](../governance/store-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 and 2026-08-23 full re-scores, framework pin v1.160.0). Items are ranked on **both scorecard axes**, one band per axis (two-axis policy adopted 2026-07-28, replacing the previous "or a notable implementation gap" wording, which had no number behind it and so never scheduled anything): @@ -23,7 +23,7 @@ The former single biggest maturity lever: #28 cleared 2026-07-03; #22 cleared on ### Maturity band (maturity < 4, ranked by priority) -Computed from the 2026-07-28 re-score, re-confirmed unchanged on the 2026-08-14 re-score (pin v1.152.0): **3 categories, 7 gap points.** +Computed from the 2026-07-28 re-score, re-confirmed unchanged on the 2026-08-14 re-score (pin v1.152.0) and again on the 2026-08-23 re-score (pin v1.160.0, `Directory.Packages.props:9-10`, HEAD `063c90dd`): **3 categories, 7 gap points.** | priority | # | Category | w | Mat | Recorded lever | |---|---|---|---|---|---| @@ -33,7 +33,8 @@ Computed from the 2026-07-28 re-score, re-confirmed unchanged on the 2026-08-14 Ties break by priority desc, then weight desc, then category asc. -- [~] **#21 Accessibility, maturity 4 corrected back to 3 (2026-07-11, drift plan D6); impl 7 → 8 DONE.** The axe + Playwright suite gates the deploy (`e2e-gate`, chromium, `workflow_call` into `e2e.yml`, in `deploy.yml`'s `needs`), and the axe scans broadened 10 → 22 pages (public, shopper, and Catalog/Sales/Identity admin surfaces), lifting impl 7 → 8. The prior maturity-4 was an over-claim: the rubric pairs axe-in-CI with a recorded manual screen-reader pass, so honest maturity is 3, matching ADC on the same rubric. A new `ACCESSIBILITY-SCREENREADER-PASS.md` runbook shipped (centralized as `store-ACCESSIBILITY-SCREENREADER-PASS.md` in Website `docs-src/guides/` since 2026-07-20), but its results log is still empty (re-verified 2026-08-14: placeholder row only; the axe suite meanwhile grew 22 → 23 scans with a dark-palette home scan, `AccessibilityTests.cs:327-340`). _Maturity 3 → 4 lever:_ record a dated manual SR pass in the new runbook (needs a human + NVDA/VoiceOver against the running Aspire app; cannot be done headless). +- [~] **#21 Accessibility, maturity 4 corrected back to 3 (2026-07-11, drift plan D6); impl 7 → 8 DONE.** The axe + Playwright suite gates the deploy (`e2e-gate`, chromium, `workflow_call` into `e2e.yml`, in `deploy.yml`'s `needs`; **qualifier 2026-08-23: the gate is UI-scoped and skippable, see the TD below**), and the axe scans broadened 10 → 22 pages (public, shopper, and Catalog/Sales/Identity admin surfaces), lifting impl 7 → 8. The prior maturity-4 was an over-claim: the rubric pairs axe-in-CI with a recorded manual screen-reader pass, so honest maturity is 3, matching ADC on the same rubric. A new `ACCESSIBILITY-SCREENREADER-PASS.md` runbook shipped (centralized as `store-ACCESSIBILITY-SCREENREADER-PASS.md` in Website `docs-src/guides/` since 2026-07-20), but its results log is still empty (re-verified 2026-08-14: placeholder row only; the axe suite meanwhile grew 22 → 23 scans with a dark-palette home scan, `AccessibilityTests.cs:327-340`). _Maturity 3 → 4 lever:_ record a dated manual SR pass in the new runbook (needs a human + NVDA/VoiceOver against the running Aspire app; cannot be done headless). +- [ ] **TD · The deploy-gating a11y/E2E run is UI-scoped, so a backend-only merge deploys with no axe or Playwright run (found 2026-08-23; affects #21, #22, #28).** The `e2e-gate` job runs only when `needs.changes.outputs.ui == 'true'` (`deploy.yml:544`, rationale comment `:539-543`, the 2026-07-29 Actions-minute saving), and the `deploy` job deliberately tolerates a SKIPPED `e2e-gate`: it is the one gate allowed to be `success` OR `skipped` while every other gate must be `success` (`deploy.yml:876-880`, with the `if: always()` guard at `:881-884`). On a backend-only merge, therefore, the 23 WCAG 2.1 AA axe scans (`Tests/E2E/MMCA.Store.E2E.Tests/Workflows/AccessibilityTests.cs`, dark-palette scan at `:327-340`) and the Playwright workflow suite do not run at all, and the chromium-only blind window priced under Deliberate / accepted becomes 100% for all three engines. This ledger previously asserted the axe suite gates the deploy unconditionally; that claim is now qualified where it appears. _Lever:_ either make `e2e-gate` unconditional on push, or record the UI-scoping as a deliberate accepted trade-off alongside the chromium-only entry (it is currently neither enforced nor recorded). - [x] **#28 Front-End Testing, maturity 3 → 4 DONE (2026-07-03) / impl 6 → 8 DONE (2026-07-11, drift plan D7).** The E2E + axe deploy gate shipped with #21; bUnit breadth grown to 214 `[Fact]`/`[Theory]` across 40 files (Catalog 63 / Sales 126 / Identity 25) with loading/empty/error/edge state coverage, the full CI gate green at 1393/1393. - [~] **#22 Responsive & Cross-Browser, maturity 3 → 4 GRANTED on the 2026-07-17 re-score; basis went STALE the next day (drift recorded on the 2026-07-23 verification pass); maturity REOPENED 4 → 3 on the 2026-07-28 re-score.** The 2026-07-18 Actions-minute reduction (commit `777348ec`, mirroring ADC's) cut the deploy `e2e-gate` to **chromium only** (`deploy.yml:494`, `browsers: '["chromium"]'`, rationale comment `:483-488`), so firefox/webkit now run only on the scheduled matrix (re-anchored 2026-08-14: `e2e.yml:143` keeps them `continue-on-error`, `:133-135` selects the engine; since 2026-07-29 the schedule runs ONE alternating engine per week, Mon firefox / Thu webkit per the crons at `:37-47`, so each engine is blind for 7 days), and the granted basis, "all three engines the gate invokes CAN fail a deploy", no longer holds. There is no cross-browser freshness job in `deploy.needs` to bound the blind window either, so cross-engine verification is convention-enforced (Consistent=3), not automatic. Scorecard §22 is now **M3/I8**, matching ADC's twenty-second cycle on identical evidence; the proposed Implementation 8→7 was adversarially REJECTED (a CI-cadence change is not a substance regression). _Maturity 3 → 4 lever:_ add a `cross-browser-freshness` job to `deploy.needs` on the `dr-freshness`/`load-freshness` pattern, which bounds staleness without paying for three engines per deploy, or promote firefox/webkit back into the gate. The chromium-only cost trade-off is recorded under Deliberate / accepted so the choice stays conscious rather than silently low. Grant provenance with anchors as of 2026-07-17: `e2e.yml:76` scoped `continue-on-error` to scheduled non-chromium runs (now `:117`), `deploy.yml:315` invoked all three engines (gate now `:417-423`), `deploy.yml:429` put `e2e-gate` in deploy `needs` (now `:634`). History of the reopen-and-fix below. The wave-5 change passed `browsers: ["chromium", "firefox", "webkit"]` into the `e2e-gate` call, so all three engines RUN in the gate, but the non-chromium legs cannot FAIL it: `e2e.yml:71` still sets `continue-on-error: ${{ matrix.browser != 'chromium' }}`, and `deploy.yml:433`'s own inline comment describes e2e-gate as chromium-only. The 2026-07-16 re-score held maturity 3 on exactly this evidence and the candidacy was declined. The green-soak history (2026-07-09 through 2026-07-11, plus the `d057afc` three-engine catch) still stands as soak evidence. _Maturity 3 → 4 lever:_ remove the `continue-on-error` conditional for the gate-invoked firefox/webkit legs (or gate them behind their own required jobs) once the soak is judged sufficient; nightly-matrix legs may stay advisory. **Lever SHIPPED same day (2026-07-16):** `continue-on-error` is now `github.event_name == 'schedule' && matrix.browser != 'chromium'`, so all three engines the gate invokes (`deploy.yml:315`) CAN fail a deploy while nightly non-chromium legs stay advisory flake alarms; the stale chromium-only comments in `e2e.yml`/`deploy.yml` corrected. Soak judged sufficient: job-level green nightly matrices 2026-07-09 through 2026-07-16, with the sole 2026-07-12 red being the all-three-engines product defect fixed in `d057afc` (a true positive, not flake). Maturity 3 → 4 candidacy recorded for the next re-score. @@ -44,7 +45,8 @@ Ties break by priority desc, then weight desc, then category asc. 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 rule, which is why maturity reached 98.4% while implementation sat at 83.6%. Computed from the current scorecard -(2026-07-28 full re-score, re-confirmed unchanged on the 2026-08-14 re-score, pin v1.152.0): +(2026-07-28 full re-score, re-confirmed unchanged on the 2026-08-14 re-score and again on the +2026-08-23 re-score, pin v1.160.0): **21 categories, 49 gap points**, the largest of the three repos. (The former "attainable ceiling" comparison line is retired per the 2026-08-01 recalibration; the index reads against 100%.) Levers are cited only where this ledger or the scorecard already records @@ -52,7 +54,7 @@ one; an unnamed lever is named at the next re-score, never invented here. | implPriority | # | Category | w | Impl | Recorded lever | |---|---|---|---|---|---| -| 4 | #20 | Design System & UI Consistency | 2 | 7 | **OPEN, recorded below, re-anchored 2026-08-14:** ProductList's three cited attributes are converted (commit `a1de5a89`, semantic classes in `store.css:28-40`), but 31 `Style=`/`CellStyle=` occurrences remain across 14 razor files, five byte-identical to the new classes (`CategoryList.razor:23,79,88`; `OrderList.razor:22`; `CustomerList.razor:81`); the sweep onto the now-existing classes is the lever | +| 4 | #20 | Design System & UI Consistency | 2 | 7 | **OPEN, recorded below, re-verified 2026-08-23:** ProductList's three cited attributes are converted (commit `a1de5a89`, semantic classes in `store.css:28-40`), but 30 `Style=`/`CellStyle=` occurrences remain across 14 razor files (count corrected from 31 on 2026-08-23, a counting fix: zero razor files changed), five byte-identical to the new classes (`CategoryList.razor:23,79,88`; `OrderList.razor:22`; `CustomerList.razor:81`); the sweep onto the now-existing classes is the lever, and the classes are mirrored in the MAUI head's `app.css:38,42,47`, a second sweep target | | 3 | #7 | Microservices Readiness | 3 | 8 | not yet identified (all per-service DBs on one physical server is a recorded accepted cap) | | 3 | #11 | Security | 3 | 8 | **OPEN, recorded below (lever named 2026-08-18 by the ADR audit):** Store's identity seeder has no environment gate, so weak plaintext seed accounts are created in every environment, production included | | 3 | #18 | UI Architecture & Components | 3 | 8 | residual inline-style logic (the basis on which the 2026-07-16 impl bump to 9 was rejected) | @@ -63,7 +65,7 @@ one; an unnamed lever is named at the next re-score, never invented here. | 2 | #6 | CQRS & Event-Driven | 2 | 8 | not yet identified | | 2 | #10 | Cross-Cutting Concerns | 2 | 8 | not yet identified | | 2 | #12 | Performance & Scalability | 2 | 8 | not yet identified | -| 2 | #15 | Best Practices & Code Quality | 2 | 8 | lever named 2026-08-14 (the verify pass proposed I7 on these; user-adjudicated hold at 8): (1) remove the expired GHSA-2m69-gcr7-jv3q audit suppression (`Directory.Build.props:54`; its removal condition at `:45-52` is met under the v1.152.0 pin), (2) document or remove the three unjustified global `NoWarn` codes (`:26`) and the uncommented duplicates in five test csprojs, (3) bring the MAUI head under CI enforcement (it is in neither `.slnf`, so its analyzers/TWAE/audit never run in CI) | +| 2 | #15 | Best Practices & Code Quality | 2 | 8 | lever named 2026-08-14 (the verify pass proposed I7 on these; user-adjudicated hold at 8): (1) remove the expired GHSA-2m69-gcr7-jv3q audit suppression (`Directory.Build.props:54`; its removal condition at `:45-52` is still met under the v1.160.0 pin, re-verified 2026-08-23), (2) document or remove the three unjustified global `NoWarn` codes (`:26`; the list gained a fourth code S8970, which IS documented at `:22-25`, so the three-unjustified count still holds) and the uncommented duplicates in five test csprojs, (3) bring the MAUI head under CI enforcement (it is in neither `.slnf`, so its analyzers/TWAE/audit never run in CI; re-verified 2026-08-23 against `MMCA.Store.CI.slnf:5-54`) | | 2 | #16 | Maintainability & Evolvability | 2 | 8 | not yet identified | | 2 | #22 | Responsive & Cross-Browser | 2 | 8 | not yet identified (the chromium-only gate is the maturity question, tracked above; the 2026-07-28 proposal to drop impl to 7 was adversarially rejected) | | 2 | #23 | Front-End Performance | 2 | 8 | not yet identified | @@ -72,7 +74,7 @@ one; an unnamed lever is named at the next re-score, never invented here. | 2 | #30 | Compliance, Privacy & Governance | 2 | 8 | not yet identified | | 2 | #31 | Cost Efficiency / FinOps | 2 | 8 | not yet identified | | 2 | #33 | Developer Experience & Inner Loop | 2 | 8 | the §33 I8→9 candidacy recorded 2026-07-16 on the Service Bus emulator tier was NOT granted on 2026-07-28 (the tier is nightly and in neither `.slnf`; recency gate re-anchored 2026-08-14 to the `cross-service-freshness` job at `deploy.yml:668`, in deploy `needs` at `:862`); see Deliberate / accepted | -| 1 | #27 | Internationalization (i18n) | 1 | 8 | **OPEN, half-closed 2026-08-14:** the `$`-glyph half is FIXED in Common (per-currency symbol resolution, `MoneyExtensions.cs:18-20,54-58`, Common change 2026-08-05, inside the v1.152.0 pin). Remaining: amounts still format with `CultureInfo.InvariantCulture` (`:69-70`) and pluralization stays the `"{0} item(s)"` / `"{0} articulo(s)"` workaround (`CartDrawer.resx:20`, `ShoppingCartList.es.resx:11`). The fix lands in MMCA.Common, so it is `[C→A]`, not Store-local | +| 1 | #27 | Internationalization (i18n) | 1 | 8 | **OPEN, half-closed 2026-08-14:** the `$`-glyph half is FIXED in Common (per-currency symbol resolution, `MoneyExtensions.cs:18-20,54-59`, re-anchored 2026-08-23; Common change 2026-08-05, inside the v1.160.0 pin). Remaining: amounts still format with `CultureInfo.InvariantCulture` (`:69-70`) and pluralization stays the `"{0} item(s)"` / `"{0} articulo(s)"` workaround (`CartDrawer.resx:20`, `ShoppingCartList.es.resx:11`). The fix lands in MMCA.Common, so it is `[C→A]`, not Store-local (2026-08-23 caveat: re-verified against Common source at HEAD, not the published v1.160.0 package body) | - [ ] **#11 Security, OPEN (found 2026-08-18 by the ADR audit): the identity seed accounts have no environment gate.** The framework leaves the choice to the app: `IdentityModuleDbSeederBase.ShouldSeed` defaults to `true` (`IdentityModuleDbSeederBase.cs:57`), directly under a security notice saying the seed credentials are deliberately weak plaintext values and that deployed environments must disable seeding (`:31-35`). ADC overrides it behind `Seeding:IncludeSampleUsers`, default false, so a production host that sets nothing seeds no accounts (`MMCA.ADC.Identity.API/IdentityModuleSeeder.cs:22-30`). **Store does not override it:** its `SeedAsync` constructs and runs the DB seeder unconditionally (`MMCA.Store.Identity.API/IdentityModuleSeeder.cs:18-23`), and ADR-030 records that the startup owner runs every module seeder in all environments. The seeded set is an admin account plus a customer account with weak plaintext passwords, one of them a real personal address (`MMCA.Store.Identity.Infrastructure/.../IdentityModuleDbSeeder.cs:16,29-31`); the only thing between them and production is the per-account `EmailExistsAsync` probe (`:37-42`), which skips an account that is already there but does nothing to stop the first seed. _Lever:_ mirror ADC (override `ShouldSeed` on a `Seeding:IncludeSampleUsers` flag defaulting to false), then check the deployed Store Identity database for already-seeded accounts and rotate or remove them. Not a re-score on its own: it is recorded here so the #11 lever stops reading "not yet identified". - [x] **#14 Testability, impl 6 → 9. DONE (2026-07-01 wave, re-scored 2026-07-02).** The floor step now measures Store's own code (reportgenerator `+MMCA.Store.*;-*.Tests`) at floor 42.0 with ~46% actual (`deploy.yml:82-86`), the ADC-parity self-filtered gate; bUnit page-level breadth reached ADC parity (8 files). **Unit-coverage program (2026-07-05):** floor ratcheted 42.0 to 51.6 (~54% measured) after Stripe money-path, Sales UI, and GDPR-handler unit tests. **Integration-coverage expansion (2026-07-06):** ~100 new integration tests over real SQL closed the money-path gaps (Stripe webhook signature contract, order state machine, deliberate 404-not-403 order ownership), GDPR erasure/export end-state, refresh-token rotation, preferences, cross-service `ProductVariantChanged` consistency, checkout concurrency, contract guards (OpenAPI + RFC 9457) for Sales/Identity, and the ProductImages/PUT leftovers; `[Idempotent]` was wired onto the Sales money POSTs with a replay contract test. The Catalog+Sales fixtures were consolidated onto `SqlServerIntegrationTestFixtureBase`. **Cross-service broker tier (2026-07-11, drift plan D5):** a non-gating nightly `MMCA.Store.CrossService.IntegrationTests` (Testcontainers RabbitMQ + SQL, `cross-service-tests.yml`) now exercises the genuine outbox-to-broker-to-consumer round-trip (Catalog `ProductVariantChanged` to Sales' zero-stock `InventoryItem`); the first run needs a manual `workflow_dispatch` (done 2026-07-11, green). **Gated by recency same day (remediation wave 6):** a `cross-service-freshness` job in `deploy.needs` fails a deploy when the latest successful nightly is older than 3 days (mirrors ADC TD-02; the Testcontainers workflow itself stays out of the deploy chain). **Deliberately skipped:** dedicated rate-limit fixtures (the WAFs neutralize the limiter; a tight-limit variant is low value for the volume, revisit only if abuse is observed). @@ -98,25 +100,26 @@ Four reviewed product defects fixed in one wave; every behavior change flipped i ## Deliberate / accepted (record the choice; don't silently leave low) - **ADR-042 device capability abstraction (latent, drift plan D8).** The core extension point is converged (Store wires browser + MAUI capabilities via `UseMauiDeviceCapabilities`/`AddBrowserDeviceCapabilities` and renders the shared `OfflineBanner`), but Store consumes no further capability in its own product UI: no `ExternalLink`, no `DeviceUIModule`/`DeepLinkListener`, no app actions. Because no Store product page carries an external anchor today, the WebView dead-end risk is LATENT, not a live defect, so there is nothing to convert now. Adopt `ExternalLink` on any future Store product page that grows an external anchor, and register a Store `IUIModule` with `DeepLinkListener` (the way ADC's `DeviceUIModule` does) if a Store MAUI feature surface is ever wanted. The framework side is complete in Common (18 capability contracts + the `MMCA.Common.UI.Maui` package), so this is consumer-side only, not `[C→A]`. -- ~~**#5 Vertical Slice (M3)**~~ RESOLVED (2026-07-17 re-score): §5 is **M4/I8**, granted on the CI-gated `SliceCohesionTests` (sealed subclass of the shared non-vacuous base, in the deploy-gating `MMCA.Store.CI.slnf:52`), the identical gate ADC credits at M4/I8. The layered-by-project hybrid remains a deliberate design choice, now correctly recorded as an implementation-axis cap (holds impl at 8), not a maturity deduction. Moved to the protect list. -- ~~**#27 Internationalization N/A**~~ RETIRED (2026-07-02): ADR-027 superseded ADR-011; Store ships full en-US + es localization with the CI-gated `TranslationCompletenessTests`. §27 is scored and included in the indices. **Updated 2026-07-03 (i18n completion sweep): §27 is M4/I8** with zero residual hard-coded literals (35 snackbars incl. cart/checkout/Stripe, 33 breadcrumb labels, nav items, both StoreHome hosts), a second CI gate (`LocalizedTextConventionTests`), the completeness floor raised 20→25, and MudBlazor chrome localized via the framework's `ResxMudLocalizer`. Impl 8→9 lever DONE (2026-07-11, remediation wave 6): `Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PseudoLocalizationTests.cs` extends the pseudo-loc text-expansion evidence to Store's own public pages (`/`, `/catalog`, `/login`): activates `qps-Ploc` via the production `/culture/set` cookie mechanism (the circuit handshake carries cookies, not query strings), asserts the `[!!` sentinel, Common's exact no-horizontal-overflow expression, and a per-page resx-owned en-US leak probe, plus a default-culture sentinel guard. No host/AppHost change needed; rides the deploy-gating chromium e2e-gate (first genuine run in CI). §27 Implementation 8→9 candidacy recorded for the next re-score. **Candidacy GRANTED on the 2026-07-17 re-score (user-adjudicated: the lever's test is real and rides the deploy-gating chromium e2e-gate): §27 was M4/I9.** **REVERSED on the 2026-07-28 re-score: §27 is M4/I8 and is back in the implementation band.** Not a regression, and not a withdrawal of the lever: `PseudoLocalizationTests.cs:64,100` is intact and un-skipped and both arch gates still run in `MMCA.Store.CI.slnf:52`. The I9 was an over-grant because it scored the lever rather than the category: two of the rubric's five criteria are unmet in current code, namely culture-aware number formatting (`Money.ToDisplayString()` hard-codes a `$` glyph and formats with `CultureInfo.InvariantCulture`, `MMCA.Common .../MoneyExtensions.cs:20,41`, an explicit rubric red flag) and mechanism-driven pluralization (the `"{0} item(s)"` / `"{0} articulo(s)"` workaround, `CartDrawer.resx:20`, `ShoppingCartList.es.resx:11`). The first pass proposed 7; 8 was adjudicated. **Both defects live in shared MMCA.Common code**, so the fix is `[C→A]` and the same deduction may apply to Common's and ADC's §27 at their next re-scores. **Update 2026-08-14: the `$`-glyph half is FIXED** (Common resolves the symbol from the price's own currency since 2026-08-05, `MoneyExtensions.cs:18-20,54-58`, inside the v1.152.0 pin Store consumes); the `CultureInfo.InvariantCulture` amount formatting (`:69-70`) and the pluralization workaround remain, so §27 holds I8 and stays in the implementation band. +- ~~**#5 Vertical Slice (M3)**~~ RESOLVED (2026-07-17 re-score): §5 is **M4/I8**, granted on the CI-gated `SliceCohesionTests` (sealed subclass of the shared non-vacuous base, in the deploy-gating `MMCA.Store.CI.slnf:53`), the identical gate ADC credits at M4/I8. The layered-by-project hybrid remains a deliberate design choice, now correctly recorded as an implementation-axis cap (holds impl at 8), not a maturity deduction. Moved to the protect list. +- ~~**#27 Internationalization N/A**~~ RETIRED (2026-07-02): ADR-027 superseded ADR-011; Store ships full en-US + es localization with the CI-gated `TranslationCompletenessTests`. §27 is scored and included in the indices. **Updated 2026-07-03 (i18n completion sweep): §27 is M4/I8** with zero residual hard-coded literals (35 snackbars incl. cart/checkout/Stripe, 33 breadcrumb labels, nav items, both StoreHome hosts), a second CI gate (`LocalizedTextConventionTests`), the completeness floor raised 20→25, and MudBlazor chrome localized via the framework's `ResxMudLocalizer`. Impl 8→9 lever DONE (2026-07-11, remediation wave 6): `Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PseudoLocalizationTests.cs` extends the pseudo-loc text-expansion evidence to Store's own public pages (`/`, `/catalog`, `/login`): activates `qps-Ploc` via the production `/culture/set` cookie mechanism (the circuit handshake carries cookies, not query strings), asserts the `[!!` sentinel, Common's exact no-horizontal-overflow expression, and a per-page resx-owned en-US leak probe, plus a default-culture sentinel guard. No host/AppHost change needed; rides the deploy-gating chromium e2e-gate (first genuine run in CI). §27 Implementation 8→9 candidacy recorded for the next re-score. **Candidacy GRANTED on the 2026-07-17 re-score (user-adjudicated: the lever's test is real and rides the deploy-gating chromium e2e-gate): §27 was M4/I9.** **REVERSED on the 2026-07-28 re-score: §27 is M4/I8 and is back in the implementation band.** Not a regression, and not a withdrawal of the lever: `PseudoLocalizationTests.cs:64,100` is intact and un-skipped and both arch gates still run in `MMCA.Store.CI.slnf:53`. The I9 was an over-grant because it scored the lever rather than the category: two of the rubric's five criteria are unmet in current code, namely culture-aware number formatting (`Money.ToDisplayString()` hard-codes a `$` glyph and formats with `CultureInfo.InvariantCulture`, `MMCA.Common .../MoneyExtensions.cs:20,41`, an explicit rubric red flag) and mechanism-driven pluralization (the `"{0} item(s)"` / `"{0} articulo(s)"` workaround, `CartDrawer.resx:20`, `ShoppingCartList.es.resx:11`). The first pass proposed 7; 8 was adjudicated. **Both defects live in shared MMCA.Common code**, so the fix is `[C→A]` and the same deduction may apply to Common's and ADC's §27 at their next re-scores. **Update 2026-08-14: the `$`-glyph half is FIXED** (Common resolves the symbol from the price's own currency since 2026-08-05, `MoneyExtensions.cs:18-20,54-59`, re-anchored 2026-08-23, inside the v1.160.0 pin Store consumes); the `CultureInfo.InvariantCulture` amount formatting (`:69-70`) and the pluralization workaround remain, so §27 holds I8 and stays in the implementation band (re-verified 2026-08-23: `CartDrawer.resx:20`, `ShoppingCartList.es.resx:11`). - **Single-region deployment**: accepted in `infra/DISASTER-RECOVERY.md` (real load doesn't justify multi-region cost). - **All per-service DBs on one physical SQL server**: logical isolation complete; shared server for cost (minor §7/§8). - **2026-07-16 re-verification note:** #9 (M4/I9) and #32 came back FLAG on the full re-score (first-pass scorers proposed regressions that the adversarial verify pass disproved against committed evidence). #9 stands at M4/I9. **#32 was re-adjudicated the same day by the drift-analysis fold:** a capability-level ADC comparison (adversarially verified) found no mechanism behind ADC's I9 that Store lacks, so §32 is now M4/I9; the earlier FLAG had reasoned from stale scorecard text (including a stale 49 lock-file count, actual 55). - **ADR-043 adoption (mobile deep links / app association / native OAuth callback): recorded DEFERRED (2026-07-16 drift fold).** The drift analysis lands this in Store, but adoption is feature-scale (Store-scheme deep links, iOS/Android manifest entries, associated domains) and rides the same trigger as the recorded ADR-042 latency: adopt when a Store MAUI surface is actively wanted. Not scheduled; revisit with the ADR-042 entry above. - **#33 broker-parity tier SHIPPED (2026-07-16, mirrors ADC):** `Tests/Integration/MMCA.Store.ServiceBusEmulator.IntegrationTests` runs MassTransit v8 against the official Service Bus emulator (pinned 2.0.1) with the real `ProductVariantChanged` contract, proving admin-plane topology creation + the AMQP round-trip nightly in `cross-service-tests.yml` (new job, same `cross-service-freshness` deploy gate). Closes the local-RabbitMQ vs prod-Service-Bus red flag with automation instead of documentation; §33 I8→9 candidacy recorded for the next re-score. **Candidacy NOT granted on the 2026-07-28 re-score:** the tier still exists but is nightly and non-gating, and its project (`Tests/Integration/MMCA.Store.ServiceBusEmulator.IntegrationTests`) is in neither solution filter, reaching the deploy chain only through the recency check (re-anchored 2026-08-14: the `cross-service-freshness` job at `deploy.yml:668`, in deploy `needs` at `:862`). §33 holds M4/I8 and stays in the implementation band. - **ADR-044 adoption (native push, third notification channel): recorded DEFERRED (2026-07-16 drift fold).** Store has no user-notification pipeline at all (no ADR-024 inbox, no SignalR channel), so ADR-044 adoption means adopting the whole notification stack first: a product decision, not remediation. Record here so the gap is conscious; schedule only if Store wants user notifications. -- **Chromium-only deploy E2E gate: accepted CI-cost trade-off (recorded 2026-07-28).** The 2026-07-18 Actions-minute reduction cut the deploy-gating `e2e-gate` to a single engine (`deploy.yml:483-495`) and left firefox/webkit on the Mon/Thu advisory matrix (`e2e.yml:124,131`). The saving is real and the decision stands; what was missing was the record, so the ledger predicted a reopen at line 26 and in the protect list without anyone having decided anything. **Recording the trade-off does not restore the score:** the rubric's maturity 4 is "enforced automatically", and a convention-enforced check is a 3, so §22 is scored M3 and sits in the maturity band with a named lever. The two are complementary: the score reflects what CI enforces, this entry reflects why. Revisit if a webkit-only or firefox-only defect ever reaches production, which is the risk being priced. **Cadence update (verified 2026-08-14):** since 2026-07-29 the scheduled matrix runs ONE alternating engine per week (Mon firefox, Thu webkit; crons `e2e.yml:37-47`, engine selection `:133-135`), not both engines twice weekly, so the priced blind window per engine is now 7 days, wider than originally recorded. +- **Chromium-only deploy E2E gate: accepted CI-cost trade-off (recorded 2026-07-28).** The 2026-07-18 Actions-minute reduction cut the deploy-gating `e2e-gate` to a single engine (re-anchored 2026-08-23: job at `deploy.yml:537`, rationale `:539-543`, `browsers: '["chromium"]'` at `:547`) and left firefox/webkit on the Mon/Thu advisory matrix (re-anchored 2026-08-23: engine selection `e2e.yml:133-135`, `continue-on-error` at `:143`). The saving is real and the decision stands; what was missing was the record, so the ledger predicted a reopen at line 26 and in the protect list without anyone having decided anything. **Recording the trade-off does not restore the score:** the rubric's maturity 4 is "enforced automatically", and a convention-enforced check is a 3, so §22 is scored M3 and sits in the maturity band with a named lever. The two are complementary: the score reflects what CI enforces, this entry reflects why. Revisit if a webkit-only or firefox-only defect ever reaches production, which is the risk being priced. **Cadence update (verified 2026-08-14, re-confirmed 2026-08-23):** since 2026-07-29 the scheduled matrix runs ONE alternating engine per week (Mon firefox, Thu webkit; crons `e2e.yml:46-47`, engine selection `:133-135`), not both engines twice weekly, so the priced blind window per engine is now 7 days, wider than originally recorded. **Content gap noted 2026-08-23: this entry prices the ENGINE dimension only.** The gate is also UI-SCOPED (`deploy.yml:544`) and a skipped `e2e-gate` does not block the deploy (`deploy.yml:876-880`), so on a backend-only merge the priced blind window is 100% for all three engines; that second, unrecorded hole is tracked as the TD under #21 in the maturity-band section, pending a decision to either enforce or accept it. - **2026-07-28 FLAG carry-forward (#19, #30).** Both categories came back FLAG on the full re-score: first-pass proposals (#19 Implementation 8→9; #30 M4/I8→M3/I7) that the adversarial verify pass rejected against evidence re-read at HEAD `8d4af68c`. Both hold their prior **M4/I8** and their implementation-band rows are unchanged, still with no named lever. #19's rejection was specific: no §19 substance landed since the prior pin (the UI diff is culture-invariant string mechanics, payment-poll cadence tuning, and batched-lookup round-trip cuts, all §12/§23 work), and a minor red flag persists in a publicly settable `IsDrawerOpen` on the shared scoped state service, mutated directly by the component outside the notify path. - **2026-08-14 FLAG carry-forward (#5, #15, #17, #19, #20, #21).** Six categories came back FLAG on the full re-score, every one an adversarial rejection of a proposed first-pass uplift against evidence re-read at HEAD `9571a963`, none a found regression: #5 held I8 (horizontal folders inside module Application layers; generic-CRUD slices on shared framework handlers; only three bespoke query types), #17 held I9 (no pre-prod Bicep validation, SQL public network access, prod-only environment), #19 held I8 for the second consecutive cycle (`IsDrawerOpen`, now a named lever in its band row), #20 held I7 (the ProductList conversion covered ~3 of 34 occurrences), #21 held M3/I8 (placeholder SR log; one added dark-mode scan is a Strong-band increment). **#15 is the one adjudicated case:** the verify pass proposed a correction to I7 on three suppression-hygiene gaps (expired GHSA-2m69-gcr7-jv3q suppression, undocumented `NoWarn` codes, MAUI head outside CI); the user adjudicated a hold at the prior I8, and the three gaps are recorded as #15's named lever in the band table above. +- **2026-08-23 FLAG carry-forward (#5, #6, #7, #9, #12, #20, #31).** Seven categories returned FLAG on this re-score; none is a found regression and every one keeps its prior score under the merged-prior rule. #9 holds M4/I9 and stays on the protect list (a proposed downgrade was rejected: the contract-guard evidence base grew to seven files plus the frozen gRPC proto contract; only the scorecard row's narration was stale, now corrected). #12 holds M3/I8 and #20 holds M4/I7, both with their band rows and levers unchanged (re-verified this run: `deploy.yml:613`/`:626-643` for #12, the 30 residual `Style=`/`CellStyle=` occurrences for #20). #6, #7, and #31 hold I8 with no lever-bearing evidence surfaced, so their band rows keep "not yet identified". **#5 is the one special case: the scorer returned NO numbers at all (maturity null / implementation null), so its M4/I8 is carried forward unverified this cycle rather than re-established; treat #5 as owing a fresh read at the next re-score, not as re-confirmed.** ## 🟠 Below-maturity-4 tracking (inclusion policy: categories scoring < 4 maturity) 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 · maturity 3 → 4 (weight 3). DONE (2026-07-11, remediation wave 2).** The §19 fitness gate now runs in the CI.slnf arch tier: `StateManagementConventionTests` (sealed subclass of the shared v1.115.0 `StateManagementConventionTestsBase`) reflects over the three module UI assemblies (now registered as `Layer.Ui` in `StoreArchitectureMap`) failing the build on any mutable static field or settable static property, plus a source scan forbidding singleton `*StateService`/`*StateContainer` registrations. Verified non-vacuous: a seeded mutable static in Catalog.UI failed the gate with the exact offender name, then green after removal. **Maturity 4 GRANTED on the 2026-07-16 re-score** (two-pass, adversarially verified; the proposed impl bump to 9 was rejected as an enforcement gain, not substance). Scorecard §19 is M4/I8; moved to the protect list. - [x] **#18 · UI Architecture & Components · maturity 3 → 4 (weight 3). DONE (2026-07-11, remediation wave 2).** The §18 fitness gate now runs in the CI.slnf arch tier: `UIArchitectureConventionTests` (sealed subclass of the shared v1.115.0 `UIArchitectureConventionTestsBase`) caps every `*.razor.cs` under Source/ at 400 lines and inline `@code` blocks at 120 lines. Conformance shipped with the gate: `OrderDetail.razor.cs` 500 → 361 (extracted `OrderSummaryPanel` + `OrderLinesPanel`) and `ProductDetail.razor.cs` 491 → 340 (extracted `ProductVariantsPanel`), markup moved verbatim (DOM identical for the E2E selectors), all bUnit suites green. Verified non-vacuous via a seeded 402-line file. **Maturity 4 GRANTED on the 2026-07-16 re-score** (two-pass, adversarially verified; the proposed impl bump to 9 was rejected, impl holds 8 on the residual inline-style logic). Scorecard §18 is M4/I8; moved to the protect list. -- [~] **#12 · Performance & Scalability · maturity 3 → 4 (weight 2). LEVER STILL OPEN** (marker corrected 2026-07-28: the `[x]` contradicted this entry's own closing text and #12 sits in both ranked bands; the wave-3 work below did ship, but the maturity candidacy it recorded was declined and has been declined again since). Wave-3 delivery (2026-07-11): Both halves of the lever are now enforced deploy preconditions: (a) a `load-freshness` job in `deploy.yml`'s `needs` fails the deploy when the latest successful monthly `load-test.yml` run is older than 35 days (the dr-freshness pattern; latest run 2026-07-01, green), and (b) the WebVitals budgets were tightened from catastrophic-only (LCP 8000) to the Core Web Vitals "good" band (LCP 2500 / FCP 1800 / TTFB 800 / CLS 0.1 / INP 500), calibrated against measured CI maxima (LCP 172ms, 10-30x headroom), asserted inside the deploy-gating chromium `e2e-gate`. **Maturity candidacy DECLINED on the 2026-07-16 re-score**: the k6 load test itself runs monthly/on-demand, so it is capacity-planning evidence rather than a merge gate; the freshness gate bounds staleness but does not gate regressions. §12 stays M3/I8, lever OPEN: either record the monthly cadence as the accepted posture (ADC's stance) or add a latency-regression check to the merge path. **Re-verified OPEN on the 2026-07-28 re-score, and both proposed moves (M3→4 and I8→9) were adversarially REJECTED:** `load-test.yml:17-18` is still monthly cron plus dispatch, `deploy.needs` (re-anchored 2026-08-14: `deploy.yml:862`) still contains no perf job, no perf fitness test exists in `Tests/Architecture`, and the one deploy-chain hook `load-freshness` (`deploy.yml:613`) gained a break-glass skip (`:626-643`), which loosens rather than tightens it. Re-verified unchanged on the 2026-08-14 re-score. The 2026-07-25 performance wave is real and verified but closed defects the prior I8 already assumed absent, and three efficiency gaps stay open (the sequential per-item cross-service gRPC loop in `BulkSetInventoryHandler.cs:40-49` against the rubric's explicit no-N+1 criterion, plus the full-size image blobs). **§23 split out and RESOLVED same day (drift-analysis fold, adversarially verified):** its CWV budget assertions are per-deploy enforcement independent of k6's cadence, the identical evidence ADC's twentieth cycle credited, so scorecard §23 is M4/I8 and moves to the protect list. -- [x] **#13 · Observability & Operability · maturity 3 → 4 (weight 2). DONE (2026-07-11, remediation wave 6).** The dashboard half already existed (the saved `store-slo-workbook` Azure Monitor workbook mirrors the three SLO alerts per service); the missing runbook half landed as `infra/OPERATIONS.md`: each provisioned alert (`failed-requests`, `server-response-time`, `dependency-failures`) mapped to concrete triage steps (workbook pane, App Insights drill path, container logs, the Stripe/gRPC/outbox failure classes) plus fast-reference recovery moves (revision rollback, PITR restore, the freshness gates) and a pair-with-`sloAlertSpecs` governance note. **Split verdict on the 2026-07-16 re-score:** Implementation 8 → 9 GRANTED (both prior deductions closed: workbook `infra/main.bicep:274` + runbook `infra/OPERATIONS.md`), but the maturity candidacy was DECLINED: dashboards/runbooks are IaC/review-enforced, and nothing in CI fails when an alert loses its runbook pairing. §13 stays M3/I9, lever OPEN: add a CI gate asserting the `sloAlertSpecs`-to-`OPERATIONS.md` pairing (mirrors ADC's reopened #13; one shared gate design can serve both repos). **Gate SHIPPED same day (2026-07-16):** `Tests/Architecture/MMCA.Store.Architecture.Tests/ObservabilityConventionTests.cs` (mirror of ADC's) machine-enforces the pairing in the CI.slnf arch gate: every `sloAlertSpecs` key needs a `### ...-alert-` runbook section carrying the alert's current `(sev N)`, orphans fail, 3-spec non-vacuity floor, both files embedded. Verified red on a seeded severity drift, green on the real files. Maturity 3 → 4 candidacy recorded for the next re-score. **Maturity 4 GRANTED on the 2026-07-17 re-score** (`ObservabilityConventionTests.cs:24,34` verified live in the CI.slnf arch gate, `MMCA.Store.CI.slnf:52`): §13 is **M4/I9**; moved to the protect list. +- [~] **#12 · Performance & Scalability · maturity 3 → 4 (weight 2). LEVER STILL OPEN** (marker corrected 2026-07-28: the `[x]` contradicted this entry's own closing text and #12 sits in both ranked bands; the wave-3 work below did ship, but the maturity candidacy it recorded was declined and has been declined again since). Wave-3 delivery (2026-07-11): Both halves of the lever are now enforced deploy preconditions: (a) a `load-freshness` job in `deploy.yml`'s `needs` fails the deploy when the latest successful monthly `load-test.yml` run is older than 35 days (the dr-freshness pattern; latest run 2026-07-01, green), and (b) the WebVitals budgets were tightened from catastrophic-only (LCP 8000) to the Core Web Vitals "good" band (LCP 2500 / FCP 1800 / TTFB 800 / CLS 0.1 / INP 500), calibrated against measured CI maxima (LCP 172ms, 10-30x headroom), asserted inside the deploy-gating chromium `e2e-gate`. **Maturity candidacy DECLINED on the 2026-07-16 re-score**: the k6 load test itself runs monthly/on-demand, so it is capacity-planning evidence rather than a merge gate; the freshness gate bounds staleness but does not gate regressions. §12 stays M3/I8, lever OPEN: either record the monthly cadence as the accepted posture (ADC's stance) or add a latency-regression check to the merge path. **Re-verified OPEN on the 2026-07-28 re-score, and both proposed moves (M3→4 and I8→9) were adversarially REJECTED:** the k6 run is still monthly cron plus dispatch (re-anchored 2026-08-23: cron `load-test.yml:18`, `workflow_dispatch` at `:9`), `deploy.needs` (re-anchored 2026-08-14: `deploy.yml:862`) still contains no perf job, no perf fitness test exists in `Tests/Architecture`, and the one deploy-chain hook `load-freshness` (`deploy.yml:613`) gained a break-glass skip (`:626-643`), which loosens rather than tightens it. Re-verified unchanged on the 2026-08-14 re-score and again on the 2026-08-23 re-score (the identical M3→4 uplift was adversarially rejected: the workflow files backing the axis are byte-unchanged since the cycle that first rejected it). The 2026-07-25 performance wave is real and verified but closed defects the prior I8 already assumed absent, and three efficiency gaps stay open (the sequential per-item cross-service gRPC loop in `BulkSetInventoryHandler.cs:40-49` against the rubric's explicit no-N+1 criterion, plus the full-size image blobs). **§23 split out and RESOLVED same day (drift-analysis fold, adversarially verified):** its CWV budget assertions are per-deploy enforcement independent of k6's cadence, the identical evidence ADC's twentieth cycle credited, so scorecard §23 is M4/I8 and moves to the protect list. +- [x] **#13 · Observability & Operability · maturity 3 → 4 (weight 2). DONE (2026-07-11, remediation wave 6).** The dashboard half already existed (the saved `store-slo-workbook` Azure Monitor workbook mirrors the three SLO alerts per service); the missing runbook half landed as `infra/OPERATIONS.md`: each provisioned alert (`failed-requests`, `server-response-time`, `dependency-failures`) mapped to concrete triage steps (workbook pane, App Insights drill path, container logs, the Stripe/gRPC/outbox failure classes) plus fast-reference recovery moves (revision rollback, PITR restore, the freshness gates) and a pair-with-`sloAlertSpecs` governance note. **Split verdict on the 2026-07-16 re-score:** Implementation 8 → 9 GRANTED (both prior deductions closed: workbook `infra/main.bicep:274` + runbook `infra/OPERATIONS.md`), but the maturity candidacy was DECLINED: dashboards/runbooks are IaC/review-enforced, and nothing in CI fails when an alert loses its runbook pairing. §13 stays M3/I9, lever OPEN: add a CI gate asserting the `sloAlertSpecs`-to-`OPERATIONS.md` pairing (mirrors ADC's reopened #13; one shared gate design can serve both repos). **Gate SHIPPED same day (2026-07-16):** `Tests/Architecture/MMCA.Store.Architecture.Tests/ObservabilityConventionTests.cs` (mirror of ADC's) machine-enforces the pairing in the CI.slnf arch gate: every `sloAlertSpecs` key needs a `### ...-alert-` runbook section carrying the alert's current `(sev N)`, orphans fail, 3-spec non-vacuity floor, both files embedded. Verified red on a seeded severity drift, green on the real files. Maturity 3 → 4 candidacy recorded for the next re-score. **Maturity 4 GRANTED on the 2026-07-17 re-score** (`ObservabilityConventionTests.cs:24,34` verified live in the CI.slnf arch gate, `MMCA.Store.CI.slnf:53`): §13 is **M4/I9**; moved to the protect list. ## 🟢 Resolved this cycle (2026-07-28, drift wave: D1/D2/D5/D6/D7 + E2/E4/E7/E8) @@ -172,7 +175,7 @@ the v1.127.0 framework sweep. - [ ] **TD · Batch the bulk-inventory existence check.** `BulkSetInventoryHandler` validates each variant with its own sequential cross-service gRPC call. A 500-item ceiling now bounds it, but collapsing it needs a new `IProductVariantService` contract method (proto, adapter, service, and every fake). **`GetUnitPricesAsync` cannot be reused for it**: it drops variants whose `Price` is null, so an existing-but-unpriced variant would be reported missing and fail the request. - [ ] **TD · Projected order-line count.** The admin order grid loads every order line only to render `OrderLines.Count`. A `LineCount` DTO field does not help, because the generic query pipeline materializes entities before mapping; doing it properly means a persisted denormalized column maintained by the domain. Not proportionate for an admin grid. - [ ] **TD · Product-image derivatives.** Images are full-size DB blobs streamed as-is and rendered as card thumbnails with no `srcset`/dimensions, so a 12-card browse grid can pull 12 full-size assets. Fixing it is a storage-design decision, not a local change. -- [x] **TD · Port ADC's expand/contract migration guard. DONE (verified 2026-07-28).** The "Expand/contract migration guard (schema rollback safety)" step runs inside the required `build-and-test` job (`deploy.yml:190`, job at `:91`): it fails any PR whose newly added migration `Up()` body contains `DropColumn`/`DropTable`/`DropIndex` without an `EXPAND-CONTRACT-OVERRIDE` marker (`:228-230`), scope and rationale at `:192-205`, and **fails closed** when the base diff is unresolvable (`:212-216`) rather than passing vacuously. The policy is documented alongside the code at `CONTRIBUTING.md:55`. This is part of what lifted §8 to Implementation 9. +- [x] **TD · Port ADC's expand/contract migration guard. DONE (verified 2026-07-28; anchors refreshed 2026-08-23, substance confirmed unchanged).** The "Expand/contract migration guard (schema rollback safety)" step runs inside the required `build-and-test` job (`deploy.yml:232`, job at `:126`): it fails any PR whose newly added migration `Up()` body contains `DropColumn`/`DropTable`/`DropIndex` without an `EXPAND-CONTRACT-OVERRIDE` marker (`:271-272`), scope and rationale at `:234-247`, and **fails closed** when the base diff is unresolvable (`:251-258`) rather than passing vacuously. The policy is documented alongside the code at `CONTRIBUTING.md:55`. This is part of what lifted §8 to Implementation 9. ## 🟢 Resolved this cycle (2026-07-11 drift-convergence, drift plan D1-D13) @@ -183,7 +186,7 @@ the v1.127.0 framework sweep. ## ✅ Already at level 4 (protect, don't regress) -**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 (#13), Testability (#14), DevOps (#17), Front-End Security (#26), Resilience (#29), Dependency & Supply-Chain (#32), Architecture Governance (#34). **#8 joined on the 2026-07-28 re-score** (Implementation 8→9): the atomic conditional-UPDATE stock decrement with deterministic lock ordering (`InventoryAllocationService.cs:70`), its `CK_InventoryItem_AvailableQuantity_NonNegative` schema backstop (`InventoryItemConfiguration.cs:27`), the single-transaction checkout write phase (`CheckOutHandler.cs:91`), the fail-closed expand/contract migration guard in the required `build-and-test` check (`deploy.yml:190`), and the raw-`IQueryable` ban with an empty allowlist (`RawQueryableConventionTests.cs:14` in `MMCA.Store.CI.slnf:52`). Protecting it means keeping the decrement atomic and the CHECK constraint in place; the honest residual is that Identity has no concurrency round-trip test. +**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 (#13), Testability (#14), DevOps (#17), Front-End Security (#26), Resilience (#29), Dependency & Supply-Chain (#32), Architecture Governance (#34). **#8 joined on the 2026-07-28 re-score** (Implementation 8→9): the atomic conditional-UPDATE stock decrement with deterministic lock ordering (`InventoryAllocationService.cs:70`), its `CK_InventoryItem_AvailableQuantity_NonNegative` schema backstop (`InventoryItemConfiguration.cs:27`), the single-transaction checkout write phase (`CheckOutHandler.cs:91`), the fail-closed expand/contract migration guard in the required `build-and-test` check (`deploy.yml:232`, re-anchored 2026-08-23), and the raw-`IQueryable` ban with an empty allowlist (`RawQueryableConventionTests.cs:14` in `MMCA.Store.CI.slnf:53`). Protecting it means keeping the decrement atomic and the CHECK constraint in place; the honest residual is that Identity has no concurrency round-trip test. **Maturity 4 but implementation <= 8, so still ranked in the implementation band above:** Vertical Slice (#5), CQRS (#6), Microservices (#7), Cross-Cutting (#10), Security (#11), Code Quality (#15), Maintainability (#16), UI Architecture (#18), State Management (#19), Design System (#20), Front-End Performance (#23), Forms (#24), Navigation (#25), i18n (#27), Front-End Testing (#28), Compliance/Privacy (#30), FinOps (#31), DevEx (#33). Closing on maturity alone is exactly what let the two indices drift apart, so these stay visible rather than disappearing into the protect list. diff --git a/docs/governance/store-ArchitectureScorecard.html b/docs/governance/store-ArchitectureScorecard.html index 2d02136..2381f31 100644 --- a/docs/governance/store-ArchitectureScorecard.html +++ b/docs/governance/store-ArchitectureScorecard.html @@ -6,21 +6,21 @@ MMCA.Store: Architecture Scorecard · MMCA · Ivan Ball-llovera - + - + - + @@ -117,9 +117,9 @@

MMCA.Store: Architecture Scorecard

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 (previously its posture lived only in the workspace docs + memory). Scored against the rubric at ArchitectureEvaluationCriteria.md; framework-wide facts in ../MMCA.Common/FACTS.md. Remediation lives in RemediationBacklog.md; the cross-repo comparison in the workspace-internal Docs/Architecture/CrossRepoComparison.md (not published).

-

Rubric: ArchitectureEvaluationCriteria.md • Date: 2026-08-14 • Two axes per category: Maturity (0-4, process/governance) and Implementation (0-10, substance/execution). Indices computed deterministically from the scores below. Verified against current source at HEAD 9571a963 (clean tree); framework dependency pinned at MMCA.Common. v1.152.0* (all 15 packages, lockstep, confirmed no divergence; ADRs are canonical in Website/docs-src/adr/, count/range owned by its README.md). What moved this cycle (2026-08-14 full re-score, 34 categories, two-pass with adversarial verification, pin v1.152.0): no score moves. All 34 categories were re-scored from evidence read at HEAD 9571a963; 28 came back CONFIRMED at their prior values and six (§5, §15, §17, §19, §20, §21) came back FLAG, every one an adversarial rejection of a proposed first-pass uplift rather than a found regression: §5 held 8 (horizontal technical folders persist inside module Application layers; generic-CRUD slices dispatch to shared framework handlers), §17 held 9 (no Bicep validate/what-if before the prod run; SQL publicNetworkAccess: Enabled, the identical caveat holding ADC at 9; single prod-only environment), §19 held 8 for the second consecutive cycle (IsDrawerOpen is still publicly settable and mutated outside the notify path, CartDrawer.razor:4, CartDrawer.razor.cs:145), §20 held 7 (the ProductList remediation converted 3 attributes while 31 identical Style=/CellStyle= occurrences remain across 14 razor files, five byte-identical to the new classes), and §21 held 3/8 (the screen-reader results log still holds only the placeholder row; the one delta since the prior pin is a single added dark-palette home axe scan, now 23 scans total). §15's verify pass proposed a correction to Implementation 7 on three suppression-hygiene gaps (the expired GHSA-2m69-gcr7-jv3q audit suppression at Directory.Build.props:54 whose own removal condition is met under the v1.152.0 pin, three undocumented global NoWarn codes at :26, and the MAUI head outside all CI enforcement); the user adjudicated a hold at the prior 8 with the three gaps recorded as §15's named backlog lever. Indices unchanged: Maturity 97.8%, Implementation 83.9%. Anchor refreshes only (no substance change): §16's stale narrated pin corrected to v1.152.0, §20/§21/§22 evidence re-anchored, and §22's nightly cadence note updated (since 2026-07-29 the scheduled matrix runs one alternating engine per week, widening the per-engine blind window to 7 days). Prior cycle, retained (2026-07-28 full re-score, 34 categories, two-pass with adversarial verification, pin v1.131.0): three scores moved. §8 Data Architecture I8→9 on substance that landed after the prior cycle: the atomic conditional-UPDATE stock decrement (SET qty = qty - n WHERE qty >= n) with deterministic variant-id lock ordering closes the oversell read-modify-write race (InventoryAllocationService.cs:70), backed by a CK_InventoryItem_AvailableQuantity_NonNegative schema CHECK constraint (InventoryItemConfiguration.cs:27), an explicit single-transaction checkout write phase with the cross-service gRPC price fetch deliberately outside the lock window (CheckOutHandler.cs:91), and a fail-closed expand/contract destructive-migration guard in the required build-and-test job (deploy.yml:190); held at 9, not 10, because Identity has no concurrency round-trip test. §22 Responsive M4→3, the reopen the 2026-07-23 drift note predicted: the deploy-gating e2e-gate passes browsers: '["chromium"]' only (deploy.yml:494) and firefox/webkit run solely on the Mon/Thu schedule where they stay continue-on-error (e2e.yml:124,131), with no cross-browser freshness job in deploy.needs, so cross-engine verification is convention-enforced (Consistent=3), not automatic; the proposed Implementation 8→7 was adversarially REJECTED as a CI-cadence change mis-posted to the substance axis, matching ADC's M3/I8 on the identical mechanism. §27 i18n I9→7→8, a corrected over-grant rather than a regression (no i18n file changed since 2026-07-17): every price renders through Money.ToDisplayString(), which hard-codes a $ glyph and formats with CultureInfo.InvariantCulture (MMCA.Common .../MoneyExtensions.cs:20,41, consumed at CatalogBrowse.razor.cs:302), the rubric's explicit "manual number formatting ignoring culture" red flag, and pluralization is the "{0} item(s)" workaround rather than the i18n mechanism (CartDrawer.resx:20); the scorer proposed 7 and the user adjudicated 8, the conservative half of the band the verifier called defensible, since the gates and coverage behind the original grant are all intact. Three further first-pass proposals were adversarially REJECTED and held at prior: §12 M3→4 and I8→9 (no new merge-path perf gate exists; load-test.yml:17-18 is still monthly cron plus dispatch, and the load-freshness gate actually GAINED a break-glass skip at deploy.yml:577-592, a weakening), §19 I8→9 (no new state-management substance since the prior pin; IsDrawerOpen is still publicly settable outside the notify path), and §30 M4/I8→M3/I7 (every cited mechanism re-read live at HEAD, no gap found). Indices Maturity 98.4%→97.8%, Implementation 83.6%→83.9%. Earlier cycles, retained below, oldest first (2026-07-03 drift-plan execution, D1/D4/D5/D8/D9/D10): §21 Accessibility M3→4 and §28 Front-End Testing M3→4 (the Playwright + axe suite now gates the deploy: e2e-gate joined deploy.yml's needs after two consecutive fully green E2E runs, 28682334766 chromium 83/83 with firefox + webkit also green, confirmed by 28683063228), §12 Performance I7→8 (client Web Vitals are now measured in CI: WebVitalsTests writes LCP/CLS/TTFB/FCP artifacts per run), §23 Front-End Performance I6→8 (the public CatalogBrowse moved to server-side paging via GetPagedAsync + bounded MobileInfiniteScrollList, and cart enrichment now uses a targeted by-variant-id batch lookup instead of fetching the whole product list), and §32 Supply-Chain I7→8 (all three CI restores run --locked-mode and the suppress-aware vulnerability audit is now gating, D8/D9). The prior cycle's moves (2026-07-02 docs sweep: §16/§25/§20 M3→4, §27 scored M4/I7, §14 I6→9, §34 I7→9) are retained in the rows below. A same-day i18n completion sweep (2026-07-03, ADR-027 Decision 9) then lifted §27 Implementation 7→8 (zero residual literals incl. the cart/checkout/Stripe snackbars, dual CI gates, MudBlazor chrome + nav localized; indices Implementation 80.3%→80.4%). A subsequent 2026-07-11 drift-convergence cycle (drift plan D1-D13, pin v1.113.0) moved six scores: §1 SOLID Implementation 8→9 (the ctor-dependency-ceiling gate ConstructorDependencyCountTests + TimeProvider injection, D9), §9 API Implementation 8→9 (the v2 ServiceInfoController + two deploy-gating Contract tests, D12), §24 Forms Maturity 3→4 (the CI-gated FormsConventionTests, D11), §28 Front-End Testing Implementation 6→8 (bUnit breadth grown to 214 facts across 40 files, D7), §29 Resilience Implementation 8→9 (the dr-freshness deploy gate + weekly dr-drill cron + GracefulShutdownTests, D3), and §21 Accessibility Maturity 4→3 with Implementation 7→8 (honest reconciliation to ADC's M3: 22 axe scans + the new screen-reader runbook, but no dated SR pass yet, D6). D2 (MI-SQL activation wiring) and D4 (cost-guard deploy gate) also landed, with no §17/§31 score move. Indices Maturity 94.4%→94.1%, Implementation 80.4%→82.5%. A 2026-07-16 full re-score (34 categories, two-pass with adversarial verification) moved three scores: §13 Observability Implementation 8→9 (both prior deductions closed: the SLO workbook is provisioned in IaC at infra/main.bicep:274 and the per-alert infra/OPERATIONS.md runbook is in-repo; Maturity holds 3 because dashboards/runbooks are IaC/review-enforced, not CI-gated), and §18 UI Architecture + §19 State Management Maturity 3→4 (the sealed UIArchitectureConventionTests and StateManagementConventionTests subclasses of the shared v1.116.0 fitness bases run non-vacuously in the deploy-gating MMCA.Store.CI.slnf on every push and PR, the same mechanism that earned ADC its M4; their proposed Implementation bumps were adversarially rejected as enforcement gains mis-posted to the substance axis). The same re-score DECLINED the recorded maturity candidacies on §12 (k6 stays monthly/on-demand, not a merge gate) and §22 (firefox/webkit are still continue-on-error in e2e.yml:71, contrary to the backlog's promotion claim), and held the §20/§24/§27 impl candidacies. §17 Implementation 8→9 additionally banked on directly verified evidence: MI-SQL is active in production (repo variable USE_MANAGED_IDENTITY_SQL=true since 2026-07-12, activation deploy 29192048197 green), correcting the row's stale inert claim. Indices Maturity 94.1%→95.9%, Implementation 82.5%→83.0%. A same-day drift-analysis fold (2026-07-16, cross-repo ADC-vs-Store comparison, each move adversarially verified) moved two more scores: §23 Maturity 3→4 (the CWV budgets are hard assertions in the deploy-gating chromium e2e-gate, the identical evidence ADC's twentieth cycle credited; the earlier same-day hold at M3 had wrongly imported §12's k6-cadence reasoning) and §32 Implementation 8→9 (capability-level parity with ADC's I9: identical --locked-mode/audit/SBOM gating; the earlier FLAG reasoned from stale scorecard text, not capability). Doc corrections in the same fold: §16's narrated pin 1.113.0→1.116.0, §32's lock-file count 49→55, and the README gained the ADC-parity broker note (§33). Indices Maturity 95.9%→96.6%, Implementation 83.0%→83.3%. A 2026-07-17 full re-score (34 categories, two-pass with adversarial verification, pin v1.117.0) moved four scores: §5 Vertical Slice M3→4 and I7→8 (the sealed SliceCohesionTests subclass runs non-vacuously in the deploy-gating MMCA.Store.CI.slnf, the identical gate ADC credits at M4/I8; the layered-by-project hybrid stays a deliberate implementation-axis cap, no longer a maturity deduction), §13 Observability M3→4 (ObservabilityConventionTests machine-enforces the sloAlertSpecs-to-OPERATIONS.md pairing in the CI merge gate, closing exactly the "not CI-gated" reasoning that held M3), §22 Responsive M3→4 (the 2026-07-16 gate flip verified live: continue-on-error in e2e.yml is scoped to scheduled non-chromium runs only, so all three engines the e2e-gate invokes can fail a deploy; note 2026-07-23: this basis drifted on 2026-07-18 when the gate was cut to chromium-only, see the §22 row), and §27 i18n I8→9 (the PseudoLocalizationTests candidacy granted: pseudo-loc sentinel, no-overflow, and en-US leak probes run over Store's own /, /catalog, and /login pages in the deploy-gating chromium e2e-gate). The narrated framework pin refreshed 1.116.0→1.117.0 throughout. Indices Maturity 96.6%→98.4%, Implementation 83.3%→83.6%.

+

Rubric: ArchitectureEvaluationCriteria.md • Date: 2026-08-23 • Two axes per category: Maturity (0-4, process/governance) and Implementation (0-10, substance/execution). Indices computed deterministically from the scores below. Verified against current source at HEAD 063c90dd (clean tree); framework dependency pinned at MMCA.Common. v1.160.0* (all 15 packages, lockstep, confirmed no divergence; ADRs are canonical in Website/docs-src/adr/, count/range owned by its README.md). What moved this cycle (2026-08-23 full re-score, 34 categories, two-pass with adversarial verification, pin v1.160.0): no score moves. 27 categories came back CONFIRMED at their prior values and seven (§5, §6, §7, §9, §12, §20, §31) came back FLAG, every one an adversarial rejection of a proposed first-pass move rather than a found regression: §6 held 8 (the only Source-side delta on the event path since the prior pin is a comment; the cross-service flow still carries its self-documented non-atomic publish window), §7 held 8 (the new evidence is a CI fitness gate, an enforcement gain on an already-M4 category, plus a second test that is vacuous in Store; the lockstep all-services deploy cap is confirmed live), §9 held 9 against a proposed DOWNGRADE (the contract-guard evidence base grew from the narrated two files to seven across all three services plus the frozen gRPC proto contract; only the row's narration and anchors were stale), §12 held M3 (the workflow files backing the axis are byte-unchanged since the cycle that rejected the identical uplift), §20 held 7 (zero .razor/.css/.razor.cs files changed since the last scored commit; the residual count corrects 31 to 30 as a counting fix, and the StoreHome stylesheets carry an additional previously un-cited hard-coded hex palette with !important), and §31 held 8 (the reversible-scale-events criterion is still unmet in its exact terms, the identical basis on which ADC's §31 uplift was rejected on 2026-08-01; the 300s metric-export interval and tighter ACR purge refine criteria already credited inside the 8). §5 is the one special case: its first-pass scorer returned no numbers, so M4/I8 is carried forward unverified this cycle and owes a fresh read at the next re-score. Citation repairs only (no substance change): MMCA.Store.CI.slnf:53 corrected to :53 throughout (line 52 is now MMCA.Store.Identity.UI.Tests), §8's migration-gate anchors re-based (model-drift deploy.yml:216-230, expand/contract :232-274), §9's guard narration and anchors corrected, §12's controller anchor re-based, and §27's MoneyExtensions anchor extended to :54-59. Indices unchanged: Maturity 97.8%, Implementation 83.9%. Prior cycle, retained (2026-08-14 full re-score, 34 categories, two-pass with adversarial verification, pin v1.152.0): no score moves. All 34 categories were re-scored from evidence read at HEAD 9571a963; 28 came back CONFIRMED at their prior values and six (§5, §15, §17, §19, §20, §21) came back FLAG, every one an adversarial rejection of a proposed first-pass uplift rather than a found regression: §5 held 8 (horizontal technical folders persist inside module Application layers; generic-CRUD slices dispatch to shared framework handlers), §17 held 9 (no Bicep validate/what-if before the prod run; SQL publicNetworkAccess: Enabled, the identical caveat holding ADC at 9; single prod-only environment), §19 held 8 for the second consecutive cycle (IsDrawerOpen is still publicly settable and mutated outside the notify path, CartDrawer.razor:4, CartDrawer.razor.cs:145), §20 held 7 (the ProductList remediation converted 3 attributes while 31 identical Style=/CellStyle= occurrences remain across 14 razor files, five byte-identical to the new classes), and §21 held 3/8 (the screen-reader results log still holds only the placeholder row; the one delta since the prior pin is a single added dark-palette home axe scan, now 23 scans total). §15's verify pass proposed a correction to Implementation 7 on three suppression-hygiene gaps (the expired GHSA-2m69-gcr7-jv3q audit suppression at Directory.Build.props:54 whose own removal condition is met under the v1.152.0 pin, three undocumented global NoWarn codes at :26, and the MAUI head outside all CI enforcement); the user adjudicated a hold at the prior 8 with the three gaps recorded as §15's named backlog lever. Indices unchanged: Maturity 97.8%, Implementation 83.9%. Anchor refreshes only (no substance change): §16's stale narrated pin corrected to v1.152.0, §20/§21/§22 evidence re-anchored, and §22's nightly cadence note updated (since 2026-07-29 the scheduled matrix runs one alternating engine per week, widening the per-engine blind window to 7 days). Earlier cycle, retained (2026-07-28 full re-score, 34 categories, two-pass with adversarial verification, pin v1.131.0): three scores moved. §8 Data Architecture I8→9 on substance that landed after the prior cycle: the atomic conditional-UPDATE stock decrement (SET qty = qty - n WHERE qty >= n) with deterministic variant-id lock ordering closes the oversell read-modify-write race (InventoryAllocationService.cs:70), backed by a CK_InventoryItem_AvailableQuantity_NonNegative schema CHECK constraint (InventoryItemConfiguration.cs:27), an explicit single-transaction checkout write phase with the cross-service gRPC price fetch deliberately outside the lock window (CheckOutHandler.cs:91), and a fail-closed expand/contract destructive-migration guard in the required build-and-test job (deploy.yml:190); held at 9, not 10, because Identity has no concurrency round-trip test. §22 Responsive M4→3, the reopen the 2026-07-23 drift note predicted: the deploy-gating e2e-gate passes browsers: '["chromium"]' only (deploy.yml:494) and firefox/webkit run solely on the Mon/Thu schedule where they stay continue-on-error (e2e.yml:124,131), with no cross-browser freshness job in deploy.needs, so cross-engine verification is convention-enforced (Consistent=3), not automatic; the proposed Implementation 8→7 was adversarially REJECTED as a CI-cadence change mis-posted to the substance axis, matching ADC's M3/I8 on the identical mechanism. §27 i18n I9→7→8, a corrected over-grant rather than a regression (no i18n file changed since 2026-07-17): every price renders through Money.ToDisplayString(), which hard-codes a $ glyph and formats with CultureInfo.InvariantCulture (MMCA.Common .../MoneyExtensions.cs:20,41, consumed at CatalogBrowse.razor.cs:302), the rubric's explicit "manual number formatting ignoring culture" red flag, and pluralization is the "{0} item(s)" workaround rather than the i18n mechanism (CartDrawer.resx:20); the scorer proposed 7 and the user adjudicated 8, the conservative half of the band the verifier called defensible, since the gates and coverage behind the original grant are all intact. Three further first-pass proposals were adversarially REJECTED and held at prior: §12 M3→4 and I8→9 (no new merge-path perf gate exists; load-test.yml:17-18 is still monthly cron plus dispatch, and the load-freshness gate actually GAINED a break-glass skip at deploy.yml:577-592, a weakening), §19 I8→9 (no new state-management substance since the prior pin; IsDrawerOpen is still publicly settable outside the notify path), and §30 M4/I8→M3/I7 (every cited mechanism re-read live at HEAD, no gap found). Indices Maturity 98.4%→97.8%, Implementation 83.6%→83.9%. Earlier cycles, retained below, oldest first (2026-07-03 drift-plan execution, D1/D4/D5/D8/D9/D10): §21 Accessibility M3→4 and §28 Front-End Testing M3→4 (the Playwright + axe suite now gates the deploy: e2e-gate joined deploy.yml's needs after two consecutive fully green E2E runs, 28682334766 chromium 83/83 with firefox + webkit also green, confirmed by 28683063228), §12 Performance I7→8 (client Web Vitals are now measured in CI: WebVitalsTests writes LCP/CLS/TTFB/FCP artifacts per run), §23 Front-End Performance I6→8 (the public CatalogBrowse moved to server-side paging via GetPagedAsync + bounded MobileInfiniteScrollList, and cart enrichment now uses a targeted by-variant-id batch lookup instead of fetching the whole product list), and §32 Supply-Chain I7→8 (all three CI restores run --locked-mode and the suppress-aware vulnerability audit is now gating, D8/D9). The prior cycle's moves (2026-07-02 docs sweep: §16/§25/§20 M3→4, §27 scored M4/I7, §14 I6→9, §34 I7→9) are retained in the rows below. A same-day i18n completion sweep (2026-07-03, ADR-027 Decision 9) then lifted §27 Implementation 7→8 (zero residual literals incl. the cart/checkout/Stripe snackbars, dual CI gates, MudBlazor chrome + nav localized; indices Implementation 80.3%→80.4%). A subsequent 2026-07-11 drift-convergence cycle (drift plan D1-D13, pin v1.113.0) moved six scores: §1 SOLID Implementation 8→9 (the ctor-dependency-ceiling gate ConstructorDependencyCountTests + TimeProvider injection, D9), §9 API Implementation 8→9 (the v2 ServiceInfoController + two deploy-gating Contract tests, D12), §24 Forms Maturity 3→4 (the CI-gated FormsConventionTests, D11), §28 Front-End Testing Implementation 6→8 (bUnit breadth grown to 214 facts across 40 files, D7), §29 Resilience Implementation 8→9 (the dr-freshness deploy gate + weekly dr-drill cron + GracefulShutdownTests, D3), and §21 Accessibility Maturity 4→3 with Implementation 7→8 (honest reconciliation to ADC's M3: 22 axe scans + the new screen-reader runbook, but no dated SR pass yet, D6). D2 (MI-SQL activation wiring) and D4 (cost-guard deploy gate) also landed, with no §17/§31 score move. Indices Maturity 94.4%→94.1%, Implementation 80.4%→82.5%. A 2026-07-16 full re-score (34 categories, two-pass with adversarial verification) moved three scores: §13 Observability Implementation 8→9 (both prior deductions closed: the SLO workbook is provisioned in IaC at infra/main.bicep:274 and the per-alert infra/OPERATIONS.md runbook is in-repo; Maturity holds 3 because dashboards/runbooks are IaC/review-enforced, not CI-gated), and §18 UI Architecture + §19 State Management Maturity 3→4 (the sealed UIArchitectureConventionTests and StateManagementConventionTests subclasses of the shared v1.116.0 fitness bases run non-vacuously in the deploy-gating MMCA.Store.CI.slnf on every push and PR, the same mechanism that earned ADC its M4; their proposed Implementation bumps were adversarially rejected as enforcement gains mis-posted to the substance axis). The same re-score DECLINED the recorded maturity candidacies on §12 (k6 stays monthly/on-demand, not a merge gate) and §22 (firefox/webkit are still continue-on-error in e2e.yml:71, contrary to the backlog's promotion claim), and held the §20/§24/§27 impl candidacies. §17 Implementation 8→9 additionally banked on directly verified evidence: MI-SQL is active in production (repo variable USE_MANAGED_IDENTITY_SQL=true since 2026-07-12, activation deploy 29192048197 green), correcting the row's stale inert claim. Indices Maturity 94.1%→95.9%, Implementation 82.5%→83.0%. A same-day drift-analysis fold (2026-07-16, cross-repo ADC-vs-Store comparison, each move adversarially verified) moved two more scores: §23 Maturity 3→4 (the CWV budgets are hard assertions in the deploy-gating chromium e2e-gate, the identical evidence ADC's twentieth cycle credited; the earlier same-day hold at M3 had wrongly imported §12's k6-cadence reasoning) and §32 Implementation 8→9 (capability-level parity with ADC's I9: identical --locked-mode/audit/SBOM gating; the earlier FLAG reasoned from stale scorecard text, not capability). Doc corrections in the same fold: §16's narrated pin 1.113.0→1.116.0, §32's lock-file count 49→55, and the README gained the ADC-parity broker note (§33). Indices Maturity 95.9%→96.6%, Implementation 83.0%→83.3%. A 2026-07-17 full re-score (34 categories, two-pass with adversarial verification, pin v1.117.0) moved four scores: §5 Vertical Slice M3→4 and I7→8 (the sealed SliceCohesionTests subclass runs non-vacuously in the deploy-gating MMCA.Store.CI.slnf, the identical gate ADC credits at M4/I8; the layered-by-project hybrid stays a deliberate implementation-axis cap, no longer a maturity deduction), §13 Observability M3→4 (ObservabilityConventionTests machine-enforces the sloAlertSpecs-to-OPERATIONS.md pairing in the CI merge gate, closing exactly the "not CI-gated" reasoning that held M3), §22 Responsive M3→4 (the 2026-07-16 gate flip verified live: continue-on-error in e2e.yml is scoped to scheduled non-chromium runs only, so all three engines the e2e-gate invokes can fail a deploy; note 2026-07-23: this basis drifted on 2026-07-18 when the gate was cut to chromium-only, see the §22 row), and §27 i18n I8→9 (the PseudoLocalizationTests candidacy granted: pseudo-loc sentinel, no-overflow, and en-US leak probes run over Store's own /, /catalog, and /login pages in the deploy-gating chromium e2e-gate). The narrated framework pin refreshed 1.116.0→1.117.0 throughout. Indices Maturity 96.6%→98.4%, Implementation 83.3%→83.6%.

Executive summary

-

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 services behind a YARP gateway, collaborating via Result-over-the-wire gRPC and MassTransit integration events on the outbox pattern. It consumes the shared MMCA.Common.* framework at v1.152.0 in lockstep with MMCA.ADC. Architecturally it is at ADC parity, and one prior assumption is corrected here: Store runs database-per-service (Store_Catalog/Store_Sales/Store_Identity, each with its own dbo.OutboxMessages; the legacy single MMCAStore DB is retained read-only as an archive/rollback only), not a single shared database. Its tactical depth (Clean Architecture, DDD, CQRS, the decorator pipeline, soft-delete/audit, RowVersion concurrency) is inherited framework substance, enforced by 23 NetArchTest fitness-test classes (shared *TestsBase subclasses from MMCA.Common.Testing.Architecture plus Store-local guards such as DataResidencyTests, PiiConventionTests, and IntegrationEventContractTests, ADR-015; the compile-time layer-guard MSBuild target is MMCA.Common-internal and does not run here).

+

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 services behind a YARP gateway, collaborating via Result-over-the-wire gRPC and MassTransit integration events on the outbox pattern. It consumes the shared MMCA.Common.* framework at v1.160.0 in lockstep with MMCA.ADC. Architecturally it is at ADC parity, and one prior assumption is corrected here: Store runs database-per-service (Store_Catalog/Store_Sales/Store_Identity, each with its own dbo.OutboxMessages; the legacy single MMCAStore DB is retained read-only as an archive/rollback only), not a single shared database. Its tactical depth (Clean Architecture, DDD, CQRS, the decorator pipeline, soft-delete/audit, RowVersion concurrency) is inherited framework substance, enforced by 23 NetArchTest fitness-test classes (shared *TestsBase subclasses from MMCA.Common.Testing.Architecture plus Store-local guards such as DataResidencyTests, PiiConventionTests, and IntegrationEventContractTests, ADR-015; the compile-time layer-guard MSBuild target is MMCA.Common-internal and does not run here).

The two axes are asymmetric: Maturity 97.8% vs Implementation 83.9%. Implementation is the weaker axis by ~14 points, a wider gap than ADC's. The maturity is high because Store inherits the framework's governed mechanisms and adds a strong operational floor (two-phase Bicep IaC, OIDC + Key Vault managed identity, a post-deploy smoke gate with auto-rollback, a drilled DR restore, a cost-guard surge-drift check, a k6 load test, and now a chromium E2E/axe deploy gate at ADC parity). The former §21/§28 gate gap and the §23 catalog fetch-all are closed this cycle, and the §27 residual unlocalized strings were closed by the same-day i18n completion sweep (I7→8; zero literals, dual CI gates); the former §18/§19 review-only maturity gap closed on 2026-07-16 (both are now CI-enforced by the shared convention fitness gates at M4), and the same-day drift-fold closed §23 (the deploy-gated CWV budget assertions credited at M4, ADC parity); the 2026-07-17 re-score then closed §5 (slice cohesion CI-gated, the hybrid kept as an implementation cap), §13 (the alert-to-runbook pairing gate), and §22 (the three-engine gate flip verified live). §22 reopened to maturity 3 on 2026-07-28 when the 2026-07-18 Actions-minute reduction was scored rather than frozen: the deploy gate runs chromium only, so §12 (k6 not a merge gate), §21 (screen-reader pass pending), and §22 (cross-engine verification convention-enforced) are the three below-4 maturity categories. §14's former coverage gap is closed: the deploy-gating floor is 51.6 measured on Store's own code (+MMCA.Store.*;-*.Tests, ~54% actual). Supply-chain (§32): the vulnerability gate is NuGetAudit + TreatWarningsAsErrors at restore plus the gating suppress-aware audit, the SBOM is a hard gate, and all three CI restores run --locked-mode.

Front-end security is the standout (§26, impl 9): access token in-memory, refresh token in an HttpOnly cookie (no localStorage), a hardened origin-pinned CSP, and runtime-config fetch (no secrets in the bundle), confirmed at ADC parity. No category is N/A: §27 Internationalization is scored (M4/I8 after the 2026-07-28 correction; the 2026-07-03 completion sweep and the 2026-07-17 pseudo-loc layout-tolerance grant had carried it to I9) since the ADR-027 en-US + es localization shipped with its CI-gated translation-completeness fitness function, superseding ADR-011's single-locale exclusion; the sweep added the LocalizedTextConventionTests literal gate and removed every residual hard-coded string. The 2026-07-28 correction is not a regression: both CI gates and the pseudo-loc E2E suite are intact and un-skipped, but culture-aware number formatting and mechanism-driven pluralization, two of the rubric's five criteria, are demonstrably unmet in current code, so the I9 was an over-grant.

Scorecard

@@ -178,7 +178,7 @@

Scorecard

4 8 8/16 - Cohesive command+handler+request+validator+mapper per operation; deliberate layered-by-project hybrid (cross-cutting in the pipeline). ↑ Maturity 3→4 + Implementation 7→8 (2026-07-17): slice cohesion is machine-enforced pre-merge by SliceCohesionTests (sealed subclass of the shared non-vacuous SliceCohesionTestsBase, two real rule facts) in the deploy-gating MMCA.Store.CI.slnf, the identical gate ADC credits at M4/I8; the hybrid stays a deliberate implementation-axis cap (holds impl at 8, not a maturity deduction). Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/SliceCohesionTests.cs:9; MMCA.Store.CI.slnf:52; Application/Orders/UseCases/Cancel/{CancelOrderCommand,CancelOrderHandler}.cs + Cohesive command+handler+request+validator+mapper per operation; deliberate layered-by-project hybrid (cross-cutting in the pipeline). ↑ Maturity 3→4 + Implementation 7→8 (2026-07-17): slice cohesion is machine-enforced pre-merge by SliceCohesionTests (sealed subclass of the shared SliceCohesionTestsBase, two real rule facts; the base carries no minimum-scanned-types floor, and the scan is non-vacuous in practice because StoreArchitectureMap anchors real Application assemblies) in the deploy-gating MMCA.Store.CI.slnf, the identical gate ADC credits at M4/I8; the hybrid stays a deliberate implementation-axis cap (holds impl at 8, not a maturity deduction). 2026-08-23: the proposed 8→9 was adversarially rejected a second consecutive cycle: horizontal technical folders persist inside the module Application layers, gate-invisible where the validated type is cross-assembly (the co-location rule exempts them, MMCA.Common .../ArchitectureRules.Slices.cs:48; instance: Identity.Application/Users/Validation/ChangePasswordRequestValidator.cs:10 over the Shared ChangePasswordRequest while its command+handler live in Users/UseCases/ChangePassword/), and generic-CRUD operations still dispatch to shared framework handlers (Catalog.Application/DependencyInjection.cs:41, Identity.Application/DependencyInjection.cs:47) with reads served by the generic IEntityQueryService (CategoriesController.cs:33). The first-pass scorer returned no numbers this cycle, so M4/I8 is carried forward unverified and owes a fresh read at the next re-score. Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/SliceCohesionTests.cs:9; MMCA.Store.CI.slnf:53; Application/Orders/UseCases/Cancel/{CancelOrderCommand,CancelOrderHandler}.cs 6 @@ -205,7 +205,7 @@

Scorecard

4 9 12/27 - Per-aggregate tx, soft-delete + filtered indexes, central audit (Common), RowVersion concurrency, versioned per-service migrations with a model-drift CI gate, LTR backups. ↑ Implementation 8→9 (2026-07-28): the write path is now race-safe by construction, not by convention. Stock decrements are an atomic conditional UPDATE (SET qty = qty - n WHERE qty >= n) with deterministic variant-id lock ordering, enlisted in the ambient transaction and stamping the audit columns explicitly because ExecuteUpdate bypasses the audit interceptor (InventoryAllocationService.cs:70); a CK_InventoryItem_AvailableQuantity_NonNegative CHECK constraint backstops it at the schema so no future path can drive stock negative (InventoryItemConfiguration.cs:27); the checkout write phase (decrements + order insert + cart transition) is one ExecuteInTransactionAsync with the cross-service gRPC price fetch deliberately outside it, so remote latency never extends lock hold time (CheckOutHandler.cs:91); and an expand/contract guard fails any PR whose new migration Up() drops a column/table/index without an EXPAND-CONTRACT-OVERRIDE marker, failing closed when the base diff is unresolvable (deploy.yml:190, policy at CONTRIBUTING.md:55). Both migration gates sit in the required build-and-test check. Held at 9, not 10: Identity carries IConcurrencyAware mutation requests but has no concurrency round-trip test, so the 409 proof covers 2 of 3 modules; note also that the shared ConcurrencyConventionTests rule scans Application types named *UpdateRequest, of which Store has none, so that particular gate is vacuous here (the substance below stands without it). Evidence: InventoryAllocationService.cs:70; InventoryItemConfiguration.cs:27; CheckOutHandler.cs:91; API-level round-trip proof incl. the child-entity token path in Tests/Integration/MMCA.Store.Catalog.IntegrationTests/Concurrency/StaleRowVersionConflictTests.cs:45 and Tests/Integration/MMCA.Store.Sales.IntegrationTests/Concurrency/OrderTransitionConcurrencyTests.cs:23; raw-IQueryable ban with an EMPTY allowlist in Tests/Architecture/MMCA.Store.Architecture.Tests/RawQueryableConventionTests.cs:14 (in MMCA.Store.CI.slnf:52); per-module model-drift gate deploy.yml:174-188 (corrected from the stale :86-99); main.bicep:383-394 + Per-aggregate tx, soft-delete + filtered indexes, central audit (Common), RowVersion concurrency, versioned per-service migrations with a model-drift CI gate, LTR backups. ↑ Implementation 8→9 (2026-07-28): the write path is now race-safe by construction, not by convention. Stock decrements are an atomic conditional UPDATE (SET qty = qty - n WHERE qty >= n) with deterministic variant-id lock ordering, enlisted in the ambient transaction and stamping the audit columns explicitly because ExecuteUpdate bypasses the audit interceptor (InventoryAllocationService.cs:70); a CK_InventoryItem_AvailableQuantity_NonNegative CHECK constraint backstops it at the schema so no future path can drive stock negative (InventoryItemConfiguration.cs:27); the checkout write phase (decrements + order insert + cart transition) is one ExecuteInTransactionAsync with the cross-service gRPC price fetch deliberately outside it, so remote latency never extends lock hold time (CheckOutHandler.cs:91); and an expand/contract guard fails any PR whose new migration Up() drops a column/table/index without an EXPAND-CONTRACT-OVERRIDE marker, failing closed when the base diff is unresolvable (deploy.yml:232-274, re-anchored 2026-08-23; policy at CONTRIBUTING.md:55). Both migration gates sit in the required build-and-test check. Held at 9, not 10: Identity carries IConcurrencyAware mutation requests but has no concurrency round-trip test, so the 409 proof covers 2 of 3 modules; note also that the shared ConcurrencyConventionTests rule scans Application types named *UpdateRequest, of which Store has none, so that particular gate is vacuous here (the substance below stands without it). Evidence: InventoryAllocationService.cs:70; InventoryItemConfiguration.cs:27; CheckOutHandler.cs:91; API-level round-trip proof incl. the child-entity token path in Tests/Integration/MMCA.Store.Catalog.IntegrationTests/Concurrency/StaleRowVersionConflictTests.cs:45 and Tests/Integration/MMCA.Store.Sales.IntegrationTests/Concurrency/OrderTransitionConcurrencyTests.cs:23; raw-IQueryable ban with an EMPTY allowlist in Tests/Architecture/MMCA.Store.Architecture.Tests/RawQueryableConventionTests.cs:14 (in MMCA.Store.CI.slnf:53); per-module model-drift gate deploy.yml:216-230 (re-anchored 2026-08-23); main.bicep:383-394 9 @@ -214,7 +214,7 @@

Scorecard

4 9 8/18 - RFC 9457 Problem Details, header versioning, pagination, DTO decoupling (ADR-001), gRPC .proto, OpenAPI served non-prod, and a demonstrated v2 contract: ServiceInfoController carries [ApiVersion("1.0", Deprecated)] + [ApiVersion("2.0")], backed by two deploy-gating contract guards (D12). Below 10: OpenAPI not exposed in prod (internal behind gateway); v2 demonstrated on one endpoint. Evidence: Catalog.API/Controllers/ServiceInfoController.cs:18; Tests/Integration/MMCA.Store.Catalog.IntegrationTests/Contract/{ApiVersioningTests,OpenApiContractTests}.cs; OrdersController.cs:77,34,114; Sales.Service/Program.cs:102,193 + RFC 9457 Problem Details, header versioning, pagination, DTO decoupling (ADR-001), gRPC .proto, OpenAPI served non-prod, and a demonstrated v2 contract: ServiceInfoController carries [ApiVersion("1.0", Deprecated)] + [ApiVersion("2.0")]. Narration corrected 2026-08-23 (no score impact; a proposed downgrade to 8 was adversarially REJECTED because the evidence base grew, only the row's text was stale): the guards are seven contract-guard files across all three services (OpenAPI shape/path-floor + RFC 9457 Problem Details for Catalog/Sales/Identity, incl. the Store-specific 409 stale-RowVersion probe, plus ServiceInfo API-versioning on Catalog), running in integration-tests, which is PR-only (deploy.yml:394, absent from deploy.needs at :862) but a server-side REQUIRED status check on main with strict=true (CONTRIBUTING.md:83), and merging to main is the prod deploy, so nothing ships without them green; ProtoContractTests additionally freezes the full cross-service gRPC wire contract in the merge-gating architecture tier (ProtoContractTests.cs:9). Below 10: OpenAPI not exposed in prod (internal behind gateway); v2 demonstrated on one anonymous diagnostic endpoint in one service (all 14 business controllers are v1.0-only). Evidence: Catalog.API/Controllers/ServiceInfoController.cs:18; Tests/Integration/MMCA.Store.Catalog.IntegrationTests/Contract/{ApiVersioningTests,OpenApiContractTests,ProblemDetailsContractTests}.cs + Sales/Identity siblings; OrdersController.cs:46,91,120,127-129 (re-anchored 2026-08-23); Sales.Service/Program.cs:148,284,307 (re-anchored 2026-08-23) 10 @@ -241,7 +241,7 @@

Scorecard

3 8 6/16 - Async throughout, projections/AsNoTracking/paging on hot paths, tiered cache (output + Redis), stateless scale-out, a real k6 load test, and client Web Vitals measured per E2E run (LCP/CLS/TTFB/FCP written as CI artifacts, D10). Evidence: Tests/Load/k6/catalog-read-load.js + load-test.yml; Tests/E2E/MMCA.Store.E2E.Tests/Workflows/WebVitalsTests.cs + e2e.yml (WEB_VITALS_OUTPUT_DIR); OrdersController.cs:73,108 + Async throughout, projections/AsNoTracking/paging on hot paths, tiered cache (output + Redis), stateless scale-out, a real k6 load test, and client Web Vitals measured per E2E run (LCP/CLS/TTFB/FCP written as CI artifacts, D10). Evidence: Tests/Load/k6/catalog-read-load.js + load-test.yml; Tests/E2E/MMCA.Store.E2E.Tests/Workflows/WebVitalsTests.cs + e2e.yml (WEB_VITALS_OUTPUT_DIR); OrdersController.cs:102-112 (bounded pageSize, asTracking: false, field projection; re-anchored 2026-08-23) 13 @@ -250,7 +250,7 @@

Scorecard

4 9 8/18 - OTel logs/traces/RED metrics via ServiceDefaults (incl. MMCA.Common.Outbox), /health+/alive+/health/ready, App Insights + 3 SLO alerts + action group + saved SLO workbook provisioned in IaC, per-alert operations runbook in-repo, correlation, poll-span noise control, CI-gated graceful shutdown. ↑ Maturity 3→4 (2026-07-17): the alert-to-runbook pairing is now a CI-gated fitness function: ObservabilityConventionTests parses the embedded infra/main.bicep sloAlertSpecs and fails the merge gate on any alert without a severity-correct OPERATIONS.md section (3-spec non-vacuity floor), closing exactly the "IaC/review-enforced, not CI-gated" reasoning that held M3. Implementation 9 (2026-07-16: workbook + runbook deductions closed). Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/ObservabilityConventionTests.cs (a sealed subclass since the 2026-07-28 extraction wave; the three [Fact]s now live in MMCA.Common.Testing.Architecture/Bases/ObservabilityConventionTestsBase.cs),34; MMCA.Store.CI.slnf:52; main.bicep:204 (sloAlertSpecs), :274 (workbook); infra/OPERATIONS.md:16; Tests/Hosts/MMCA.Store.Gateway.Tests/GracefulShutdownTests.cs (a sealed subclass since the 2026-07-28 extraction wave; the assertion body now lives in MMCA.Common.Testing/GracefulShutdownTestsBase.cs) + OTel logs/traces/RED metrics via ServiceDefaults (incl. MMCA.Common.Outbox), /health+/alive+/health/ready, App Insights + 3 SLO alerts + action group + saved SLO workbook provisioned in IaC, per-alert operations runbook in-repo, correlation, poll-span noise control, CI-gated graceful shutdown. ↑ Maturity 3→4 (2026-07-17): the alert-to-runbook pairing is now a CI-gated fitness function: ObservabilityConventionTests parses the embedded infra/main.bicep sloAlertSpecs and fails the merge gate on any alert without a severity-correct OPERATIONS.md section (3-spec non-vacuity floor), closing exactly the "IaC/review-enforced, not CI-gated" reasoning that held M3. Implementation 9 (2026-07-16: workbook + runbook deductions closed). Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/ObservabilityConventionTests.cs (a sealed subclass since the 2026-07-28 extraction wave; the three [Fact]s now live in MMCA.Common.Testing.Architecture/Bases/ObservabilityConventionTestsBase.cs),34; MMCA.Store.CI.slnf:53; main.bicep:204 (sloAlertSpecs), :274 (workbook); infra/OPERATIONS.md:16; Tests/Hosts/MMCA.Store.Gateway.Tests/GracefulShutdownTests.cs (a sealed subclass since the 2026-07-28 extraction wave; the assertion body now lives in MMCA.Common.Testing/GracefulShutdownTestsBase.cs) 14 @@ -277,7 +277,7 @@

Scorecard

4 8 8/16 - Versioned framework contracts (Common 1.152.0, lockstep, matching this document's own header pin; the 2026-08-13 stale-pin note is resolved, re-verified 2026-08-14 at Directory.Packages.props:8-21) with the lockstep invariant executable: FrameworkVersionConsistencyTests asserts every MMCA.Common.* pin shares one version and fails the build on a partial sweep, running in the CI merge gate. Consumes the shared arch-test package, current CLAUDE.md, extractable modules. Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/FrameworkVersionConsistencyTests.cs; MMCA.Store.CI.slnf:52; Directory.Packages.props:8-19 + Versioned framework contracts (Common 1.160.0, lockstep, matching this document's own header pin; re-verified 2026-08-23 at Directory.Packages.props:8-21) with the lockstep invariant executable: FrameworkVersionConsistencyTests asserts every MMCA.Common.* pin shares one version and fails the build on a partial sweep, running in the CI merge gate. Consumes the shared arch-test package, current CLAUDE.md, extractable modules. Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/FrameworkVersionConsistencyTests.cs; MMCA.Store.CI.slnf:53; Directory.Packages.props:8-19 17 @@ -295,7 +295,7 @@

Scorecard

4 8 12/24 - Code-behind split, @inherits DataGridListPageBase<ProductDTO>, scoped cart service owns data/behavior, reuse of Common.UI primitives. ↑ Maturity 3→4 (2026-07-16): the container/presentational conventions are machine-enforced pre-merge by UIArchitectureConventionTests (sealed subclass of the shared base, 400-line code-behind cap + 120-line inline @code cap, non-vacuous MinimumCodeBehindFiles guard; largest code-behind is 368 lines), running in the deploy-gating MMCA.Store.CI.slnf on push and PR, the same mechanism as ADC's M4. Implementation holds 8: minor inline-style logic in markup remains. Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/UIArchitectureConventionTests.cs:11; StoreArchitectureMap.cs:29,37,45; MMCA.Store.CI.slnf:52; deploy.yml:59,429; ProductList.razor.cs:14; CartDrawer.razor:50 + Code-behind split, @inherits DataGridListPageBase<ProductDTO>, scoped cart service owns data/behavior, reuse of Common.UI primitives. ↑ Maturity 3→4 (2026-07-16): the container/presentational conventions are machine-enforced pre-merge by UIArchitectureConventionTests (sealed subclass of the shared base, 400-line code-behind cap + 120-line inline @code cap, non-vacuous MinimumCodeBehindFiles guard; largest code-behind is 368 lines), running in the deploy-gating MMCA.Store.CI.slnf on push and PR, the same mechanism as ADC's M4. Implementation holds 8: minor inline-style logic in markup remains. Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/UIArchitectureConventionTests.cs:11; StoreArchitectureMap.cs:29,37,45; MMCA.Store.CI.slnf:53; deploy.yml:59,429; ProductList.razor.cs:14; CartDrawer.razor:50 19 @@ -304,7 +304,7 @@

Scorecard

4 8 12/24 - Single source of truth, scoped (no static cross-user state), unidirectional flow with OnChange+InvokeAsync(StateHasChanged)+Dispose, single-flight token hydrate. ↑ Maturity 3→4 (2026-07-16): both §19 red flags are machine-enforced pre-merge by StateManagementConventionTests (mutable-static-state reflection scan over the Layer.Ui assemblies with a non-vacuous guard, plus the singleton-*StateService source scan), in the deploy-gating MMCA.Store.CI.slnf; CartStateService is registered TryAddScoped, proving the rule the gate enforces. Implementation holds 8. Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/StateManagementConventionTests.cs:12; Sales.UI/DependencyInjection.cs:35; MMCA.Store.CI.slnf:52; CartStateService.cs:38; CartButton.razor:33; ServerTokenStorageService.cs:44 + Single source of truth, scoped (no static cross-user state), unidirectional flow with OnChange+InvokeAsync(StateHasChanged)+Dispose, single-flight token hydrate. ↑ Maturity 3→4 (2026-07-16): both §19 red flags are machine-enforced pre-merge by StateManagementConventionTests (mutable-static-state reflection scan over the Layer.Ui assemblies with a non-vacuous guard, plus the singleton-*StateService source scan), in the deploy-gating MMCA.Store.CI.slnf; CartStateService is registered TryAddScoped, proving the rule the gate enforces. Implementation holds 8. Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/StateManagementConventionTests.cs:12; Sales.UI/DependencyInjection.cs:35; MMCA.Store.CI.slnf:53; CartStateService.cs:38; CartButton.razor:33; ServerTokenStorageService.cs:44 20 @@ -313,7 +313,7 @@

Scorecard

4 7 8/14 - MudBlazor + Common.UI theme/tokens used consistently, shared grid-paging wrapper; the brand-color token convention is now CI-enforced by BrandColorTokenTests (shipped 5fbd003, guards both UI hosts' home CSS against hard-coded brand hex). 2026-08-14 verify: a proposed 7→8 was adversarially REJECTED. Commit a1de5a89 converted the three previously cited ProductList.razor attributes to semantic classes (.list-search-field, .grid-cell-count, .grid-cell-actions, rules in store.css:28-40), but 31 Style=/CellStyle= occurrences remain across 14 razor files, five byte-identical to the classes just created; the shared classes existing while 3 of the 4 sibling admin list pages do not use them is itself the rubric's fought-page-by-page red flag. Evidence: App.razor:11; Tests/Architecture/MMCA.Store.Architecture.Tests/BrandColorTokenTests.cs; residuals re-anchored 2026-08-14: CategoryList.razor:23 (Style=), :79/:88 (CellStyle=), OrderList.razor:22, CustomerList.razor:81, plus CatalogBrowse.razor/CatalogProductDetail.razor/OrderLinesPanel.razor/CustomerDetail.razor + MudBlazor + Common.UI theme/tokens used consistently, shared grid-paging wrapper; the brand-color token convention is now CI-enforced by BrandColorTokenTests (shipped 5fbd003, guards both UI hosts' home CSS against hard-coded brand hex). 2026-08-14 verify: a proposed 7→8 was adversarially REJECTED; re-rejected 2026-08-23 (zero .razor/.css/.razor.cs files changed since the last scored commit, so the identical evidence set stands). Commit a1de5a89 converted the three previously cited ProductList.razor attributes to semantic classes (.list-search-field, .grid-cell-count, .grid-cell-actions, rules in store.css:28-40), but 30 Style=/CellStyle= occurrences remain across 14 razor files (count corrected from 31 on 2026-08-23, a counting fix, not a conversion), five byte-identical to the classes just created; the shared classes existing while 3 of the 4 sibling admin list pages do not use them is itself the rubric's fought-page-by-page red flag. A further previously un-cited red flag surfaced 2026-08-23: the StoreHome landing stylesheet hard-codes a hex palette alongside !important overrides with only --mmca-primary tokenized, duplicated byte-for-byte in both UI hosts (UI.Web.Client/Pages/StoreHome.razor.css:203 and the MAUI head's StoreHome.razor.css:203,271). Evidence: App.razor:11; Tests/Architecture/MMCA.Store.Architecture.Tests/BrandColorTokenTests.cs; residuals re-anchored 2026-08-14, re-verified byte-identical 2026-08-23: CategoryList.razor:23 (Style=), :79/:88 (CellStyle=), OrderList.razor:22, CustomerList.razor:81, plus CatalogBrowse.razor/CatalogProductDetail.razor/OrderLinesPanel.razor/CustomerDetail.razor 21 @@ -349,7 +349,7 @@

Scorecard

4 8 8/16 - Unsaved-changes guard with current-state accessor (9 pages), double-submit blocked, all states designed, destructive confirm, abandoned-payment recovery. ↑ Maturity 3→4 (D11): the four admin create forms' guard/dirty/validated-MudForm/Required markers are machine-enforced by the CI-gated FormsConventionTests (MinimumCreateForms=4), matching ADC on the same evidence. Client validation is MudForm-level (not full FluentValidation parity), the impl 8→9 lever. Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/FormsConventionTests.cs; MMCA.Store.CI.slnf:52; ProductCreate.razor:8,58; CartStateService.cs:232 + Unsaved-changes guard with current-state accessor (9 pages), double-submit blocked, all states designed, destructive confirm, abandoned-payment recovery. ↑ Maturity 3→4 (D11): the four admin create forms' guard/dirty/validated-MudForm/Required markers are machine-enforced by the CI-gated FormsConventionTests (MinimumCreateForms=4), matching ADC on the same evidence. Client validation is MudForm-level (not full FluentValidation parity), the impl 8→9 lever. Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/FormsConventionTests.cs; MMCA.Store.CI.slnf:53; ProductCreate.razor:8,58; CartStateService.cs:232 25 @@ -376,7 +376,7 @@

Scorecard

4 8 4/8 - ↓ Implementation 9→8 (2026-07-28), a corrected over-grant, not a regression. No i18n file has changed since the 2026-07-17 polish wave (last touching commit 103580f7), and every gate behind the original grant is intact and un-skipped, but two of the rubric's five criteria are demonstrably unmet in current code. (1) Culture-aware formatting (half-closed as of v1.152.0, re-verified 2026-08-14): Money.ToDisplayString() no longer hard-codes a $ glyph; it resolves the symbol from the price's own currency (USD/EUR map, unknown codes render symbol-less; MMCA.Common .../MMCA.Common.UI/Extensions/MoneyExtensions.cs:18-20,54-58, Common change 2026-08-05, inside the v1.152.0 pin Store consumes). The remaining half stands: amounts still format with CultureInfo.InvariantCulture (:69-70, consumed at CatalogBrowse.razor.cs:302), the rubric's explicit "manual number formatting ignoring culture" red flag. This is a bypass, not missing plumbing: UseCommonRequestLocalization registers both supported cultures and supported UI cultures (MMCA.Common .../WebApplicationExtensions.cs:141). (2) Pluralization is the "(s)" workaround, not handled by the i18n mechanism (CartDrawer.resx:20 "{0} item(s)", ShoppingCartList.es.resx:11 "{0} articulo(s)"). Layout tolerance is also proven on 3 public pages only, with no RTL locale. The first-pass score was 7; 8 is the adjudicated value, the conservative half of the band the adversarial pass called defensible. The root cause is shared framework code (MoneyExtensions), so the same deduction may apply to MMCA.Common and MMCA.ADC at their next re-scores. Maturity 4 unchanged: both arch gates run in the required build-and-test check via MMCA.Store.CI.slnf:52 + deploy.yml:138, and the pseudo-loc E2E suite rides the deploy-gating e2e-gate (deploy.yml:489,786; e2e.yml:347 runs the project unfiltered). Citation drift corrected: UseCommonRequestLocalization is at UI.Web/Program.cs:140 and MapCultureEndpoint at :173 (row previously cited :107,140); AddErrorResources is at Identity.Service/Program.cs:174 (previously :150). Prior (2026-07-17), retained: the layout-tolerance lever is realized on Store's own pages: PseudoLocalizationTests activates qps-Ploc via the production /culture/set cookie mechanism and asserts the [!! sentinel, the no-horizontal-overflow expression, and a per-page en-US leak probe over /, /catalog, and /login, riding the deploy-gating chromium e2e-gate (Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PseudoLocalizationTests.cs:57). Prior: Implementation 7→8 (2026-07-03 i18n completion sweep, ADR-027 Decision 9); Maturity 4 holds with a second gate. Full ADR-027 adoption (supersedes ADR-011): en-US + es .resx pairs across all three module UIs, the API error resources, both StoreHome hosts, and the three UI modules' nav items (30 base / 30 .es siblings; ~130 new key pairs on the sweep), UseCommonRequestLocalization + MapCultureEndpoint (UI.Web/Program.cs:107,140), per-module AddErrorResources (Identity.Service/Program.cs:150), User.PreferredCulture + AddUserPreferences migration. The 2026-07-03 sweep's own deductions are closed (scoped to hard-coded literals; this claim never covered the culture-formatting and pluralization criteria corrected above on 2026-07-28): zero hard-coded snackbars (35 sites to whole-sentence page keys, including the cart/checkout/Stripe strings; raw {ex.Message} never surfaces), the ErrorMessages.Success concatenation is gone (obsoleted upstream, all 28 sites swept), 33 literal breadcrumb labels localize from Breadcrumb.* keys built in OnInitialized, nav menus localize via NavItem.TitleResource + new module resx pairs, and MudBlazor built-in chrome localizes via the framework's ResxMudLocalizer (inherited). Maturity 4, now doubly gated in CI.slnf: TranslationCompletenessTests (floor raised 20→25) + the NEW LocalizedTextConventionTests (no hard-coded snackbar/title/<PageTitle>/breadcrumb/NavItem literal can ship; MinimumScannedFiles=40). Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/{TranslationCompletenessTests.cs,LocalizedTextConventionTests.cs}; Sales.UI/.../CartDrawer.razor.cs + OrderDetail.razor.cs (whole-sentence Snackbar.* keys); CatalogUIModule.cs (TitleResource + resx pair); verification: Store CI suite 1120/1120 green post-sweep + ↓ Implementation 9→8 (2026-07-28), a corrected over-grant, not a regression. No i18n file has changed since the 2026-07-17 polish wave (last touching commit 103580f7), and every gate behind the original grant is intact and un-skipped, but two of the rubric's five criteria are demonstrably unmet in current code. (1) Culture-aware formatting (half-closed as of v1.152.0, re-verified 2026-08-14): Money.ToDisplayString() no longer hard-codes a $ glyph; it resolves the symbol from the price's own currency (USD/EUR map, unknown codes render symbol-less; MMCA.Common .../MMCA.Common.UI/Extensions/MoneyExtensions.cs:18-20,54-59 (re-anchored 2026-08-23), Common change 2026-08-05, inside the v1.160.0 pin Store consumes). The remaining half stands: amounts still format with CultureInfo.InvariantCulture (:69-70, consumed at CatalogBrowse.razor.cs:302), the rubric's explicit "manual number formatting ignoring culture" red flag. This is a bypass, not missing plumbing: UseCommonRequestLocalization registers both supported cultures and supported UI cultures (MMCA.Common .../WebApplicationExtensions.cs:141). (2) Pluralization is the "(s)" workaround, not handled by the i18n mechanism (CartDrawer.resx:20 "{0} item(s)", ShoppingCartList.es.resx:11 "{0} articulo(s)"). Layout tolerance is also proven on 3 public pages only, with no RTL locale. The first-pass score was 7; 8 is the adjudicated value, the conservative half of the band the adversarial pass called defensible. The root cause is shared framework code (MoneyExtensions), so the same deduction may apply to MMCA.Common and MMCA.ADC at their next re-scores. Maturity 4 unchanged: both arch gates run in the required build-and-test check via MMCA.Store.CI.slnf:53 + deploy.yml:138, and the pseudo-loc E2E suite rides the deploy-gating e2e-gate (deploy.yml:489,786; e2e.yml:347 runs the project unfiltered). Citation drift corrected: UseCommonRequestLocalization is at UI.Web/Program.cs:140 and MapCultureEndpoint at :173 (row previously cited :107,140); AddErrorResources is at Identity.Service/Program.cs:174 (previously :150). Prior (2026-07-17), retained: the layout-tolerance lever is realized on Store's own pages: PseudoLocalizationTests activates qps-Ploc via the production /culture/set cookie mechanism and asserts the [!! sentinel, the no-horizontal-overflow expression, and a per-page en-US leak probe over /, /catalog, and /login, riding the deploy-gating chromium e2e-gate (Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PseudoLocalizationTests.cs:57). Prior: Implementation 7→8 (2026-07-03 i18n completion sweep, ADR-027 Decision 9); Maturity 4 holds with a second gate. Full ADR-027 adoption (supersedes ADR-011): en-US + es .resx pairs across all three module UIs, the API error resources, both StoreHome hosts, and the three UI modules' nav items (30 base / 30 .es siblings; ~130 new key pairs on the sweep), UseCommonRequestLocalization + MapCultureEndpoint (UI.Web/Program.cs:107,140), per-module AddErrorResources (Identity.Service/Program.cs:150), User.PreferredCulture + AddUserPreferences migration. The 2026-07-03 sweep's own deductions are closed (scoped to hard-coded literals; this claim never covered the culture-formatting and pluralization criteria corrected above on 2026-07-28): zero hard-coded snackbars (35 sites to whole-sentence page keys, including the cart/checkout/Stripe strings; raw {ex.Message} never surfaces), the ErrorMessages.Success concatenation is gone (obsoleted upstream, all 28 sites swept), 33 literal breadcrumb labels localize from Breadcrumb.* keys built in OnInitialized, nav menus localize via NavItem.TitleResource + new module resx pairs, and MudBlazor built-in chrome localizes via the framework's ResxMudLocalizer (inherited). Maturity 4, now doubly gated in CI.slnf: TranslationCompletenessTests (floor raised 20→25) + the NEW LocalizedTextConventionTests (no hard-coded snackbar/title/<PageTitle>/breadcrumb/NavItem literal can ship; MinimumScannedFiles=40). Evidence: Tests/Architecture/MMCA.Store.Architecture.Tests/{TranslationCompletenessTests.cs,LocalizedTextConventionTests.cs}; Sales.UI/.../CartDrawer.razor.cs + OrderDetail.razor.cs (whole-sentence Snackbar.* keys); CatalogUIModule.cs (TitleResource + resx pair); verification: Store CI suite 1120/1120 green post-sweep 28 @@ -447,8 +447,8 @@

Scorecard

Indices

    -
  • 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 maturity 3)
  • -
  • Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 671 ÷ 800 = 83.9% (re-confirmed with no moves on the 2026-08-14 re-score; §15's proposed correction to 7 was user-adjudicated to hold at 8)
  • +
  • Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 313 ÷ 320 = 97.8% (re-confirmed with no moves on the 2026-08-23 re-score; §12, §21, and §22 are the three categories at maturity 3)
  • +
  • Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 671 ÷ 800 = 83.9% (re-confirmed with no moves on the 2026-08-23 re-score; the seven FLAG categories keep their prior scores under the merged-prior rule, §5 carried unverified this cycle)
  • The implementation index reads directly against 100% (recalibration 2026-08-01: a 10 is awardable for an almost perfect implementation, so the former "attainable ceiling" line is retired). The denominators stay ×4 and ×10 so the trend line remains comparable to every prior cycle.
  • Weaker axis: Implementation (execution quality), by ~14 points.
  • No N/A categories: §27 joined the denominators on the 2026-07-02 cycle (ADR-027 superseded ADR-011) and remains scored. §32 weight = 2 (default; raised to 3 only for the published framework MMCA.Common).
  • @@ -456,7 +456,7 @@

    Indices

    Top 5 strengths

    1. Clean Architecture + DDD + CQRS depth, fitness-enforced: §3/§4 (impl 9): inherited framework substance, guarded by the shared NetArchTest suite (23 test classes, StoreArchitectureMap.cs:14-43).
    2. -
    3. Full microservices parity on a race-safe data layer, §7 (impl 8) / §8 (impl 9): database-per-service + per-service outbox + versioned gRPC + MassTransit broker + RS256/JWKS, extractable modules (infra/main.bicep:396-418 per-service DBs, :445-474 Service Bus), with the oversell race closed by an atomic conditional UPDATE under deterministic lock ordering plus a schema CHECK backstop (InventoryAllocationService.cs:70, InventoryItemConfiguration.cs:27) and destructive migrations blocked at the merge gate (deploy.yml:190). (Corrects the prior "single shared DB" assumption.)
    4. +
    5. Full microservices parity on a race-safe data layer, §7 (impl 8) / §8 (impl 9): database-per-service + per-service outbox + versioned gRPC + MassTransit broker + RS256/JWKS, extractable modules (infra/main.bicep:396-418 per-service DBs, :445-474 Service Bus), with the oversell race closed by an atomic conditional UPDATE under deterministic lock ordering plus a schema CHECK backstop (InventoryAllocationService.cs:70, InventoryItemConfiguration.cs:27) and destructive migrations blocked at the merge gate (deploy.yml:232-274). (Corrects the prior "single shared DB" assumption.)
    6. Exemplary DevOps / operational floor: §17 (impl 9) / §31 / §12: two-phase Bicep, OIDC + Key Vault MI, passwordless MI-SQL active in prod (2026-07-12), post-deploy smoke gate with auto-rollback (deploy.yml:461-518), cost-guard/dr-drill/load-test workflows.
    7. Reference front-end security, §26 (impl 9): in-memory access token + HttpOnly refresh cookie (no localStorage), hardened origin-pinned CSP, runtime-config fetch (ServerTokenStorageService.cs:10-13, BlazorCspPolicyProvider.cs:71).
    8. Drilled DR + real erasure path (§29 impl 9 / §30 impl 8): recorded 28.9-min restore (DISASTER-RECOVERY.md:146-148), the dr-freshness deploy gate + weekly cron + CI-gated GracefulShutdownTests, IAnonymizable anonymize-in-place + export + residency/PII fitness functions.
    9. @@ -467,11 +467,11 @@

      Top 5 risks

    10. Remediation: record a dated NVDA/VoiceOver pass in the runbook against the running app (needs a human; cannot be done headless). Expected: §21 mat 3→4.
-
  • Load evidence is capacity-planning cadence, not a merge gate: §12 (mat 3): the k6 suite is real with pass/fail thresholds and a load-freshness deploy gate, but the test itself runs monthly/on-demand (load-test.yml:17-18), so a latency regression can merge and deploy inside the freshness window. The window loosened further on 2026-07-28: load-freshness gained a break-glass skip (deploy.yml:626-643, job at :613, re-anchored 2026-08-14), justified but a weakening of the only deploy-chain hook §12 has.
      +
    • Load evidence is capacity-planning cadence, not a merge gate: §12 (mat 3): the k6 suite is real with pass/fail thresholds and a load-freshness deploy gate, but the test itself runs monthly/on-demand (load-test.yml:18 cron, workflow_dispatch at :9; re-anchored 2026-08-23), so a latency regression can merge and deploy inside the freshness window. The window loosened further on 2026-07-28: load-freshness gained a break-glass skip (deploy.yml:626-643, job at :613, re-anchored 2026-08-14), justified but a weakening of the only deploy-chain hook §12 has.
      • Remediation: right-size deliberately: either accept the monthly cadence as the recorded posture (matching ADC's accepted §12 M3) or add a cheap latency-regression smoke to the merge path. Expected: decision recorded either way; §12 mat 3→4 only with a gate.
    • -
    • Design-system residual inline styles: §20 (impl 7, the widest M-over-I gap): the token convention is CI-enforced (BrandColorTokenTests), and the semantic-class pattern now exists (commit a1de5a89 converted ProductList's three attributes to .list-search-field/.grid-cell-count/.grid-cell-actions, store.css:28-40), but 31 Style=/CellStyle= occurrences remain across 14 razor files, five byte-identical to those classes (CategoryList.razor:23,79,88; OrderList.razor:22; CustomerList.razor:81; re-anchored 2026-08-14).
        +
      • Design-system residual inline styles: §20 (impl 7, the widest M-over-I gap): the token convention is CI-enforced (BrandColorTokenTests), and the semantic-class pattern now exists (commit a1de5a89 converted ProductList's three attributes to .list-search-field/.grid-cell-count/.grid-cell-actions, store.css:28-40), but 30 Style=/CellStyle= occurrences remain across 14 razor files (count corrected from 31 on 2026-08-23), five byte-identical to those classes (CategoryList.razor:23,79,88; OrderList.razor:22; CustomerList.razor:81; re-verified byte-identical 2026-08-23).
        • Remediation: sweep the remaining occurrences onto the now-existing semantic classes (the sibling admin list pages first, where the classes are byte-identical drop-ins). Expected: §20 impl 7→8.
      • diff --git a/docs/governance/store-RemediationBacklog.html b/docs/governance/store-RemediationBacklog.html index 99e19a8..aa9e3b4 100644 --- a/docs/governance/store-RemediationBacklog.html +++ b/docs/governance/store-RemediationBacklog.html @@ -114,7 +114,7 @@

        Architecture governance

        MMCA.Store: Architecture Remediation Backlog

        -

        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 re-score, framework pin v1.152.0). +

        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 and 2026-08-23 full re-scores, framework pin v1.160.0). Items are ranked on both scorecard axes, one band per axis (two-axis policy adopted 2026-07-28, replacing the previous "or a notable implementation gap" wording, which had no number behind it and so never scheduled anything):

        @@ -133,7 +133,7 @@

        MMCA.Store: Architecture Rem

        🔴 Priority: a11y / E2E merge gate (#21, #28, #22)

        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 the 2026-07-18 chromium-only cut was scored rather than frozen. #21 remains the highest open lever, pending a recorded screen-reader pass.

        Maturity band (maturity < 4, ranked by priority)

        -

        Computed from the 2026-07-28 re-score, re-confirmed unchanged on the 2026-08-14 re-score (pin v1.152.0): 3 categories, 7 gap points.

        +

        Computed from the 2026-07-28 re-score, re-confirmed unchanged on the 2026-08-14 re-score (pin v1.152.0) and again on the 2026-08-23 re-score (pin v1.160.0, Directory.Packages.props:9-10, HEAD 063c90dd): 3 categories, 7 gap points.

        @@ -172,7 +172,8 @@

        Maturity band (maturity &l

        Ties break by priority desc, then weight desc, then category asc.

          -
        • [~] #21 Accessibility, maturity 4 corrected back to 3 (2026-07-11, drift plan D6); impl 7 → 8 DONE. The axe + Playwright suite gates the deploy (e2e-gate, chromium, workflow_call into e2e.yml, in deploy.yml's needs), and the axe scans broadened 10 → 22 pages (public, shopper, and Catalog/Sales/Identity admin surfaces), lifting impl 7 → 8. The prior maturity-4 was an over-claim: the rubric pairs axe-in-CI with a recorded manual screen-reader pass, so honest maturity is 3, matching ADC on the same rubric. A new ACCESSIBILITY-SCREENREADER-PASS.md runbook shipped (centralized as store-ACCESSIBILITY-SCREENREADER-PASS.md in Website docs-src/guides/ since 2026-07-20), but its results log is still empty (re-verified 2026-08-14: placeholder row only; the axe suite meanwhile grew 22 → 23 scans with a dark-palette home scan, AccessibilityTests.cs:327-340). Maturity 3 → 4 lever: record a dated manual SR pass in the new runbook (needs a human + NVDA/VoiceOver against the running Aspire app; cannot be done headless).
        • +
        • [~] #21 Accessibility, maturity 4 corrected back to 3 (2026-07-11, drift plan D6); impl 7 → 8 DONE. The axe + Playwright suite gates the deploy (e2e-gate, chromium, workflow_call into e2e.yml, in deploy.yml's needs; qualifier 2026-08-23: the gate is UI-scoped and skippable, see the TD below), and the axe scans broadened 10 → 22 pages (public, shopper, and Catalog/Sales/Identity admin surfaces), lifting impl 7 → 8. The prior maturity-4 was an over-claim: the rubric pairs axe-in-CI with a recorded manual screen-reader pass, so honest maturity is 3, matching ADC on the same rubric. A new ACCESSIBILITY-SCREENREADER-PASS.md runbook shipped (centralized as store-ACCESSIBILITY-SCREENREADER-PASS.md in Website docs-src/guides/ since 2026-07-20), but its results log is still empty (re-verified 2026-08-14: placeholder row only; the axe suite meanwhile grew 22 → 23 scans with a dark-palette home scan, AccessibilityTests.cs:327-340). Maturity 3 → 4 lever: record a dated manual SR pass in the new runbook (needs a human + NVDA/VoiceOver against the running Aspire app; cannot be done headless).
        • +
        • TD · The deploy-gating a11y/E2E run is UI-scoped, so a backend-only merge deploys with no axe or Playwright run (found 2026-08-23; affects #21, #22, #28). The e2e-gate job runs only when needs.changes.outputs.ui == 'true' (deploy.yml:544, rationale comment :539-543, the 2026-07-29 Actions-minute saving), and the deploy job deliberately tolerates a SKIPPED e2e-gate: it is the one gate allowed to be success OR skipped while every other gate must be success (deploy.yml:876-880, with the if: always() guard at :881-884). On a backend-only merge, therefore, the 23 WCAG 2.1 AA axe scans (Tests/E2E/MMCA.Store.E2E.Tests/Workflows/AccessibilityTests.cs, dark-palette scan at :327-340) and the Playwright workflow suite do not run at all, and the chromium-only blind window priced under Deliberate / accepted becomes 100% for all three engines. This ledger previously asserted the axe suite gates the deploy unconditionally; that claim is now qualified where it appears. Lever: either make e2e-gate unconditional on push, or record the UI-scoping as a deliberate accepted trade-off alongside the chromium-only entry (it is currently neither enforced nor recorded).
        • #28 Front-End Testing, maturity 3 → 4 DONE (2026-07-03) / impl 6 → 8 DONE (2026-07-11, drift plan D7). The E2E + axe deploy gate shipped with #21; bUnit breadth grown to 214 [Fact]/[Theory] across 40 files (Catalog 63 / Sales 126 / Identity 25) with loading/empty/error/edge state coverage, the full CI gate green at 1393/1393.
        • [~] #22 Responsive & Cross-Browser, maturity 3 → 4 GRANTED on the 2026-07-17 re-score; basis went STALE the next day (drift recorded on the 2026-07-23 verification pass); maturity REOPENED 4 → 3 on the 2026-07-28 re-score. The 2026-07-18 Actions-minute reduction (commit 777348ec, mirroring ADC's) cut the deploy e2e-gate to chromium only (deploy.yml:494, browsers: '["chromium"]', rationale comment :483-488), so firefox/webkit now run only on the scheduled matrix (re-anchored 2026-08-14: e2e.yml:143 keeps them continue-on-error, :133-135 selects the engine; since 2026-07-29 the schedule runs ONE alternating engine per week, Mon firefox / Thu webkit per the crons at :37-47, so each engine is blind for 7 days), and the granted basis, "all three engines the gate invokes CAN fail a deploy", no longer holds. There is no cross-browser freshness job in deploy.needs to bound the blind window either, so cross-engine verification is convention-enforced (Consistent=3), not automatic. Scorecard §22 is now M3/I8, matching ADC's twenty-second cycle on identical evidence; the proposed Implementation 8→7 was adversarially REJECTED (a CI-cadence change is not a substance regression). Maturity 3 → 4 lever: add a cross-browser-freshness job to deploy.needs on the dr-freshness/load-freshness pattern, which bounds staleness without paying for three engines per deploy, or promote firefox/webkit back into the gate. The chromium-only cost trade-off is recorded under Deliberate / accepted so the choice stays conscious rather than silently low. Grant provenance with anchors as of 2026-07-17: e2e.yml:76 scoped continue-on-error to scheduled non-chromium runs (now :117), deploy.yml:315 invoked all three engines (gate now :417-423), deploy.yml:429 put e2e-gate in deploy needs (now :634). History of the reopen-and-fix below. The wave-5 change passed browsers: ["chromium", "firefox", "webkit"] into the e2e-gate call, so all three engines RUN in the gate, but the non-chromium legs cannot FAIL it: e2e.yml:71 still sets continue-on-error: ${{ matrix.browser != 'chromium' }}, and deploy.yml:433's own inline comment describes e2e-gate as chromium-only. The 2026-07-16 re-score held maturity 3 on exactly this evidence and the candidacy was declined. The green-soak history (2026-07-09 through 2026-07-11, plus the d057afc three-engine catch) still stands as soak evidence. Maturity 3 → 4 lever: remove the continue-on-error conditional for the gate-invoked firefox/webkit legs (or gate them behind their own required jobs) once the soak is judged sufficient; nightly-matrix legs may stay advisory. Lever SHIPPED same day (2026-07-16): continue-on-error is now github.event_name == 'schedule' && matrix.browser != 'chromium', so all three engines the gate invokes (deploy.yml:315) CAN fail a deploy while nightly non-chromium legs stay advisory flake alarms; the stale chromium-only comments in e2e.yml/deploy.yml corrected. Soak judged sufficient: job-level green nightly matrices 2026-07-09 through 2026-07-16, with the sole 2026-07-12 red being the all-three-engines product defect fixed in d057afc (a true positive, not flake). Maturity 3 → 4 candidacy recorded for the next re-score.
        @@ -181,7 +182,8 @@

        Implementa

        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 rule, which is why maturity reached 98.4% while implementation sat at 83.6%. Computed from the current scorecard - (2026-07-28 full re-score, re-confirmed unchanged on the 2026-08-14 re-score, pin v1.152.0): + (2026-07-28 full re-score, re-confirmed unchanged on the 2026-08-14 re-score and again on the + 2026-08-23 re-score, pin v1.160.0): 21 categories, 49 gap points, the largest of the three repos. (The former "attainable ceiling" comparison line is retired per the 2026-08-01 recalibration; the index reads against 100%.) Levers are cited only where this ledger or the scorecard already records @@ -203,7 +205,7 @@

        Implementa Design System & UI Consistency 2 7 - OPEN, recorded below, re-anchored 2026-08-14: ProductList's three cited attributes are converted (commit a1de5a89, semantic classes in store.css:28-40), but 31 Style=/CellStyle= occurrences remain across 14 razor files, five byte-identical to the new classes (CategoryList.razor:23,79,88; OrderList.razor:22; CustomerList.razor:81); the sweep onto the now-existing classes is the lever + OPEN, recorded below, re-verified 2026-08-23: ProductList's three cited attributes are converted (commit a1de5a89, semantic classes in store.css:28-40), but 30 Style=/CellStyle= occurrences remain across 14 razor files (count corrected from 31 on 2026-08-23, a counting fix: zero razor files changed), five byte-identical to the new classes (CategoryList.razor:23,79,88; OrderList.razor:22; CustomerList.razor:81); the sweep onto the now-existing classes is the lever, and the classes are mirrored in the MAUI head's app.css:38,42,47, a second sweep target 3 @@ -291,7 +293,7 @@

        Implementa Best Practices & Code Quality 2 8 - lever named 2026-08-14 (the verify pass proposed I7 on these; user-adjudicated hold at 8): (1) remove the expired GHSA-2m69-gcr7-jv3q audit suppression (Directory.Build.props:54; its removal condition at :45-52 is met under the v1.152.0 pin), (2) document or remove the three unjustified global NoWarn codes (:26) and the uncommented duplicates in five test csprojs, (3) bring the MAUI head under CI enforcement (it is in neither .slnf, so its analyzers/TWAE/audit never run in CI) + lever named 2026-08-14 (the verify pass proposed I7 on these; user-adjudicated hold at 8): (1) remove the expired GHSA-2m69-gcr7-jv3q audit suppression (Directory.Build.props:54; its removal condition at :45-52 is still met under the v1.160.0 pin, re-verified 2026-08-23), (2) document or remove the three unjustified global NoWarn codes (:26; the list gained a fourth code S8970, which IS documented at :22-25, so the three-unjustified count still holds) and the uncommented duplicates in five test csprojs, (3) bring the MAUI head under CI enforcement (it is in neither .slnf, so its analyzers/TWAE/audit never run in CI; re-verified 2026-08-23 against MMCA.Store.CI.slnf:5-54) 2 @@ -363,7 +365,7 @@

        Implementa Internationalization (i18n) 1 8 - OPEN, half-closed 2026-08-14: the $-glyph half is FIXED in Common (per-currency symbol resolution, MoneyExtensions.cs:18-20,54-58, Common change 2026-08-05, inside the v1.152.0 pin). Remaining: amounts still format with CultureInfo.InvariantCulture (:69-70) and pluralization stays the "{0} item(s)" / "{0} articulo(s)" workaround (CartDrawer.resx:20, ShoppingCartList.es.resx:11). The fix lands in MMCA.Common, so it is [C→A], not Store-local + OPEN, half-closed 2026-08-14: the $-glyph half is FIXED in Common (per-currency symbol resolution, MoneyExtensions.cs:18-20,54-59, re-anchored 2026-08-23; Common change 2026-08-05, inside the v1.160.0 pin). Remaining: amounts still format with CultureInfo.InvariantCulture (:69-70) and pluralization stays the "{0} item(s)" / "{0} articulo(s)" workaround (CartDrawer.resx:20, ShoppingCartList.es.resx:11). The fix lands in MMCA.Common, so it is [C→A], not Store-local (2026-08-23 caveat: re-verified against Common source at HEAD, not the published v1.160.0 package body)
          @@ -391,25 +393,26 @@

          🐞 Defect-fix wave (2026-07-05)

          Deliberate / accepted (record the choice; don't silently leave low)

          • ADR-042 device capability abstraction (latent, drift plan D8). The core extension point is converged (Store wires browser + MAUI capabilities via UseMauiDeviceCapabilities/AddBrowserDeviceCapabilities and renders the shared OfflineBanner), but Store consumes no further capability in its own product UI: no ExternalLink, no DeviceUIModule/DeepLinkListener, no app actions. Because no Store product page carries an external anchor today, the WebView dead-end risk is LATENT, not a live defect, so there is nothing to convert now. Adopt ExternalLink on any future Store product page that grows an external anchor, and register a Store IUIModule with DeepLinkListener (the way ADC's DeviceUIModule does) if a Store MAUI feature surface is ever wanted. The framework side is complete in Common (18 capability contracts + the MMCA.Common.UI.Maui package), so this is consumer-side only, not [C→A].
          • -
          • #5 Vertical Slice (M3) RESOLVED (2026-07-17 re-score): §5 is M4/I8, granted on the CI-gated SliceCohesionTests (sealed subclass of the shared non-vacuous base, in the deploy-gating MMCA.Store.CI.slnf:52), the identical gate ADC credits at M4/I8. The layered-by-project hybrid remains a deliberate design choice, now correctly recorded as an implementation-axis cap (holds impl at 8), not a maturity deduction. Moved to the protect list.
          • -
          • #27 Internationalization N/A RETIRED (2026-07-02): ADR-027 superseded ADR-011; Store ships full en-US + es localization with the CI-gated TranslationCompletenessTests. §27 is scored and included in the indices. Updated 2026-07-03 (i18n completion sweep): §27 is M4/I8 with zero residual hard-coded literals (35 snackbars incl. cart/checkout/Stripe, 33 breadcrumb labels, nav items, both StoreHome hosts), a second CI gate (LocalizedTextConventionTests), the completeness floor raised 20→25, and MudBlazor chrome localized via the framework's ResxMudLocalizer. Impl 8→9 lever DONE (2026-07-11, remediation wave 6): Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PseudoLocalizationTests.cs extends the pseudo-loc text-expansion evidence to Store's own public pages (/, /catalog, /login): activates qps-Ploc via the production /culture/set cookie mechanism (the circuit handshake carries cookies, not query strings), asserts the [!! sentinel, Common's exact no-horizontal-overflow expression, and a per-page resx-owned en-US leak probe, plus a default-culture sentinel guard. No host/AppHost change needed; rides the deploy-gating chromium e2e-gate (first genuine run in CI). §27 Implementation 8→9 candidacy recorded for the next re-score. Candidacy GRANTED on the 2026-07-17 re-score (user-adjudicated: the lever's test is real and rides the deploy-gating chromium e2e-gate): §27 was M4/I9. REVERSED on the 2026-07-28 re-score: §27 is M4/I8 and is back in the implementation band. Not a regression, and not a withdrawal of the lever: PseudoLocalizationTests.cs:64,100 is intact and un-skipped and both arch gates still run in MMCA.Store.CI.slnf:52. The I9 was an over-grant because it scored the lever rather than the category: two of the rubric's five criteria are unmet in current code, namely culture-aware number formatting (Money.ToDisplayString() hard-codes a $ glyph and formats with CultureInfo.InvariantCulture, MMCA.Common .../MoneyExtensions.cs:20,41, an explicit rubric red flag) and mechanism-driven pluralization (the "{0} item(s)" / "{0} articulo(s)" workaround, CartDrawer.resx:20, ShoppingCartList.es.resx:11). The first pass proposed 7; 8 was adjudicated. Both defects live in shared MMCA.Common code, so the fix is [C→A] and the same deduction may apply to Common's and ADC's §27 at their next re-scores. Update 2026-08-14: the $-glyph half is FIXED (Common resolves the symbol from the price's own currency since 2026-08-05, MoneyExtensions.cs:18-20,54-58, inside the v1.152.0 pin Store consumes); the CultureInfo.InvariantCulture amount formatting (:69-70) and the pluralization workaround remain, so §27 holds I8 and stays in the implementation band.
          • +
          • #5 Vertical Slice (M3) RESOLVED (2026-07-17 re-score): §5 is M4/I8, granted on the CI-gated SliceCohesionTests (sealed subclass of the shared non-vacuous base, in the deploy-gating MMCA.Store.CI.slnf:53), the identical gate ADC credits at M4/I8. The layered-by-project hybrid remains a deliberate design choice, now correctly recorded as an implementation-axis cap (holds impl at 8), not a maturity deduction. Moved to the protect list.
          • +
          • #27 Internationalization N/A RETIRED (2026-07-02): ADR-027 superseded ADR-011; Store ships full en-US + es localization with the CI-gated TranslationCompletenessTests. §27 is scored and included in the indices. Updated 2026-07-03 (i18n completion sweep): §27 is M4/I8 with zero residual hard-coded literals (35 snackbars incl. cart/checkout/Stripe, 33 breadcrumb labels, nav items, both StoreHome hosts), a second CI gate (LocalizedTextConventionTests), the completeness floor raised 20→25, and MudBlazor chrome localized via the framework's ResxMudLocalizer. Impl 8→9 lever DONE (2026-07-11, remediation wave 6): Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PseudoLocalizationTests.cs extends the pseudo-loc text-expansion evidence to Store's own public pages (/, /catalog, /login): activates qps-Ploc via the production /culture/set cookie mechanism (the circuit handshake carries cookies, not query strings), asserts the [!! sentinel, Common's exact no-horizontal-overflow expression, and a per-page resx-owned en-US leak probe, plus a default-culture sentinel guard. No host/AppHost change needed; rides the deploy-gating chromium e2e-gate (first genuine run in CI). §27 Implementation 8→9 candidacy recorded for the next re-score. Candidacy GRANTED on the 2026-07-17 re-score (user-adjudicated: the lever's test is real and rides the deploy-gating chromium e2e-gate): §27 was M4/I9. REVERSED on the 2026-07-28 re-score: §27 is M4/I8 and is back in the implementation band. Not a regression, and not a withdrawal of the lever: PseudoLocalizationTests.cs:64,100 is intact and un-skipped and both arch gates still run in MMCA.Store.CI.slnf:53. The I9 was an over-grant because it scored the lever rather than the category: two of the rubric's five criteria are unmet in current code, namely culture-aware number formatting (Money.ToDisplayString() hard-codes a $ glyph and formats with CultureInfo.InvariantCulture, MMCA.Common .../MoneyExtensions.cs:20,41, an explicit rubric red flag) and mechanism-driven pluralization (the "{0} item(s)" / "{0} articulo(s)" workaround, CartDrawer.resx:20, ShoppingCartList.es.resx:11). The first pass proposed 7; 8 was adjudicated. Both defects live in shared MMCA.Common code, so the fix is [C→A] and the same deduction may apply to Common's and ADC's §27 at their next re-scores. Update 2026-08-14: the $-glyph half is FIXED (Common resolves the symbol from the price's own currency since 2026-08-05, MoneyExtensions.cs:18-20,54-59, re-anchored 2026-08-23, inside the v1.160.0 pin Store consumes); the CultureInfo.InvariantCulture amount formatting (:69-70) and the pluralization workaround remain, so §27 holds I8 and stays in the implementation band (re-verified 2026-08-23: CartDrawer.resx:20, ShoppingCartList.es.resx:11).
          • Single-region deployment: accepted in infra/DISASTER-RECOVERY.md (real load doesn't justify multi-region cost).
          • All per-service DBs on one physical SQL server: logical isolation complete; shared server for cost (minor §7/§8).
          • 2026-07-16 re-verification note: #9 (M4/I9) and #32 came back FLAG on the full re-score (first-pass scorers proposed regressions that the adversarial verify pass disproved against committed evidence). #9 stands at M4/I9. #32 was re-adjudicated the same day by the drift-analysis fold: a capability-level ADC comparison (adversarially verified) found no mechanism behind ADC's I9 that Store lacks, so §32 is now M4/I9; the earlier FLAG had reasoned from stale scorecard text (including a stale 49 lock-file count, actual 55).
          • ADR-043 adoption (mobile deep links / app association / native OAuth callback): recorded DEFERRED (2026-07-16 drift fold). The drift analysis lands this in Store, but adoption is feature-scale (Store-scheme deep links, iOS/Android manifest entries, associated domains) and rides the same trigger as the recorded ADR-042 latency: adopt when a Store MAUI surface is actively wanted. Not scheduled; revisit with the ADR-042 entry above.
          • #33 broker-parity tier SHIPPED (2026-07-16, mirrors ADC): Tests/Integration/MMCA.Store.ServiceBusEmulator.IntegrationTests runs MassTransit v8 against the official Service Bus emulator (pinned 2.0.1) with the real ProductVariantChanged contract, proving admin-plane topology creation + the AMQP round-trip nightly in cross-service-tests.yml (new job, same cross-service-freshness deploy gate). Closes the local-RabbitMQ vs prod-Service-Bus red flag with automation instead of documentation; §33 I8→9 candidacy recorded for the next re-score. Candidacy NOT granted on the 2026-07-28 re-score: the tier still exists but is nightly and non-gating, and its project (Tests/Integration/MMCA.Store.ServiceBusEmulator.IntegrationTests) is in neither solution filter, reaching the deploy chain only through the recency check (re-anchored 2026-08-14: the cross-service-freshness job at deploy.yml:668, in deploy needs at :862). §33 holds M4/I8 and stays in the implementation band.
          • ADR-044 adoption (native push, third notification channel): recorded DEFERRED (2026-07-16 drift fold). Store has no user-notification pipeline at all (no ADR-024 inbox, no SignalR channel), so ADR-044 adoption means adopting the whole notification stack first: a product decision, not remediation. Record here so the gap is conscious; schedule only if Store wants user notifications.
          • -
          • Chromium-only deploy E2E gate: accepted CI-cost trade-off (recorded 2026-07-28). The 2026-07-18 Actions-minute reduction cut the deploy-gating e2e-gate to a single engine (deploy.yml:483-495) and left firefox/webkit on the Mon/Thu advisory matrix (e2e.yml:124,131). The saving is real and the decision stands; what was missing was the record, so the ledger predicted a reopen at line 26 and in the protect list without anyone having decided anything. Recording the trade-off does not restore the score: the rubric's maturity 4 is "enforced automatically", and a convention-enforced check is a 3, so §22 is scored M3 and sits in the maturity band with a named lever. The two are complementary: the score reflects what CI enforces, this entry reflects why. Revisit if a webkit-only or firefox-only defect ever reaches production, which is the risk being priced. Cadence update (verified 2026-08-14): since 2026-07-29 the scheduled matrix runs ONE alternating engine per week (Mon firefox, Thu webkit; crons e2e.yml:37-47, engine selection :133-135), not both engines twice weekly, so the priced blind window per engine is now 7 days, wider than originally recorded.
          • +
          • Chromium-only deploy E2E gate: accepted CI-cost trade-off (recorded 2026-07-28). The 2026-07-18 Actions-minute reduction cut the deploy-gating e2e-gate to a single engine (re-anchored 2026-08-23: job at deploy.yml:537, rationale :539-543, browsers: '["chromium"]' at :547) and left firefox/webkit on the Mon/Thu advisory matrix (re-anchored 2026-08-23: engine selection e2e.yml:133-135, continue-on-error at :143). The saving is real and the decision stands; what was missing was the record, so the ledger predicted a reopen at line 26 and in the protect list without anyone having decided anything. Recording the trade-off does not restore the score: the rubric's maturity 4 is "enforced automatically", and a convention-enforced check is a 3, so §22 is scored M3 and sits in the maturity band with a named lever. The two are complementary: the score reflects what CI enforces, this entry reflects why. Revisit if a webkit-only or firefox-only defect ever reaches production, which is the risk being priced. Cadence update (verified 2026-08-14, re-confirmed 2026-08-23): since 2026-07-29 the scheduled matrix runs ONE alternating engine per week (Mon firefox, Thu webkit; crons e2e.yml:46-47, engine selection :133-135), not both engines twice weekly, so the priced blind window per engine is now 7 days, wider than originally recorded. Content gap noted 2026-08-23: this entry prices the ENGINE dimension only. The gate is also UI-SCOPED (deploy.yml:544) and a skipped e2e-gate does not block the deploy (deploy.yml:876-880), so on a backend-only merge the priced blind window is 100% for all three engines; that second, unrecorded hole is tracked as the TD under #21 in the maturity-band section, pending a decision to either enforce or accept it.
          • 2026-07-28 FLAG carry-forward (#19, #30). Both categories came back FLAG on the full re-score: first-pass proposals (#19 Implementation 8→9; #30 M4/I8→M3/I7) that the adversarial verify pass rejected against evidence re-read at HEAD 8d4af68c. Both hold their prior M4/I8 and their implementation-band rows are unchanged, still with no named lever. #19's rejection was specific: no §19 substance landed since the prior pin (the UI diff is culture-invariant string mechanics, payment-poll cadence tuning, and batched-lookup round-trip cuts, all §12/§23 work), and a minor red flag persists in a publicly settable IsDrawerOpen on the shared scoped state service, mutated directly by the component outside the notify path.
          • 2026-08-14 FLAG carry-forward (#5, #15, #17, #19, #20, #21). Six categories came back FLAG on the full re-score, every one an adversarial rejection of a proposed first-pass uplift against evidence re-read at HEAD 9571a963, none a found regression: #5 held I8 (horizontal folders inside module Application layers; generic-CRUD slices on shared framework handlers; only three bespoke query types), #17 held I9 (no pre-prod Bicep validation, SQL public network access, prod-only environment), #19 held I8 for the second consecutive cycle (IsDrawerOpen, now a named lever in its band row), #20 held I7 (the ProductList conversion covered ~3 of 34 occurrences), #21 held M3/I8 (placeholder SR log; one added dark-mode scan is a Strong-band increment). #15 is the one adjudicated case: the verify pass proposed a correction to I7 on three suppression-hygiene gaps (expired GHSA-2m69-gcr7-jv3q suppression, undocumented NoWarn codes, MAUI head outside CI); the user adjudicated a hold at the prior I8, and the three gaps are recorded as #15's named lever in the band table above.
          • +
          • 2026-08-23 FLAG carry-forward (#5, #6, #7, #9, #12, #20, #31). Seven categories returned FLAG on this re-score; none is a found regression and every one keeps its prior score under the merged-prior rule. #9 holds M4/I9 and stays on the protect list (a proposed downgrade was rejected: the contract-guard evidence base grew to seven files plus the frozen gRPC proto contract; only the scorecard row's narration was stale, now corrected). #12 holds M3/I8 and #20 holds M4/I7, both with their band rows and levers unchanged (re-verified this run: deploy.yml:613/:626-643 for #12, the 30 residual Style=/CellStyle= occurrences for #20). #6, #7, and #31 hold I8 with no lever-bearing evidence surfaced, so their band rows keep "not yet identified". #5 is the one special case: the scorer returned NO numbers at all (maturity null / implementation null), so its M4/I8 is carried forward unverified this cycle rather than re-established; treat #5 as owing a fresh read at the next re-score, not as re-confirmed.

          🟠 Below-maturity-4 tracking (inclusion policy: categories scoring < 4 maturity)

          These categories score maturity 3; the ledger records them per its own line-4 inclusion policy (mirrors ADC's equivalent entries).

          • #19 · State Management & Data Flow · maturity 3 → 4 (weight 3). DONE (2026-07-11, remediation wave 2). The §19 fitness gate now runs in the CI.slnf arch tier: StateManagementConventionTests (sealed subclass of the shared v1.115.0 StateManagementConventionTestsBase) reflects over the three module UI assemblies (now registered as Layer.Ui in StoreArchitectureMap) failing the build on any mutable static field or settable static property, plus a source scan forbidding singleton *StateService/*StateContainer registrations. Verified non-vacuous: a seeded mutable static in Catalog.UI failed the gate with the exact offender name, then green after removal. Maturity 4 GRANTED on the 2026-07-16 re-score (two-pass, adversarially verified; the proposed impl bump to 9 was rejected as an enforcement gain, not substance). Scorecard §19 is M4/I8; moved to the protect list.
          • #18 · UI Architecture & Components · maturity 3 → 4 (weight 3). DONE (2026-07-11, remediation wave 2). The §18 fitness gate now runs in the CI.slnf arch tier: UIArchitectureConventionTests (sealed subclass of the shared v1.115.0 UIArchitectureConventionTestsBase) caps every *.razor.cs under Source/ at 400 lines and inline @code blocks at 120 lines. Conformance shipped with the gate: OrderDetail.razor.cs 500 → 361 (extracted OrderSummaryPanel + OrderLinesPanel) and ProductDetail.razor.cs 491 → 340 (extracted ProductVariantsPanel), markup moved verbatim (DOM identical for the E2E selectors), all bUnit suites green. Verified non-vacuous via a seeded 402-line file. Maturity 4 GRANTED on the 2026-07-16 re-score (two-pass, adversarially verified; the proposed impl bump to 9 was rejected, impl holds 8 on the residual inline-style logic). Scorecard §18 is M4/I8; moved to the protect list.
          • -
          • [~] #12 · Performance & Scalability · maturity 3 → 4 (weight 2). LEVER STILL OPEN (marker corrected 2026-07-28: the [x] contradicted this entry's own closing text and #12 sits in both ranked bands; the wave-3 work below did ship, but the maturity candidacy it recorded was declined and has been declined again since). Wave-3 delivery (2026-07-11): Both halves of the lever are now enforced deploy preconditions: (a) a load-freshness job in deploy.yml's needs fails the deploy when the latest successful monthly load-test.yml run is older than 35 days (the dr-freshness pattern; latest run 2026-07-01, green), and (b) the WebVitals budgets were tightened from catastrophic-only (LCP 8000) to the Core Web Vitals "good" band (LCP 2500 / FCP 1800 / TTFB 800 / CLS 0.1 / INP 500), calibrated against measured CI maxima (LCP 172ms, 10-30x headroom), asserted inside the deploy-gating chromium e2e-gate. Maturity candidacy DECLINED on the 2026-07-16 re-score: the k6 load test itself runs monthly/on-demand, so it is capacity-planning evidence rather than a merge gate; the freshness gate bounds staleness but does not gate regressions. §12 stays M3/I8, lever OPEN: either record the monthly cadence as the accepted posture (ADC's stance) or add a latency-regression check to the merge path. Re-verified OPEN on the 2026-07-28 re-score, and both proposed moves (M3→4 and I8→9) were adversarially REJECTED: load-test.yml:17-18 is still monthly cron plus dispatch, deploy.needs (re-anchored 2026-08-14: deploy.yml:862) still contains no perf job, no perf fitness test exists in Tests/Architecture, and the one deploy-chain hook load-freshness (deploy.yml:613) gained a break-glass skip (:626-643), which loosens rather than tightens it. Re-verified unchanged on the 2026-08-14 re-score. The 2026-07-25 performance wave is real and verified but closed defects the prior I8 already assumed absent, and three efficiency gaps stay open (the sequential per-item cross-service gRPC loop in BulkSetInventoryHandler.cs:40-49 against the rubric's explicit no-N+1 criterion, plus the full-size image blobs). §23 split out and RESOLVED same day (drift-analysis fold, adversarially verified): its CWV budget assertions are per-deploy enforcement independent of k6's cadence, the identical evidence ADC's twentieth cycle credited, so scorecard §23 is M4/I8 and moves to the protect list.
          • -
          • #13 · Observability & Operability · maturity 3 → 4 (weight 2). DONE (2026-07-11, remediation wave 6). The dashboard half already existed (the saved store-slo-workbook Azure Monitor workbook mirrors the three SLO alerts per service); the missing runbook half landed as infra/OPERATIONS.md: each provisioned alert (failed-requests, server-response-time, dependency-failures) mapped to concrete triage steps (workbook pane, App Insights drill path, container logs, the Stripe/gRPC/outbox failure classes) plus fast-reference recovery moves (revision rollback, PITR restore, the freshness gates) and a pair-with-sloAlertSpecs governance note. Split verdict on the 2026-07-16 re-score: Implementation 8 → 9 GRANTED (both prior deductions closed: workbook infra/main.bicep:274 + runbook infra/OPERATIONS.md), but the maturity candidacy was DECLINED: dashboards/runbooks are IaC/review-enforced, and nothing in CI fails when an alert loses its runbook pairing. §13 stays M3/I9, lever OPEN: add a CI gate asserting the sloAlertSpecs-to-OPERATIONS.md pairing (mirrors ADC's reopened #13; one shared gate design can serve both repos). Gate SHIPPED same day (2026-07-16): Tests/Architecture/MMCA.Store.Architecture.Tests/ObservabilityConventionTests.cs (mirror of ADC's) machine-enforces the pairing in the CI.slnf arch gate: every sloAlertSpecs key needs a ### ...-alert-<key> runbook section carrying the alert's current (sev N), orphans fail, 3-spec non-vacuity floor, both files embedded. Verified red on a seeded severity drift, green on the real files. Maturity 3 → 4 candidacy recorded for the next re-score. Maturity 4 GRANTED on the 2026-07-17 re-score (ObservabilityConventionTests.cs:24,34 verified live in the CI.slnf arch gate, MMCA.Store.CI.slnf:52): §13 is M4/I9; moved to the protect list.
          • +
          • [~] #12 · Performance & Scalability · maturity 3 → 4 (weight 2). LEVER STILL OPEN (marker corrected 2026-07-28: the [x] contradicted this entry's own closing text and #12 sits in both ranked bands; the wave-3 work below did ship, but the maturity candidacy it recorded was declined and has been declined again since). Wave-3 delivery (2026-07-11): Both halves of the lever are now enforced deploy preconditions: (a) a load-freshness job in deploy.yml's needs fails the deploy when the latest successful monthly load-test.yml run is older than 35 days (the dr-freshness pattern; latest run 2026-07-01, green), and (b) the WebVitals budgets were tightened from catastrophic-only (LCP 8000) to the Core Web Vitals "good" band (LCP 2500 / FCP 1800 / TTFB 800 / CLS 0.1 / INP 500), calibrated against measured CI maxima (LCP 172ms, 10-30x headroom), asserted inside the deploy-gating chromium e2e-gate. Maturity candidacy DECLINED on the 2026-07-16 re-score: the k6 load test itself runs monthly/on-demand, so it is capacity-planning evidence rather than a merge gate; the freshness gate bounds staleness but does not gate regressions. §12 stays M3/I8, lever OPEN: either record the monthly cadence as the accepted posture (ADC's stance) or add a latency-regression check to the merge path. Re-verified OPEN on the 2026-07-28 re-score, and both proposed moves (M3→4 and I8→9) were adversarially REJECTED: the k6 run is still monthly cron plus dispatch (re-anchored 2026-08-23: cron load-test.yml:18, workflow_dispatch at :9), deploy.needs (re-anchored 2026-08-14: deploy.yml:862) still contains no perf job, no perf fitness test exists in Tests/Architecture, and the one deploy-chain hook load-freshness (deploy.yml:613) gained a break-glass skip (:626-643), which loosens rather than tightens it. Re-verified unchanged on the 2026-08-14 re-score and again on the 2026-08-23 re-score (the identical M3→4 uplift was adversarially rejected: the workflow files backing the axis are byte-unchanged since the cycle that first rejected it). The 2026-07-25 performance wave is real and verified but closed defects the prior I8 already assumed absent, and three efficiency gaps stay open (the sequential per-item cross-service gRPC loop in BulkSetInventoryHandler.cs:40-49 against the rubric's explicit no-N+1 criterion, plus the full-size image blobs). §23 split out and RESOLVED same day (drift-analysis fold, adversarially verified): its CWV budget assertions are per-deploy enforcement independent of k6's cadence, the identical evidence ADC's twentieth cycle credited, so scorecard §23 is M4/I8 and moves to the protect list.
          • +
          • #13 · Observability & Operability · maturity 3 → 4 (weight 2). DONE (2026-07-11, remediation wave 6). The dashboard half already existed (the saved store-slo-workbook Azure Monitor workbook mirrors the three SLO alerts per service); the missing runbook half landed as infra/OPERATIONS.md: each provisioned alert (failed-requests, server-response-time, dependency-failures) mapped to concrete triage steps (workbook pane, App Insights drill path, container logs, the Stripe/gRPC/outbox failure classes) plus fast-reference recovery moves (revision rollback, PITR restore, the freshness gates) and a pair-with-sloAlertSpecs governance note. Split verdict on the 2026-07-16 re-score: Implementation 8 → 9 GRANTED (both prior deductions closed: workbook infra/main.bicep:274 + runbook infra/OPERATIONS.md), but the maturity candidacy was DECLINED: dashboards/runbooks are IaC/review-enforced, and nothing in CI fails when an alert loses its runbook pairing. §13 stays M3/I9, lever OPEN: add a CI gate asserting the sloAlertSpecs-to-OPERATIONS.md pairing (mirrors ADC's reopened #13; one shared gate design can serve both repos). Gate SHIPPED same day (2026-07-16): Tests/Architecture/MMCA.Store.Architecture.Tests/ObservabilityConventionTests.cs (mirror of ADC's) machine-enforces the pairing in the CI.slnf arch gate: every sloAlertSpecs key needs a ### ...-alert-<key> runbook section carrying the alert's current (sev N), orphans fail, 3-spec non-vacuity floor, both files embedded. Verified red on a seeded severity drift, green on the real files. Maturity 3 → 4 candidacy recorded for the next re-score. Maturity 4 GRANTED on the 2026-07-17 re-score (ObservabilityConventionTests.cs:24,34 verified live in the CI.slnf arch gate, MMCA.Store.CI.slnf:53): §13 is M4/I9; moved to the protect list.

          🟢 Resolved this cycle (2026-07-28, drift wave: D1/D2/D5/D6/D7 + E2/E4/E7/E8)

            @@ -464,7 +467,7 @@

            Deferred from that program
          • TD · Batch the bulk-inventory existence check. BulkSetInventoryHandler validates each variant with its own sequential cross-service gRPC call. A 500-item ceiling now bounds it, but collapsing it needs a new IProductVariantService contract method (proto, adapter, service, and every fake). GetUnitPricesAsync cannot be reused for it: it drops variants whose Price is null, so an existing-but-unpriced variant would be reported missing and fail the request.
          • TD · Projected order-line count. The admin order grid loads every order line only to render OrderLines.Count. A LineCount DTO field does not help, because the generic query pipeline materializes entities before mapping; doing it properly means a persisted denormalized column maintained by the domain. Not proportionate for an admin grid.
          • TD · Product-image derivatives. Images are full-size DB blobs streamed as-is and rendered as card thumbnails with no srcset/dimensions, so a 12-card browse grid can pull 12 full-size assets. Fixing it is a storage-design decision, not a local change.
          • -
          • TD · Port ADC's expand/contract migration guard. DONE (verified 2026-07-28). The "Expand/contract migration guard (schema rollback safety)" step runs inside the required build-and-test job (deploy.yml:190, job at :91): it fails any PR whose newly added migration Up() body contains DropColumn/DropTable/DropIndex without an EXPAND-CONTRACT-OVERRIDE marker (:228-230), scope and rationale at :192-205, and fails closed when the base diff is unresolvable (:212-216) rather than passing vacuously. The policy is documented alongside the code at CONTRIBUTING.md:55. This is part of what lifted §8 to Implementation 9.
          • +
          • TD · Port ADC's expand/contract migration guard. DONE (verified 2026-07-28; anchors refreshed 2026-08-23, substance confirmed unchanged). The "Expand/contract migration guard (schema rollback safety)" step runs inside the required build-and-test job (deploy.yml:232, job at :126): it fails any PR whose newly added migration Up() body contains DropColumn/DropTable/DropIndex without an EXPAND-CONTRACT-OVERRIDE marker (:271-272), scope and rationale at :234-247, and fails closed when the base diff is unresolvable (:251-258) rather than passing vacuously. The policy is documented alongside the code at CONTRIBUTING.md:55. This is part of what lifted §8 to Implementation 9.

          🟢 Resolved this cycle (2026-07-11 drift-convergence, drift plan D1-D13)

            @@ -474,7 +477,7 @@

            🟢
          • #17 DevOps, MI-SQL ACTIVATED in prod (2026-07-12; wiring landed inert 2026-07-11 as drift plan D2). The full infra/SQL-MANAGED-IDENTITY.md sequence is complete: Stage 1 Entra admin live on the server (SQL_AAD_ADMIN_* repo vars), Stage 2 grants verified in all three DBs (mmca-prod-apps-identity as EXTERNAL_USER + db_owner in Store_Catalog/Store_Sales/Store_Identity), Stage 3 USE_MANAGED_IDENTITY_SQL=true deployed green (run 29192048197, full gate chain + post-deploy smoke). All three services now run passwordless Authentication=Active Directory Managed Identity; the SQL password path remains only as the documented dual-auth rollback. Mirrors ADC's 2026-06-28 activation; §17 Implementation 8→9 BANKED on the 2026-07-16 re-score (repo variable USE_MANAGED_IDENTITY_SQL=true + green run 29192048197 re-verified directly; the scorecard's stale inert claim corrected). The SQL public-network-access acceptance (no VNet) stands as documented.

          ✅ Already at level 4 (protect, don't regress)

          -

          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 (#13), Testability (#14), DevOps (#17), Front-End Security (#26), Resilience (#29), Dependency & Supply-Chain (#32), Architecture Governance (#34). #8 joined on the 2026-07-28 re-score (Implementation 8→9): the atomic conditional-UPDATE stock decrement with deterministic lock ordering (InventoryAllocationService.cs:70), its CK_InventoryItem_AvailableQuantity_NonNegative schema backstop (InventoryItemConfiguration.cs:27), the single-transaction checkout write phase (CheckOutHandler.cs:91), the fail-closed expand/contract migration guard in the required build-and-test check (deploy.yml:190), and the raw-IQueryable ban with an empty allowlist (RawQueryableConventionTests.cs:14 in MMCA.Store.CI.slnf:52). Protecting it means keeping the decrement atomic and the CHECK constraint in place; the honest residual is that Identity has no concurrency round-trip test.

          +

          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 (#13), Testability (#14), DevOps (#17), Front-End Security (#26), Resilience (#29), Dependency & Supply-Chain (#32), Architecture Governance (#34). #8 joined on the 2026-07-28 re-score (Implementation 8→9): the atomic conditional-UPDATE stock decrement with deterministic lock ordering (InventoryAllocationService.cs:70), its CK_InventoryItem_AvailableQuantity_NonNegative schema backstop (InventoryItemConfiguration.cs:27), the single-transaction checkout write phase (CheckOutHandler.cs:91), the fail-closed expand/contract migration guard in the required build-and-test check (deploy.yml:232, re-anchored 2026-08-23), and the raw-IQueryable ban with an empty allowlist (RawQueryableConventionTests.cs:14 in MMCA.Store.CI.slnf:53). Protecting it means keeping the decrement atomic and the CHECK constraint in place; the honest residual is that Identity has no concurrency round-trip test.

          Maturity 4 but implementation <= 8, so still ranked in the implementation band above: Vertical Slice (#5), CQRS (#6), Microservices (#7), Cross-Cutting (#10), Security (#11), Code Quality (#15), Maintainability (#16), UI Architecture (#18), State Management (#19), Design System (#20), Front-End Performance (#23), Forms (#24), Navigation (#25), i18n (#27), Front-End Testing (#28), Compliance/Privacy (#30), FinOps (#31), DevEx (#33). Closing on maturity alone is exactly what let the two indices drift apart, so these stay visible rather than disappearing into the protect list.

          Below maturity 4: #12, #21, and #22 (see the maturity band above).

          History: #18 and #19 reached maturity 4 on the 2026-07-16 re-score via the CI-gated convention fitness tests; #5, #13, and #22 reached maturity 4 on the 2026-07-17 re-score; #28 reached maturity 4 on 2026-07-03 via the E2E/axe deploy gate, and #24 on 2026-07-11 via FormsConventionTests (D11); #21 Accessibility was corrected back to maturity 3 on 2026-07-11 (D6), pending a recorded screen-reader pass; #16/#20/#25/#27 reached maturity 4 in the 2026-07-02 docs re-score on gates shipped 2026-07-01; #32 and #34 reached maturity 4 on 2026-06-26. Drift watch resolved (2026-07-23 → 2026-07-28): #22's granted basis went stale on 2026-07-18 when the deploy e2e-gate was cut to chromium-only, and the predicted reopen happened on the 2026-07-28 re-score. #22 is now M3/I8, out of this list and into the maturity band; the cost trade-off behind it is recorded under Deliberate / accepted.

          diff --git a/sitemap.xml b/sitemap.xml index e76f3a2..6aff4a8 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -747,12 +747,12 @@ https://ivanball.github.io/docs/governance/store-ArchitectureScorecard.html - 2026-08-14 + 2026-08-23 0.6 https://ivanball.github.io/docs/governance/store-RemediationBacklog.html - 2026-08-18 + 2026-08-23 0.6