From e61219c98822d2abf052e6ff4d92c33b02757f39 Mon Sep 17 00:00:00 2001 From: Ivan Ball-llovera Date: Sun, 23 Aug 2026 10:27:49 -0400 Subject: [PATCH] docs: re-score adc ArchitectureScorecard + reconcile backlog (27th cycle) Twenty-seventh-cycle full 34-category re-score at pin v1.160.0, HEAD 96f0919a. Two implementation down-moves: S4 DDD 9->8, S22 Responsive 8->7. Eight proposed lifts adversarially rejected. Indices: Maturity 97.2% (unchanged) / Implementation 85.6% -> 85.0% (680/800). Backlog: S4 enters the implementation band, S22 joins S15 at the top, new TD-20 (conditional e2e-gate) with a Deliberate/accepted amendment, integration-tier "gates every deploy" wording corrected to PR-gating, TD-16 re-measured (eight files in the 38-line band). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016usKhixm2drfHmwmmmqwbn --- assets/data/search-index.json | 2 +- .../governance/adc-ArchitectureScorecard.md | 22 ++--- docs-src/governance/adc-RemediationBacklog.md | 70 +++++++------- .../governance/adc-ArchitectureScorecard.html | 36 ++++---- docs/governance/adc-RemediationBacklog.html | 91 ++++++++++--------- platform.html | 4 +- sitemap.xml | 4 +- 7 files changed, 120 insertions(+), 109 deletions(-) diff --git a/assets/data/search-index.json b/assets/data/search-index.json index 6bec396..956bf04 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 PseudoLocalizationTests RemediationBacklog.md"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#executive-summary","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Executive summary","x":"MMCA.ADC is a mature, evidence-driven .NET 10.0 (LangVersion preview) DDD/Clean Architecture system for the Atlanta Developers Conference, deliberately extracted from a modular…","i":"MMCA.ADC.CrossService.IntegrationTests MMCA.ADC.Notification.IntegrationTests SessionIncludeChildrenRegressionTests UIArchitectureConventionTestsBase FrameworkVersionConsistencyTests LocalizedTextConventionTestsBase MMCA.Common.Testing.Architecture ErrorMessages.ValidationError LocalizedTextConventionTests ObservabilityConventionTests SpecificationConventionTests BlazorCspPolicyProvider.cs"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#scorecard","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted column = Maturity·weight / Implementation·weight per row. Axis-gap findings: the former §21 gap (excellent-but-not-enforced, impl 7 over maturity 2) closed on 2026-07-02…","i":"MmcaCultureBootstrap.SetBrowserCultureAsync ResilienceCircuitBreakerFaultInjectionTests MMCA.ADC.CrossService.IntegrationTests dotnet_analyzer_diagnostic.severity FrameworkVersionConsistencyTests.cs StateManagementConventionTestsBase MMCA.ADC.Notification.Application UIArchitectureConventionTestsBase MMCA.Common.Testing.Architecture ConstructorDependencyCountTests LocalizedTextConventionTests.cs ObservabilityConventionTests.cs"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#indices","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Indices","x":"- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 311 ÷ 320 = 97.2% (down from 97.8%, 313/320: the twenty-second-cycle full re-score (2026-07-21, pin v1.121.0) corrected §22…"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#top-5-strengths","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 strengths","x":"1. Architecture intent is executable, not prose: 29 architecture-test classes / 91 methods gate CI (§34 Governance, mat 4 / impl 9, and §3 Clean Architecture, mat 4 / impl 9):…","i":"SessionIncludeChildrenRegressionTests MMCA.Common.Testing.Architecture SpecificationConventionTests.cs AddSessionCookieAuthentication StateManagementConventionTests MicroserviceExtractionTests AddCommonSecurityHeaders ArchitecturalAnalysis.md LayerDependencyTests AddCommonBlazorCsp DataResidencyTests DomainPurityTests"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#top-5-risks","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Top 5 risks","x":"Expected-delta note (updated 2026-08-01): several entries below record an expected lift of \"impl 9→10\". Under the 2026-08-01 recalibration those are attainable, not aspirational:…","i":"publicNetworkAccess packages.lock.json MMCA.ADC.CI.slnf deploy.needs maxReplicas MMCA.ADC.UI CI.slnf"},{"u":"/docs/governance/adc-ArchitectureScorecard.html#cross-repo-comparison","d":"MMCA.ADC: Architecture Scorecard","k":"Architecture Governance","t":"Cross-repo comparison","x":"How this repo's quality relates to the other consumers (where the framework's quality propagates, where a repo diverges or under-uses it, and where a consumer does better than…"},{"u":"/docs/governance/adc-RemediationBacklog.html","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","x":"Derived from ArchitectureScorecard.md (single-axis 0-4, baseline 75%, 241/320, dated 2026-06-08). Current authoritative two-axis scores (twenty-sixth-cycle full re-score,…","i":"MMCA.ADC.CrossService.IntegrationTests SqlServerIntegrationTestFixtureBase SessionSelectionDashboard.razor.cs MMCA.ADC.Notification.Application FrameworkVersionConsistencyTests StateManagementConventionTests UIArchitectureConventionTests IntegrationTestReworkPlan.md LocalizedTextConventionTests ObservabilityConventionTests TranslationCompletenessTests MMCA.ADC.Integration.slnf"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-6-highest-leverage","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔴 Priority 6: highest leverage","x":"Token handling uses two rubric-named anti-patterns, with no CSP defense-in-depth. Status (2026-06-27): cookie-only refresh + in-memory access (auth-path BFF), OAuth…","i":"ResilienceCircuitBreakerFaultInjectionTests DisconnectedCircuitRetentionPeriod ManagementRouteAuthorizationTests GatewaySecurityHeadersMiddleware E2E_LIFT_REGISTRATION_THROTTLE OAuthController.CompleteAsync OAuthController.ExchangeAsync SameOriginProxyTokenRefresher MMCA.ADC.Conference.UI.Tests AuthenticationStateProvider EventDetailPage.StatusChip InvalidOperationException"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-4","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟠 Priority 4","x":"The (4−score)×weight formula puts this at 4, but the High flag is a contractual/regulatory exposure that contradicts a shipped, publicly-served policy: treat it as do-soon. -…","i":"user_notification_export.proto LocalizedTextConventionTests TranslationCompletenessTests user_engagement_export.proto ExportUserDataHandlerTests ErasureAndPiiLoggingTests DeleteUserHandlerTests ErrorMessages.Success SessionQuestionAnswer User.PreferredCulture UserRegisteredHandler EventQuestionAnswer"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-3-score-3-weight-3-one-rung-from-a-4","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟡 Priority 3: score 3, weight 3 (one rung from a 4)","x":"- ~~(High) The 258-test Testcontainers integration tier references the deleted MMCA.ADC.WebAPI host, won't build, and is excluded.~~ RESOLVED: reworked as per-service…","i":"Update_WithStaleRowVersion_ReturnsConflict MMCA.ADC.CrossService.IntegrationTests SessionSelectionDashboard.razor.cs StateManagementConventionTestsBase ManagementRouteAuthorizationTests UIArchitectureConventionTestsBase InProcessEventBus.PublishAsync SessionSelectionSpeakerOverlap StateManagementConventionTests UIArchitectureConventionTests DbUpdateConcurrencyException PublicSessionList.razor.cs"},{"u":"/docs/governance/adc-RemediationBacklog.html#priority-2-score-3-weight-2-polish--hardening","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Priority 2: score 3, weight 2 (polish / hardening)","x":"- ~~(Medium) No OpenAPI served by any running service, yet CLAUDE.md still advertises 4 doc UIs (/swagger, /nswag-swagger, /api-docs, /scalar/v1).~~ - [x] Serve OpenAPI per…","i":"Microsoft.AspNetCore.Authorization.Authorize AuthorizationPolicies.RequireOrganizer MMCA.ADC.Conference.IntegrationTests ManagementRouteAuthorizationTests FrameworkVersionConsistencyTests IdentityRouteAuthorizationTests IntegrationEventContractTests MMCA.ADC.Migrations.SqlServer Microsoft.AspNetCore.OpenApi ObservabilityConventionTests MicroserviceExtractionTests Validation.CorrectFollowing"},{"u":"/docs/governance/adc-RemediationBacklog.html#implementation-band-implementation--8-ranked-by-implpriority","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🔵 Implementation band (implementation <= 8, ranked by implPriority)","x":"Added 2026-07-28 when the ledger gained its second ranked axis. Until then implementation gaps appeared only as unranked sub-bullets inside maturity items, so they were never…","i":"SessionSelectionDashboard.razor.cs MMCA.ADC.Notification.Application AdcArchitectureMap.DefineLayers ConferenceCategoryDetail.razor HappeningNow.razor.cs TreatWarningsAsErrors DeviceSettings.razor SponsorCreate.razor SponsorDetail.razor System.Private.Uri workflow_dispatch UI.Web.Client"},{"u":"/docs/governance/adc-RemediationBacklog.html#resolved-2026-07-25-performance-program-2","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"🟢 Resolved 2026-07-25 (performance program 2)","x":"Second evidence-led performance pass over Common/ADC/Store. ADC's share shipped as three PRs plus the v1.127.0 framework sweep. - [x] Output cache shared across replicas.…","i":"Session.SessionQuestionAnswers Event.EventQuestionAnswers SessionQuestionViewBuilder CategoryItemLookupService SessionScoringProcessor SpeakerDashboardService SessionQuestionAnswers EventQuestionAnswers SessionCategoryItems SpeakerCategoryItems GetOpenPollsHandler PublicSessionDetail"},{"u":"/docs/governance/adc-RemediationBacklog.html#deliberate--accepted-recorded-decisions-not-scheduled-work","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"Deliberate / accepted (recorded decisions, not scheduled work)","x":"Conscious, recorded choices, not pending work (the former TECHDEBT.md accepted-risk section): - Single-region deployment (no multi-region failover): accepted in…","i":"SessionRoomScheduling.ValidateRoomAssignmentAsync MMCA.ADC.Notification.Application ConstructorDependencyCountTests LocalizedTextConventionTests TranslationCompletenessTests ArchitecturalAnalysis.md PseudoLocalizationTests AuthenticationService BrandColorTokenTests DeviceSettings.razor skip_freshness_gates SliceCohesionTests"},{"u":"/docs/governance/adc-RemediationBacklog.html#already-at-level-4-protect-dont-regress","d":"MMCA.ADC: Architecture Remediation Backlog","k":"Architecture Governance","t":"✅ Already at level 4: protect, don't regress","x":"1 SOLID · 2 Design Patterns · 3 Clean Architecture · 4 Domain-Driven Design · 5 Vertical Slice Architecture · 6 CQRS & Event-Driven · 7 Microservices Readiness · 8 Data…","i":"AnthropicScoringService.ScoreSessionAsync MMCA.ADC.CrossService.IntegrationTests GetSessionSelectionDashboardHandler SessionSelectionDashboard.razor.cs GetSpeakerSessionOverlapHandler GetCategoryDistributionHandler Session.AddSessionCategoryItem Session.CategoryItem.Duplicate Speaker.AddSpeakerCategoryItem Speaker.CategoryItem.Duplicate ObservabilityConventionTests OperationCanceledException"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html","d":"Architecture Evaluation Criteria","k":"Architecture Governance","x":"A structured rubric for evaluating the architecture of an enterprise application. Each category defines what is being assessed, concrete criteria to check, red flags that signal…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#how-to-use-this-rubric","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"How to Use This Rubric","x":"Score each category 0–4. Use the same scale everywhere so totals are comparable. Alongside the maturity level, rate how well each category is actually implemented on a finer 0–10…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#1-solid-principles","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"1. SOLID Principles","x":"Intent: Object/module-level design discipline that keeps code flexible and decoupled. Criteria - SRP: each class/handler has one reason to change; no \"god\" services orchestrating…","i":"NotSupportedException switch new"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#2-design-patterns","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"2. Design Patterns","x":"Intent: Appropriate, idiomatic use of patterns, solving real problems, not pattern theater. Criteria - Creational (Factory methods on entities, Builder, Options) used where…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#3-clean-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"3. Clean Architecture","x":"Intent: Dependencies point inward; business rules are independent of frameworks, UI, and data stores. Criteria - Dependency rule enforced: Domain → (nothing); Application →…","i":"JsonProperty DbContext Table"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#4-domain-driven-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"4. Domain-Driven Design","x":"Intent: The model reflects the business; boundaries follow capability boundaries, not technical layers. Criteria - Ubiquitous language: type/method names match business terms…","i":"decimal Result string Guid"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#5-vertical-slice-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"5. Vertical Slice Architecture","x":"Intent: Code is organized by feature/capability, so a change touches one cohesive slice. Criteria - Features grouped by use case (command/query + handler + validator + DTO…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#6-cqrs--event-driven-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"6. CQRS & Event-Driven Design","x":"Intent: Reads and writes are separated where it pays off; integration via events is reliable. Criteria - Commands (mutate, return Result) and queries (read, side-effect-free) are…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#7-microservices-readiness","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"7. Microservices Readiness","x":"Intent: Whether services (or future-extractable modules) are independently deployable and own their data. Criteria - Service boundaries align with bounded contexts; one team can…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#8-data-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"8. Data Architecture","x":"Intent: Persistence, consistency, and migrations are deliberate and safe. Criteria - Transaction boundaries match aggregate boundaries; unit-of-work scope is clear. - Migrations…","i":"Include"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#9-api--contract-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"9. API & Contract Design","x":"Intent: External and inter-service contracts are clear, stable, and evolvable. Criteria - Consistent resource/endpoint design (REST/minimal APIs/gRPC) with predictable shapes. -…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#10-cross-cutting-concerns","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"10. Cross-Cutting Concerns","x":"Intent: Validation, caching, resilience, configuration, and mapping are centralized and consistent. Criteria - Validation, logging, transactions handled by pipeline…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#11-security","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"11. Security","x":"Intent: AuthN/AuthZ, secrets, and data protection are correct by construction. Criteria - Authentication centralized; tokens validated; identity flows documented (e.g.,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#12-performance--scalability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"12. Performance & Scalability","x":"Intent: The system meets latency/throughput goals and scales horizontally. Criteria - Async I/O throughout; no sync-over-async; no blocking the request thread. - Hot-path query…","i":"Result Wait"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#13-observability--operability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"13. Observability & Operability","x":"Intent: You can understand and operate the system in production. Criteria - Structured logging with correlation/trace IDs flowing across module/service boundaries. - Distributed…","i":"Console.WriteLine"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#14-testability--test-strategy","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"14. Testability & Test Strategy","x":"Intent: The design supports fast, reliable, meaningful tests at the right levels. Criteria - Healthy test pyramid: many fast unit tests on domain/application, fewer integration,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#15-best-practices--code-quality","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"15. Best Practices & Code Quality","x":"Intent: Day-to-day craftsmanship that keeps the codebase healthy. Criteria - Analyzers at error severity (style, security, threading, maintainability) enforced in CI;…","i":"disable warning pragma"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#16-maintainability--evolvability","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"16. Maintainability & Evolvability","x":"Intent: The system absorbs change cheaply and ages well. (The governance/documentation depth behind this (ADRs, fitness functions, diagrams) is scored separately in §34.)…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#17-devops--deployment","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"17. DevOps & Deployment","x":"Intent: Building, releasing, and provisioning are automated, repeatable, and safe. (The local developer experience / inner loop behind this (local orchestration, cross-repo dev,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#18-ui-architecture--component-design","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"18. UI Architecture & Component Design","x":"Intent: Components are cohesive, reusable, and composed cleanly, the UI has a deliberate structure, not page-sized blobs. Criteria - Container/presentational split: smart…","i":"EventCallback ShouldRender razor key"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#19-state-management--data-flow","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"19. State Management & Data Flow","x":"Intent: Client state has a clear owner and predictable flow; server state is cached and invalidated deliberately. Criteria - Single source of truth per piece of state; ownership…","i":"StateHasChanged IsDirty"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#20-design-system-theming--ui-consistency","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"20. Design System, Theming & UI Consistency","x":"Intent: A coherent visual language enforced by a component library, not re-implemented per screen. Criteria - Component library used consistently (e.g., MudBlazor): teams build…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#21-accessibility-a11y","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"21. Accessibility (a11y)","x":"Intent: The UI is usable by everyone, including assistive-technology users, and ideally enforced, not aspirational. Criteria - Semantic structure: correct…","i":"span div"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#22-responsive-design--cross-browserdevice","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"22. Responsive Design & Cross-Browser/Device","x":"Intent: The UI works across viewport sizes, input modes, and supported browsers. Criteria - Fluid/responsive layouts via the design system's grid/breakpoints; no fixed-width…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#23-front-end-performance--rendering","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"23. Front-End Performance & Rendering","x":"Intent: The UI loads and responds fast; rendering work is bounded. (Complements §12: this is the client side.) Criteria - Initial load: bundle/payload size controlled;…","i":"ShouldRender key"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#24-forms-validation--ux-safety","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"24. Forms, Validation & UX Safety","x":"Intent: Data entry is safe, forgiving, and consistent, users don't lose work or get confused by errors. Criteria - Validation parity: client-side validation for fast feedback…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#25-navigation-routing--information-architecture","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"25. Navigation, Routing & Information Architecture","x":"Intent: Users can find their way; routes are meaningful, guarded, and role-aware. Criteria - Route design: clean, bookmarkable, deep-linkable URLs; parameters typed and…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#26-front-end-security","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"26. Front-End Security","x":"Intent: The client doesn't become the weak link, XSS, token handling, and trust boundaries are correct. (Complements §11.) Criteria - Output encoding / XSS: no unsanitized HTML…","i":"MarkupString innerHTML"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#27-internationalization--localization","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"27. Internationalization & Localization","x":"Intent: The UI can be translated and respects culture, if in scope. (Score weight 0–1 if single-locale by design.) Criteria - Externalized strings: UI text in resource files, not…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#28-front-end-testing--quality","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"28. Front-End Testing & Quality","x":"Intent: The UI is verified at the right levels with stable, meaningful tests. (Complements §14.) Criteria - Component tests (e.g., bUnit) for rendering logic, parameters, events,…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#29-resilience-reliability--business-continuity","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"29. Resilience, Reliability & Business Continuity","x":"Intent: The system survives partial failure and recovers from disaster within defined objectives. (Extends the resilience facets of §7/§12 into a first-class recovery story.)…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#30-compliance-privacy--data-governance","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"30. Compliance, Privacy & Data Governance","x":"Intent: Personal and regulated data is classified, governed, and handled lawfully across its lifecycle. (§11 defends against attackers; this answers to regulators.) Criteria -…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#31-cost-efficiency--finops","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"31. Cost Efficiency / FinOps","x":"Intent: Cloud spend is proportional to value and driven by data, not guesswork. (§17 mentions cost; this makes it a first-class axis.) Criteria - Right-sizing: compute/database…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#32-dependency--supply-chain-management","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"32. Dependency & Supply-Chain Management","x":"Intent: Third-party and inter-package dependencies are controlled, auditable, and evolve safely, especially critical for a framework that publishes packages. (Elevates §15's…"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#33-developer-experience--inner-loop","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"33. Developer Experience & Inner Loop","x":"Intent: Developers build, run, test, and iterate locally with fast, low-friction feedback. (Promoted out of §17: that scores release/ops automation; this scores the inner loop.)…","i":"editorconfig local.props"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#34-architecture-governance--documentation","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"34. Architecture Governance & Documentation","x":"Intent: Decisions are recorded, conformance is enforced, and the system is documented so it stays coherent as it evolves. (Promoted out of §16: that scores the property of…","i":"CLAUDE.md"},{"u":"/docs/governance/ArchitectureEvaluationCriteria.html#appendix-quick-scan-checklist","d":"Architecture Evaluation Criteria","k":"Architecture Governance","t":"Appendix: Quick-Scan Checklist","x":"A 2-minute triage before the full evaluation: any \"no\" warrants a deeper look. - [ ] Can you draw the dependency graph and is it acyclic and inward-pointing? - [ ] Is the domain…"},{"u":"/docs/governance/common-ArchitectureScorecard.html","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","x":"Canonical, version-controlled scorecard for this repo: the single source of truth for MMCA.Common's architecture scores (replaces the former single-axis snapshot; see git…","i":"ResilienceCircuitBreakerFaultInjectionTests SpecificationsDoNotNavigateToOtherEntities CrossSourceSpecification.BuildAsync BaseIntegrationEvent.SchemaVersion AggregateRootEntityControllerBase ArchitectureEvaluationCriteria.md DomainInvariantViolationException LocalizedTextConventionTestsBase MMCA.Common.Testing.Architecture OpenIdConnectMetadataWarmupTask ResourceTranslationsAreComplete EventVersioningConventionTests"},{"u":"/docs/governance/common-ArchitectureScorecard.html#scorecard","d":"MMCA.Common: Architecture Scorecard","k":"Architecture Governance","t":"Scorecard","x":"Weighted column = Maturity·weight / Implementation·weight per row. Axis-gap findings: §17/§8 are mature-but-execution-deferred (mechanism shipped, deeper proof lives downstream);…","i":"SpecificationsDoNotNavigateToOtherEntities CrossSourceSpecification.BuildAsync BaseIntegrationEvent.SchemaVersion TransportDoesNotLeakIntoCoreLayers ArchitectureEvaluationCriteria.md CrossDataSourceDegradeConvention MMCA.Common.Testing.Architecture OpenIdConnectMetadataWarmupTask 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 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 diff --git a/docs-src/governance/adc-ArchitectureScorecard.md b/docs-src/governance/adc-ArchitectureScorecard.md index f8ad3f0..3dd4c6f 100644 --- a/docs-src/governance/adc-ArchitectureScorecard.md +++ b/docs-src/governance/adc-ArchitectureScorecard.md @@ -2,13 +2,13 @@ > **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). 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/adc-RemediationBacklog.md) (the single ledger: the former `TECHDEBT.md` tactical `TD-NN` register was folded in 2026-06-26); 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 (HEAD `19021d93`, clean tree); framework dependency pinned at **MMCA.Common.* v1.152.0** (all 15 packages, lockstep, `Directory.Packages.props:92-110`; the canonical ADR set is **001-078**, indexed in `../adr/README.md`; framework-wide facts in `../MMCA.Common/FACTS.md`). **What moved this cycle (twenty-sixth-cycle full 34-category re-score, 2026-08-14, pin v1.152.0): no score moves.** All 34 categories were re-confirmed at their prior Maturity/Implementation from evidence read this run (26 rescored first-pass, 8 adversarially adjudicated). All eight adjudications were proposed lifts and all eight were **rejected** as verified non-moves, a second consecutive cycle in which every proposed lift landed one criterion short. **§5 holds 4/8** (the rubric's first §5 criterion wants the **DTO** in the slice, and ADC's DTOs still sit in the Shared assembly with their mappers in horizontal `Application/{Aggregate}/DTOs/` folders beside horizontal `Validation/`, `Specifications/` and `DomainEventHandlers/`; `AdcArchitectureMap.cs:12-43` still carries no `Module("Notification", ...)` entry, so enforcement covers 3 of the 4 modules: that half is now named as backlog **TD-19**). **§7 holds 4/8** (the bidirectional sync-gRPC red flag that caps it did not close, it **broadened** to a second pair, Identity-Notification, across 7 sync client registrations in 4 services, and the same map omission leaves the transport-at-the-edge guard covering 3 of 4 services). **§12 holds 3/8** (a maturity 3→4 was proposed; `git log` since the prior cycle's HEAD shows **zero** commits touching `.github/workflows/load-test.yml`, `deploy.yml` or `Tests/Load/`, so the blocker four prior cycles cited, a backend capacity proof executed out of band behind a recency-only deploy gate, is byte-for-byte intact). **§13 holds 4/9** (a 9→10 was proposed a second time; runbooks are still missing for three of the six ENABLED production alerts, including the single severity-1 gateway-availability alert at `infra/main.bicep:496-502`, declared outside the `sloAlertSpecs` array the pairing gate parses). **§15 holds 4/7** (all three grounds of the twenty-fourth-cycle downgrade are intact in current source, and the expired SQLite suppression is further past its own written removal condition than when it was recorded: the row cited v1.135.0, the pin is now **v1.152.0**, seventeen releases past the v1.121.0 sweep). **§23 holds 4/8** (both halves of the category's own named lever, WASM code-split and image optimization, are verified still open, so Initial load and Asset hygiene stay short of reference quality). **§28 holds 4/8** (the genuinely new state-management bUnit coverage is real but is a within-band improvement, not a band change, and two rubric criteria still carry concrete unmet elements). **§31 holds 4/8** (the conference-day surge is still a manual scale-up with a manual reset instruction, `cost-guard.yml:4`, `:83`, and no automated or scheduled revert exists, so "reversible scale events" is unmet in its exact terms; the new daily ACR purge and the production metrics-instrument suppression strengthen other criteria without closing that one). Beyond the scores this is an anchor-and-provenance pass: the pin (v1.135.0→**v1.152.0**), HEAD (`995a7886`→`19021d93`) and ADR range (001-064→**001-078**) are updated, and the drifted citations corrected in place are the §5 architecture-map range, the §13 sev-1 alert (`infra/main.bicep:481`→`:496-502`), the §21 axe coverage (17 pages→**31 axe test methods over ~29 distinct pages**, `AccessibilityTests.cs:21-365`, including the conference-day surfaces), the §27 routable-page denominator (37→**49** `@page` files under `Source`, 48 excluding the MAUI-only `DeviceSettings.razor`, so the deferred lift's cost figure rises from roughly 34 pages to roughly 45), the §18 code-behind measurements and the §33 `cross-service-tests.yml` job anchors. **TD-16 was re-measured:** the high-water code-behind rose 395→**398** of the 400 cap (`SessionSelectionDashboard.razor.cs`), so headroom narrowed from 5 lines to 2. Indices unchanged: **Maturity 97.2% (311/320) / Implementation 85.6% (685/800)**, re-summed this run. **The twenty-fifth-cycle record follows for provenance (2026-08-01, pin v1.135.0):** no score moves. All 34 categories re-confirmed at their prior Maturity/Implementation from evidence read this run (28 rescored first-pass, 6 adversarially adjudicated). All six adjudications were proposed implementation lifts and all six were **rejected** as verified non-moves, which is itself the finding: the implementation axis is not stalling for want of effort, it is sitting one criterion short in six independent places. **§5 holds 4/8** (the rubric's first §5 criterion wants command + handler + validator + **DTO** in the slice; ADC's DTOs live in the Shared assembly, `Conference.Shared/Events/EventDTO.cs`, with their mappers in a horizontal `Application/Events/DTOs/`, alongside horizontal `Validation/`, `Specifications/` and `DomainEventHandlers/` folders; the deliberate layered-by-project hybrid is unchanged, and `AdcArchitectureMap.cs:12-44` still omits `MMCA.ADC.Notification.Application` from the enforced set, so a stranded handler added there would not be caught: benign today, not reference-quality enforcement). **§13 holds 4/9** (a 9→10 was proposed; three ENABLED production alerts carry no runbook triage section and sit outside the CI pairing gate's scope, including the single severity-1 gateway-availability alert at `infra/main.bicep:481`, so the rubric's "runbooks for common failures" criterion is unmet: that is a real gap, not the trivial polish the top rung now allows). **§24 holds 4/8** (the prior cycle's named 8→9 lever, a bUnit render-level assertion of the summary's error items, genuinely shipped and is CI-gated, but a fresh read finds two criteria only partially met: client validation does not mirror the server's cross-field and format rules, which is the category's first criterion *and* its first red flag, and the form-level error summary is present on 7 of the 15 MudForm forms). **§27 holds 4/8 for a fourth consecutive rejection** on byte-identical pseudo-localization evidence, plus one newly citable culture-aware-formatting violation not in the prior row's prose. **§31 holds 4/8** (its own stated 8→9 lever, automating the conference-day surge and revert, is demonstrably not pulled, so "reversible scale events" stays unmet in its exact terms). **§33 holds 3/8 for a second consecutive rejection**: the lift rested on the broker-parity red flag being closed, and it is not: the AppHost still provisions RabbitMQ only, while the Azure Service Bus proof restored to the weekday nightly on 2026-07-29 is `continue-on-error` and explicitly rides no gate (`cross-service-tests.yml:144-149`; `cross-service-freshness` keys off the `cross-service` job, `deploy.yml:663`), so it is advisory, not closure, and the README sentence used to justify the lift is itself inaccurate about the gate. The below-maturity-4 set stays **§12/§21/§22/§33** and §15 stays the only implementation score below 8. The v1.132.0 through v1.135.0 lockstep sweeps and the 2026-08-01 BugHunt remediation (ADC PR #94) moved no score. Beyond the scores, this cycle is an anchor-and-provenance pass: the pin (v1.131.0→**v1.135.0**), HEAD (`2ec77796`→`995a7886`), ADR range (001-060→**001-064**) and roughly thirty drifted `path:line` citations were corrected in place against current source, and the TD-16/TD-17 ledger entries were re-measured (see `RemediationBacklog.md`). Indices unchanged: **Maturity 97.2% (311/320) / Implementation 85.6% (685/800)**, re-summed this run. **The twenty-fourth-cycle record follows for provenance (2026-07-28, pin v1.131.0):** one score moves, and it moves down. **§15 Best Practices & Code Quality Implementation 8→7** (Maturity 4 holds): three gaps verified in current source, none of which the prior row's basis ("only justified/tracked suppressions", "documented/dated NoWarn") still describes correctly. (1) The SQLite audit suppression `GHSA-2m69-gcr7-jv3q` is expired by its own written removal condition (`Directory.Build.props:49-51`, whose comment at `:41-48` states it "stays only until the next MMCA.Common release and consumer pin sweep carry the fix through the published package graph, then it must be removed", updated 2026-07-20): ADC is now pinned at v1.131.0 (`Directory.Packages.props:123`), ten releases past the v1.121.0 SQLite sweep, MMCA.Common has removed its own suppression and pins the patched bundle directly (`MMCA.Common/Directory.Packages.props:39,42`, `SQLitePCLRaw.bundle_e_sqlite3` 3.0.5), and ADC's committed lock graph already resolves a patched transitive 3.0.4, so the entry now suppresses nothing in the audited graph while ADR-038 (`../adr/038-supply-chain-provenance.md:49-52`) already records the accepted-advisory list as empty. (2) Three global `NoWarn` codes (CS1591, RMG020, EXTEXP0001) carry no justification or date (`Directory.Build.props:22`), unlike every audit suppression in the same file (`:7-12`, `:41-48`) and unlike the test-wide ones commented at `:31-33`. (3) The MAUI `MMCA.ADC.UI` project sits outside every CI build and outside the audited graph (`MMCA.ADC.CI.slnf:25` lists only UI.Web and UI.Web.Client; no workflow installs the `maui-android` workload), so analyzers and TWAE are review-only there and its own `NoWarn CA5392` is never gated (`Source/Hosts/UI/MMCA.ADC.UI/MMCA.ADC.UI.csproj:131`); the gating vulnerable-package scan also runs against `CI.slnf` only (`deploy.yml:288`), so the MAUI graph that the `:8-12` suppressions exist for is never audited in CI. The two hygiene items are a named effort-S lever in the backlog; the structural MAUI half is recorded as **TD-18** rather than fixed, because adding a MAUI CI build cuts against the deliberate 2026-07-18 Actions-minute reduction. Maturity 4 was independently re-derived, not inherited: blanket `dotnet_analyzer_diagnostic.severity = error` (`.editorconfig:312`) plus TWAE/AnalysisMode=All/CodeAnalysisTreatWarningsAsErrors/EnforceCodeStyleInBuild (`Directory.Build.props:16`) and five analyzers repo-wide (`:53-74`), enforced by the required `build-and-test` Release build with a `--locked-mode` restore (`deploy.yml:180`, required per `CONTRIBUTING.md:80`). Positive signals unchanged: exactly one hand-written pragma disable in `Source/`, with an inline reason, and all five in-source `SuppressMessage` attributes carry a Justification. One adversarially adjudicated non-move: **§27 holds M4/I8 for the third consecutive cycle** on byte-identical evidence (`PseudoLocalizationTests.cs:51` still declares exactly 3 public pages, public by design per `:31`, against **37** routable `@page` files counted this run; the file is untouched since `c5e6f653` on 2026-07-11 and no `.resx` has landed since 2026-07-20). Rather than leave it a live candidacy for a fourth rejection, the lift is now adjudicated **DEFERRED** with its cost stated in the backlog's Deliberate/accepted section, with explicit re-open triggers (a second locale beyond `es`, any RTL locale, or a reported layout regression on an authenticated page). §12/§21/§22/§33 re-confirmed at M3/I8, so the below-maturity-4 set stays §12/§21/§22/§33. The v1.124.0 through v1.131.0 lockstep sweeps moved no score. Evidence refresh (no score move): the arch-test suite is now **29 test classes across 31 `.cs` files executing 91 methods, re-run green this cycle (91/91)**, up from the 26/28/82 snapshot, and **90 of the 91 are now inherited** from the shared `MMCA.Common.Testing.Architecture` rule library, since the §13 alert-runbook pairing gate has been lifted upstream (`ObservabilityConventionTests.cs:7` is now a bare thin subclass); the single remaining ADC-local method is the TD-14 Profile-form guard (`FormsConventionTests.cs:31`). Indices: **Maturity 97.2% (311/320, unchanged) / Implementation 85.9%→85.6% (685/800)**. The twenty-third-cycle record follows for provenance: **(twenty-third cycle, 2026-07-23, pin v1.123.0):** no score moves; all 34 categories re-confirmed at their prior scores from evidence read this run (32 CONFIRMED first-pass, 2 adversarially adjudicated). The two adjudications were proposed maturity lifts, both rejected as verified non-moves: **§12 holds M3/I8** (the k6 capacity tier still executes monthly cron + workflow_dispatch out of band, `load-test.yml:8-18`, and deploy's `load-freshness` is a recency check on the last successful run with no per-deploy k6 cost, `deploy.yml:548-551`; Notification stays pinned `maxReplicas: 1`, `main.bicep:1424`) and **§21 holds M3/I8** (the recorded, dated manual screen-reader pass, the sole maturity-4 lever, is still the empty placeholder at `adc-ACCESSIBILITY-SCREENREADER-PASS.md:62`; the 18-page chromium axe/E2E deploy gate re-confirmed active via `e2e-gate` in `deploy.needs`, `deploy.yml:791`). §22 and §33 re-confirmed at M3/I8, so the below-maturity-4 set stays §12/§21/§22/§33; every implementation score is 8 or higher. The v1.122.0/v1.123.0 lockstep sweeps (filter DSL + cache observability; the IIntegrationEventPublisher removal with callers moved to IEventBus directly) moved no score. Indices unchanged: **Maturity 97.2% (311/320) / Implementation 85.9% (687/800)**. The twenty-second-cycle record follows for provenance: **(twenty-second cycle, 2026-07-21, pin v1.121.0):** two scores move, both down, both on drift verified in current source and neither a code-quality regression. **§22 Responsive & Cross-Browser Maturity 4→3.** The twenty-first cycle's M4 rested on the deploy-gating `e2e-gate` passing all three engines; the 2026-07-18 Actions-minute reduction cut that gate to chromium only (`deploy.yml:488` `browsers: '["chromium"]'`, with the cost rationale recorded in the job comment at `:478-480`), so firefox and webkit now run only on the weeknight `schedule` (`e2e.yml:39`), where they are `continue-on-error` (`e2e.yml:119`). Cross-engine verification is therefore nightly-advisory, which is the rubric's Consistent (3), not Optimized (4); the responsive substance is untouched, so Implementation holds at 8. This is the scoring consequence of a deliberate cost choice, recorded as such in the backlog's Deliberate/accepted section. **§18 UI Architecture & Components Implementation 9→8.** Maturity 4 re-confirmed independently (`UIArchitectureConventionTests.cs` is a real sealed subclass of the shared base, the arch-test project is in `MMCA.ADC.CI.slnf:58`, and that filter is built and tested by the required `build-and-test` check at `deploy.yml:181`), but the exemplary-band 9 no longer holds: the largest code-behind in the repo, `Engagement.UI/Pages/HappeningNow/HappeningNow.razor.cs`, sits at exactly **400** lines against the enforced `MaxCodeBehindLines => 400` cap (`MMCA.Common.Testing.Architecture/Bases/UIArchitectureConventionTestsBase.cs:22`), with six more files in the 360-379 band, so the next added method fails the gate rather than being caught in review. That is a real, isolated, citable gap (rubric Strong 7-8), and the prior 9 also rested partly on a broken citation (`MobileInfiniteScrollList.razor:49-52`, a file only 43 lines long). Tracked as **TD-16** in the backlog. One adversarially adjudicated non-move: **§27 holds M4/I8** (a proposed impl 8→9 was re-proposed and re-rejected on the identical basis as the twenty-first cycle: `PseudoLocalizationTests.cs:51` still covers exactly 3 public pages of 36 routable pages, unchanged since the prior rejection). This cycle also corrected drifted line anchors across the file (`CI.slnf:56`→`:58`, `deploy.yml:303-309/343/417`→`:483-489/:791`, `e2e.yml:78`→`:119`, `main.bicep:1113`→`:1424` and `:341`→`:488`). The twenty-first-cycle record follows for provenance: **(twenty-first cycle, 2026-07-17, pin v1.117.0):** two maturity lifts, both on gates shipped 2026-07-16 that close the exact blockers the twentieth cycle recorded: **§13 Observability 3→4** (`ObservabilityConventionTests`, an ADC-local fitness function in the CI.slnf arch gate, machine-enforces the alert-to-runbook pairing between `infra/main.bicep` `sloAlertSpecs` and `infra/OPERATIONS.md`, converting the "review-enforced IaC, not CI-gated" maturity cap) and **§22 Responsive & Cross-Browser 3→4** (the deploy-gating `e2e-gate` passes the full chromium+firefox+webkit matrix, `deploy.yml:309`, and `e2e.yml:78` scopes `continue-on-error` to scheduled nightly non-chromium legs only, so every engine the gate invokes can fail a deploy; promoted 2026-07-16 after 8 consecutive fully green nightly matrices). One adversarially adjudicated non-move: **§27 holds M4/I8** (the wave-6 `PseudoLocalizationTests` tier covers 3 public pages, a partial extension of the text-expansion evidence, so the recorded impl 8→9 candidacy was rejected). This cycle also corrects stale prose introduced 2026-07-17 by PR #15, which accidentally bundled a superseded nineteenth-cycle draft (a "§12 mat 4" strength claim, a risk-1 rewrite asserting the firefox/webkit legs cannot fail the gate, and a mislabeled backlog update paragraph); **§12 stays M3/I8** per the twentieth-cycle adjudication, re-confirmed this run (its k6 tier is freshness-gated via `load-freshness`, `deploy.yml:348,417`, but the tier itself runs monthly/dispatch and the Notification app stays pinned `maxReplicas: 1`). The arch-test suite re-ran green this cycle (**82/82** methods across 26 test classes; the three new methods are the ADC-local §13 gate). Indices: **Maturity 96.6%→97.8% (313/320) / Implementation 86.3% (690/800, unchanged)**. The twentieth-cycle record follows for provenance: **(twentieth cycle, 2026-07-15, pin v1.116.0)** five scores moved, all up, each on evidence that postdates the nineteenth cycle. Three maturity lifts close stale "no gate exists" rationales: **§18 UI Architecture 3→4** (`UIArchitectureConventionTests` now machine-enforces the code-behind/container split in the CI.slnf gate), **§19 State Management 3→4** (`StateManagementConventionTests` enforces no-mutable-static-UI-state + scoped stateful services in the same gate), and **§23 Front-End Performance 3→4** (the Core Web Vitals budgets were recalibrated 2026-07-11 into real failing assertions riding the deploy-gating chromium e2e-gate, superseding the advisory-by-design budgets the nineteenth cycle correctly rejected). Two implementation lifts: **§13 Observability 8→9** (the two named gaps closed: the Azure Monitor SLO workbook dashboard and the per-alert `infra/OPERATIONS.md` runbook) and **§24 Forms 7→8** (TD-14 shipped: per-form MudAlert error summaries on all six create forms, machine-enforced markers, and a dedicated Profile-form fitness test). Adversarially adjudicated non-moves: §28 holds M4/I8 (its row's false "E2E #5 un-skipped" claim and three drifted line anchors are corrected below; the test is re-quarantined at `SpeakerSelfServiceTests.cs:57`), §33 holds M3/I8 (a proposed impl 9 was rejected: broker parity local-RabbitMQ vs prod-Service-Bus is mitigated, not closed, per `README.md:74`), and §34 holds M4/I9 (the same 9→8 downgrade the nineteenth cycle rejected was re-proposed and re-rejected). The arch-test suite re-ran green this cycle (**79/79** inherited methods across 25 thin-subclass classes, up from 74/23 on the two new §18/§19 gates). Indices: **Maturity 94.1%→96.6% (309/320) / Implementation 85.8%→86.3% (690/800)**._ +_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 (HEAD `96f0919a`, clean tree); framework dependency pinned at **MMCA.Common.* v1.160.0** (all 15 packages, lockstep, `Directory.Packages.props:92-110`; the canonical ADR set is **001-096**, indexed in `../adr/README.md`; framework-wide facts in `../MMCA.Common/FACTS.md`). **What moved this cycle (twenty-seventh-cycle full 34-category re-score, 2026-08-23, pin v1.160.0): two scores move, both down, both on the implementation axis.** **§4 Domain-Driven Design Implementation 9→8** (Maturity 4 holds, independently re-derived): the prior row's citations had all drifted, and a fresh read against the rubric's own criteria finds three minor-but-real gaps that place the substance in the Strong 7-8 band rather than reference quality: (1) aggregate roots carry cross-aggregate object navigations with **public setters** alongside the by-ID FKs (`Session.Event`/`Session.Room`, `Sponsor.Event`, `Activity.Event`: the only public setters in the domain layer), so "references between aggregates by ID, not object graph" is only partially met; (2) `Event.Create`/`Event.Update` validate only name, timezone and date range while six newer optional fields (organizer contact email, sponsorship/ticketing URLs and friends) are accepted unvalidated by the aggregate with their email/URL rules living in the Application FluentValidation layer (`EventValidationRules.cs:65`), inconsistent with the repo's own convention (`Sponsor.Create` enforces its URL and booth-number invariants inside the aggregate); (3) `Event.OrganizerContactEmail` is a raw `string?` while the `Email` value object is used for the same concept on both `User` and `Speaker`, and ADC defines no value objects of its own. **§22 Responsive & Cross-Browser Implementation 8→7** (Maturity 3 holds): the prior "holds 8 on unchanged substance" no longer stands, since an independent read finds the rubric's density-options criterion has **zero** adoption in ADC and the content-reflow criterion is only partially met on the 17 non-DataGrid table pages, including the data-dense conference-day surfaces; this names the implementation lever the backlog had recorded as "not yet identified". Eight adjudications were proposed lifts and all eight were **rejected** as verified non-moves, a third consecutive cycle in which every proposed lift landed short: §5 holds 4/8 (DTOs still in Shared with horizontal mapper/validator folders; the enforced validator rule exempts exactly the horizontal validators that exist, `ArchitectureRules.Slices.cs:38-39`; the forgot-password vertical landed command+handler slices with no in-slice validator plus a new controller, fresh proof the hybrid still edits switchboards); §7 holds 4/8 (the synchronous-coupling red flag broadened rather than closed); §15 holds 4/7 (all three twenty-fourth-cycle grounds byte-intact, the expired SQLite suppression now twenty-five releases past the v1.121.0 sweep); §17 holds 4/9 (no CI/CD substance changed; the SQL public-network-access cap is verbatim open); §18 holds 4/8 (the 400-line-cap pressure **widened**: eight code-behinds within 38 lines of the cap, two at 398, three files grew since 2026-08-14); §21 holds 3/8 (the screen-reader-pass placeholder is still empty, and four routable pages shipped 2026-08-19 with no axe coverage, a new gap not a lift); §28 holds 4/8 (zero visual-regression/snapshot tests while the shared `MarkupSnapshot` helper sits unused, and the E2E/axe layer is a conditional deploy gate, not a merge gate); §31 holds 4/8 (the surge automation lever unpulled for a third cycle, `cost-guard.yml` byte-unchanged). One structural finding is recorded rather than scored: the deploy-gating chromium E2E/axe/CWV suite is **conditional on the diff being UI-affecting** (`deploy.yml:538`) and the `deploy` job accepts a skipped gate (`:896`), so a backend-only merge reaches production with no browser run: recorded as backlog **TD-20** plus a Deliberate/accepted amendment, and the ledger's "gates every deploy" phrasing is qualified accordingly. Indices: **Maturity 97.2% (311/320, unchanged) / Implementation 85.6%→85.0% (680/800)**, both re-summed this run; the 5-point implementation drop reconciles exactly as §4 (-1 × w3) + §22 (-1 × w2). **The twenty-sixth-cycle record follows for provenance (2026-08-14, pin v1.152.0): no score moves.** All 34 categories were re-confirmed at their prior Maturity/Implementation from evidence read this run (26 rescored first-pass, 8 adversarially adjudicated). All eight adjudications were proposed lifts and all eight were **rejected** as verified non-moves, a second consecutive cycle in which every proposed lift landed one criterion short. **§5 holds 4/8** (the rubric's first §5 criterion wants the **DTO** in the slice, and ADC's DTOs still sit in the Shared assembly with their mappers in horizontal `Application/{Aggregate}/DTOs/` folders beside horizontal `Validation/`, `Specifications/` and `DomainEventHandlers/`; `AdcArchitectureMap.cs:12-43` still carries no `Module("Notification", ...)` entry, so enforcement covers 3 of the 4 modules: that half is now named as backlog **TD-19**). **§7 holds 4/8** (the bidirectional sync-gRPC red flag that caps it did not close, it **broadened** to a second pair, Identity-Notification, across 7 sync client registrations in 4 services, and the same map omission leaves the transport-at-the-edge guard covering 3 of 4 services). **§12 holds 3/8** (a maturity 3→4 was proposed; `git log` since the prior cycle's HEAD shows **zero** commits touching `.github/workflows/load-test.yml`, `deploy.yml` or `Tests/Load/`, so the blocker four prior cycles cited, a backend capacity proof executed out of band behind a recency-only deploy gate, is byte-for-byte intact). **§13 holds 4/9** (a 9→10 was proposed a second time; runbooks are still missing for three of the six ENABLED production alerts, including the single severity-1 gateway-availability alert at `infra/main.bicep:496-502`, declared outside the `sloAlertSpecs` array the pairing gate parses). **§15 holds 4/7** (all three grounds of the twenty-fourth-cycle downgrade are intact in current source, and the expired SQLite suppression is further past its own written removal condition than when it was recorded: the row cited v1.135.0, the pin is now **v1.152.0**, seventeen releases past the v1.121.0 sweep). **§23 holds 4/8** (both halves of the category's own named lever, WASM code-split and image optimization, are verified still open, so Initial load and Asset hygiene stay short of reference quality). **§28 holds 4/8** (the genuinely new state-management bUnit coverage is real but is a within-band improvement, not a band change, and two rubric criteria still carry concrete unmet elements). **§31 holds 4/8** (the conference-day surge is still a manual scale-up with a manual reset instruction, `cost-guard.yml:4`, `:83`, and no automated or scheduled revert exists, so "reversible scale events" is unmet in its exact terms; the new daily ACR purge and the production metrics-instrument suppression strengthen other criteria without closing that one). Beyond the scores this is an anchor-and-provenance pass: the pin (v1.135.0→**v1.152.0**), HEAD (`995a7886`→`19021d93`) and ADR range (001-064→**001-078**) are updated, and the drifted citations corrected in place are the §5 architecture-map range, the §13 sev-1 alert (`infra/main.bicep:481`→`:496-502`), the §21 axe coverage (17 pages→**31 axe test methods over ~29 distinct pages**, `AccessibilityTests.cs:21-365`, including the conference-day surfaces), the §27 routable-page denominator (37→**49** `@page` files under `Source`, 48 excluding the MAUI-only `DeviceSettings.razor`, so the deferred lift's cost figure rises from roughly 34 pages to roughly 45), the §18 code-behind measurements and the §33 `cross-service-tests.yml` job anchors. **TD-16 was re-measured:** the high-water code-behind rose 395→**398** of the 400 cap (`SessionSelectionDashboard.razor.cs`), so headroom narrowed from 5 lines to 2. Indices unchanged: **Maturity 97.2% (311/320) / Implementation 85.6% (685/800)**, re-summed this run. **The twenty-fifth-cycle record follows for provenance (2026-08-01, pin v1.135.0):** no score moves. All 34 categories re-confirmed at their prior Maturity/Implementation from evidence read this run (28 rescored first-pass, 6 adversarially adjudicated). All six adjudications were proposed implementation lifts and all six were **rejected** as verified non-moves, which is itself the finding: the implementation axis is not stalling for want of effort, it is sitting one criterion short in six independent places. **§5 holds 4/8** (the rubric's first §5 criterion wants command + handler + validator + **DTO** in the slice; ADC's DTOs live in the Shared assembly, `Conference.Shared/Events/EventDTO.cs`, with their mappers in a horizontal `Application/Events/DTOs/`, alongside horizontal `Validation/`, `Specifications/` and `DomainEventHandlers/` folders; the deliberate layered-by-project hybrid is unchanged, and `AdcArchitectureMap.cs:12-44` still omits `MMCA.ADC.Notification.Application` from the enforced set, so a stranded handler added there would not be caught: benign today, not reference-quality enforcement). **§13 holds 4/9** (a 9→10 was proposed; three ENABLED production alerts carry no runbook triage section and sit outside the CI pairing gate's scope, including the single severity-1 gateway-availability alert at `infra/main.bicep:481`, so the rubric's "runbooks for common failures" criterion is unmet: that is a real gap, not the trivial polish the top rung now allows). **§24 holds 4/8** (the prior cycle's named 8→9 lever, a bUnit render-level assertion of the summary's error items, genuinely shipped and is CI-gated, but a fresh read finds two criteria only partially met: client validation does not mirror the server's cross-field and format rules, which is the category's first criterion *and* its first red flag, and the form-level error summary is present on 7 of the 15 MudForm forms). **§27 holds 4/8 for a fourth consecutive rejection** on byte-identical pseudo-localization evidence, plus one newly citable culture-aware-formatting violation not in the prior row's prose. **§31 holds 4/8** (its own stated 8→9 lever, automating the conference-day surge and revert, is demonstrably not pulled, so "reversible scale events" stays unmet in its exact terms). **§33 holds 3/8 for a second consecutive rejection**: the lift rested on the broker-parity red flag being closed, and it is not: the AppHost still provisions RabbitMQ only, while the Azure Service Bus proof restored to the weekday nightly on 2026-07-29 is `continue-on-error` and explicitly rides no gate (`cross-service-tests.yml:144-149`; `cross-service-freshness` keys off the `cross-service` job, `deploy.yml:663`), so it is advisory, not closure, and the README sentence used to justify the lift is itself inaccurate about the gate. The below-maturity-4 set stays **§12/§21/§22/§33** and §15 stays the only implementation score below 8. The v1.132.0 through v1.135.0 lockstep sweeps and the 2026-08-01 BugHunt remediation (ADC PR #94) moved no score. Beyond the scores, this cycle is an anchor-and-provenance pass: the pin (v1.131.0→**v1.135.0**), HEAD (`2ec77796`→`995a7886`), ADR range (001-060→**001-064**) and roughly thirty drifted `path:line` citations were corrected in place against current source, and the TD-16/TD-17 ledger entries were re-measured (see `RemediationBacklog.md`). Indices unchanged: **Maturity 97.2% (311/320) / Implementation 85.6% (685/800)**, re-summed this run. **The twenty-fourth-cycle record follows for provenance (2026-07-28, pin v1.131.0):** one score moves, and it moves down. **§15 Best Practices & Code Quality Implementation 8→7** (Maturity 4 holds): three gaps verified in current source, none of which the prior row's basis ("only justified/tracked suppressions", "documented/dated NoWarn") still describes correctly. (1) The SQLite audit suppression `GHSA-2m69-gcr7-jv3q` is expired by its own written removal condition (`Directory.Build.props:49-51`, whose comment at `:41-48` states it "stays only until the next MMCA.Common release and consumer pin sweep carry the fix through the published package graph, then it must be removed", updated 2026-07-20): ADC is now pinned at v1.131.0 (`Directory.Packages.props:123`), ten releases past the v1.121.0 SQLite sweep, MMCA.Common has removed its own suppression and pins the patched bundle directly (`MMCA.Common/Directory.Packages.props:39,42`, `SQLitePCLRaw.bundle_e_sqlite3` 3.0.5), and ADC's committed lock graph already resolves a patched transitive 3.0.4, so the entry now suppresses nothing in the audited graph while ADR-038 (`../adr/038-supply-chain-provenance.md:49-52`) already records the accepted-advisory list as empty. (2) Three global `NoWarn` codes (CS1591, RMG020, EXTEXP0001) carry no justification or date (`Directory.Build.props:22`), unlike every audit suppression in the same file (`:7-12`, `:41-48`) and unlike the test-wide ones commented at `:31-33`. (3) The MAUI `MMCA.ADC.UI` project sits outside every CI build and outside the audited graph (`MMCA.ADC.CI.slnf:25` lists only UI.Web and UI.Web.Client; no workflow installs the `maui-android` workload), so analyzers and TWAE are review-only there and its own `NoWarn CA5392` is never gated (`Source/Hosts/UI/MMCA.ADC.UI/MMCA.ADC.UI.csproj:131`); the gating vulnerable-package scan also runs against `CI.slnf` only (`deploy.yml:288`), so the MAUI graph that the `:8-12` suppressions exist for is never audited in CI. The two hygiene items are a named effort-S lever in the backlog; the structural MAUI half is recorded as **TD-18** rather than fixed, because adding a MAUI CI build cuts against the deliberate 2026-07-18 Actions-minute reduction. Maturity 4 was independently re-derived, not inherited: blanket `dotnet_analyzer_diagnostic.severity = error` (`.editorconfig:312`) plus TWAE/AnalysisMode=All/CodeAnalysisTreatWarningsAsErrors/EnforceCodeStyleInBuild (`Directory.Build.props:16`) and five analyzers repo-wide (`:53-74`), enforced by the required `build-and-test` Release build with a `--locked-mode` restore (`deploy.yml:180`, required per `CONTRIBUTING.md:80`). Positive signals unchanged: exactly one hand-written pragma disable in `Source/`, with an inline reason, and all five in-source `SuppressMessage` attributes carry a Justification. One adversarially adjudicated non-move: **§27 holds M4/I8 for the third consecutive cycle** on byte-identical evidence (`PseudoLocalizationTests.cs:51` still declares exactly 3 public pages, public by design per `:31`, against **37** routable `@page` files counted this run; the file is untouched since `c5e6f653` on 2026-07-11 and no `.resx` has landed since 2026-07-20). Rather than leave it a live candidacy for a fourth rejection, the lift is now adjudicated **DEFERRED** with its cost stated in the backlog's Deliberate/accepted section, with explicit re-open triggers (a second locale beyond `es`, any RTL locale, or a reported layout regression on an authenticated page). §12/§21/§22/§33 re-confirmed at M3/I8, so the below-maturity-4 set stays §12/§21/§22/§33. The v1.124.0 through v1.131.0 lockstep sweeps moved no score. Evidence refresh (no score move): the arch-test suite is now **29 test classes across 31 `.cs` files executing 91 methods, re-run green this cycle (91/91)**, up from the 26/28/82 snapshot, and **90 of the 91 are now inherited** from the shared `MMCA.Common.Testing.Architecture` rule library, since the §13 alert-runbook pairing gate has been lifted upstream (`ObservabilityConventionTests.cs:7` is now a bare thin subclass); the single remaining ADC-local method is the TD-14 Profile-form guard (`FormsConventionTests.cs:31`). Indices: **Maturity 97.2% (311/320, unchanged) / Implementation 85.9%→85.6% (685/800)**. The twenty-third-cycle record follows for provenance: **(twenty-third cycle, 2026-07-23, pin v1.123.0):** no score moves; all 34 categories re-confirmed at their prior scores from evidence read this run (32 CONFIRMED first-pass, 2 adversarially adjudicated). The two adjudications were proposed maturity lifts, both rejected as verified non-moves: **§12 holds M3/I8** (the k6 capacity tier still executes monthly cron + workflow_dispatch out of band, `load-test.yml:8-18`, and deploy's `load-freshness` is a recency check on the last successful run with no per-deploy k6 cost, `deploy.yml:548-551`; Notification stays pinned `maxReplicas: 1`, `main.bicep:1424`) and **§21 holds M3/I8** (the recorded, dated manual screen-reader pass, the sole maturity-4 lever, is still the empty placeholder at `adc-ACCESSIBILITY-SCREENREADER-PASS.md:62`; the 18-page chromium axe/E2E deploy gate re-confirmed active via `e2e-gate` in `deploy.needs`, `deploy.yml:791`). §22 and §33 re-confirmed at M3/I8, so the below-maturity-4 set stays §12/§21/§22/§33; every implementation score is 8 or higher. The v1.122.0/v1.123.0 lockstep sweeps (filter DSL + cache observability; the IIntegrationEventPublisher removal with callers moved to IEventBus directly) moved no score. Indices unchanged: **Maturity 97.2% (311/320) / Implementation 85.9% (687/800)**. The twenty-second-cycle record follows for provenance: **(twenty-second cycle, 2026-07-21, pin v1.121.0):** two scores move, both down, both on drift verified in current source and neither a code-quality regression. **§22 Responsive & Cross-Browser Maturity 4→3.** The twenty-first cycle's M4 rested on the deploy-gating `e2e-gate` passing all three engines; the 2026-07-18 Actions-minute reduction cut that gate to chromium only (`deploy.yml:488` `browsers: '["chromium"]'`, with the cost rationale recorded in the job comment at `:478-480`), so firefox and webkit now run only on the weeknight `schedule` (`e2e.yml:39`), where they are `continue-on-error` (`e2e.yml:119`). Cross-engine verification is therefore nightly-advisory, which is the rubric's Consistent (3), not Optimized (4); the responsive substance is untouched, so Implementation holds at 8. This is the scoring consequence of a deliberate cost choice, recorded as such in the backlog's Deliberate/accepted section. **§18 UI Architecture & Components Implementation 9→8.** Maturity 4 re-confirmed independently (`UIArchitectureConventionTests.cs` is a real sealed subclass of the shared base, the arch-test project is in `MMCA.ADC.CI.slnf:58`, and that filter is built and tested by the required `build-and-test` check at `deploy.yml:181`), but the exemplary-band 9 no longer holds: the largest code-behind in the repo, `Engagement.UI/Pages/HappeningNow/HappeningNow.razor.cs`, sits at exactly **400** lines against the enforced `MaxCodeBehindLines => 400` cap (`MMCA.Common.Testing.Architecture/Bases/UIArchitectureConventionTestsBase.cs:22`), with six more files in the 360-379 band, so the next added method fails the gate rather than being caught in review. That is a real, isolated, citable gap (rubric Strong 7-8), and the prior 9 also rested partly on a broken citation (`MobileInfiniteScrollList.razor:49-52`, a file only 43 lines long). Tracked as **TD-16** in the backlog. One adversarially adjudicated non-move: **§27 holds M4/I8** (a proposed impl 8→9 was re-proposed and re-rejected on the identical basis as the twenty-first cycle: `PseudoLocalizationTests.cs:51` still covers exactly 3 public pages of 36 routable pages, unchanged since the prior rejection). This cycle also corrected drifted line anchors across the file (`CI.slnf:56`→`:58`, `deploy.yml:303-309/343/417`→`:483-489/:791`, `e2e.yml:78`→`:119`, `main.bicep:1113`→`:1424` and `:341`→`:488`). The twenty-first-cycle record follows for provenance: **(twenty-first cycle, 2026-07-17, pin v1.117.0):** two maturity lifts, both on gates shipped 2026-07-16 that close the exact blockers the twentieth cycle recorded: **§13 Observability 3→4** (`ObservabilityConventionTests`, an ADC-local fitness function in the CI.slnf arch gate, machine-enforces the alert-to-runbook pairing between `infra/main.bicep` `sloAlertSpecs` and `infra/OPERATIONS.md`, converting the "review-enforced IaC, not CI-gated" maturity cap) and **§22 Responsive & Cross-Browser 3→4** (the deploy-gating `e2e-gate` passes the full chromium+firefox+webkit matrix, `deploy.yml:309`, and `e2e.yml:78` scopes `continue-on-error` to scheduled nightly non-chromium legs only, so every engine the gate invokes can fail a deploy; promoted 2026-07-16 after 8 consecutive fully green nightly matrices). One adversarially adjudicated non-move: **§27 holds M4/I8** (the wave-6 `PseudoLocalizationTests` tier covers 3 public pages, a partial extension of the text-expansion evidence, so the recorded impl 8→9 candidacy was rejected). This cycle also corrects stale prose introduced 2026-07-17 by PR #15, which accidentally bundled a superseded nineteenth-cycle draft (a "§12 mat 4" strength claim, a risk-1 rewrite asserting the firefox/webkit legs cannot fail the gate, and a mislabeled backlog update paragraph); **§12 stays M3/I8** per the twentieth-cycle adjudication, re-confirmed this run (its k6 tier is freshness-gated via `load-freshness`, `deploy.yml:348,417`, but the tier itself runs monthly/dispatch and the Notification app stays pinned `maxReplicas: 1`). The arch-test suite re-ran green this cycle (**82/82** methods across 26 test classes; the three new methods are the ADC-local §13 gate). Indices: **Maturity 96.6%→97.8% (313/320) / Implementation 86.3% (690/800, unchanged)**. The twentieth-cycle record follows for provenance: **(twentieth cycle, 2026-07-15, pin v1.116.0)** five scores moved, all up, each on evidence that postdates the nineteenth cycle. Three maturity lifts close stale "no gate exists" rationales: **§18 UI Architecture 3→4** (`UIArchitectureConventionTests` now machine-enforces the code-behind/container split in the CI.slnf gate), **§19 State Management 3→4** (`StateManagementConventionTests` enforces no-mutable-static-UI-state + scoped stateful services in the same gate), and **§23 Front-End Performance 3→4** (the Core Web Vitals budgets were recalibrated 2026-07-11 into real failing assertions riding the deploy-gating chromium e2e-gate, superseding the advisory-by-design budgets the nineteenth cycle correctly rejected). Two implementation lifts: **§13 Observability 8→9** (the two named gaps closed: the Azure Monitor SLO workbook dashboard and the per-alert `infra/OPERATIONS.md` runbook) and **§24 Forms 7→8** (TD-14 shipped: per-form MudAlert error summaries on all six create forms, machine-enforced markers, and a dedicated Profile-form fitness test). Adversarially adjudicated non-moves: §28 holds M4/I8 (its row's false "E2E #5 un-skipped" claim and three drifted line anchors are corrected below; the test is re-quarantined at `SpeakerSelfServiceTests.cs:57`), §33 holds M3/I8 (a proposed impl 9 was rejected: broker parity local-RabbitMQ vs prod-Service-Bus is mitigated, not closed, per `README.md:74`), and §34 holds M4/I9 (the same 9→8 downgrade the nineteenth cycle rejected was re-proposed and re-rejected). The arch-test suite re-ran green this cycle (**79/79** inherited methods across 25 thin-subclass classes, up from 74/23 on the two new §18/§19 gates). Indices: **Maturity 94.1%→96.6% (309/320) / Implementation 85.8%→86.3% (690/800)**._ _**Update 2026-06-30 (under-8 Implementation lift, §12).** One Implementation score moves up; Maturity holds: **Maturity 94.1% (301/320)** unchanged, **Implementation 85.9% → 86.1% (689/800)**. **§12 Performance & Scalability Implementation 7→8**: a `WebVitalsTests` Playwright tier now measures client-side Core Web Vitals (LCP/CLS/FCP/TTFB + a single-interaction INP sample) on the home, public-events, and login pages and emits a dated `web-vitals-*.json` artifact, closing the "no measured client-side CWV/INP" gap so both the backend k6 and client halves are measured. This is a test/CI-only change (no `MMCA.Common` release needed); it builds clean against the framework source. **§21 Accessibility holds at Implementation 7 this cycle**: its named 7→8 lever (promoting axe to a merge gate) is scoped as a follow-up, a backend-less in-process axe host mirroring MMCA.Common's proven gallery-host pattern, plus a recorded manual screen-reader pass: rather than rushed here (see `RemediationBacklog.md` #21). The shared `/login`/`/register` a11y surface is already chromium-merge-gated upstream in MMCA.Common's CI._ ## Executive summary -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 monolith into four independently-hosted microservices (Identity, Conference, Engagement, Notification) behind a YARP gateway. Services collaborate synchronously via Result-over-the-wire gRPC contracts and asynchronously via MassTransit integration events on the outbox pattern, run database-per-service, and consume shared framework primitives as versioned `MMCA.Common.*` NuGet packages pinned uniformly at **v1.152.0** (all 15, `Directory.Packages.props:92-110`). The standout characteristic is that architectural intent is not just documented but executable: **29 architecture-test classes (31 .cs files; 91 executed methods, re-run green 2026-07-28)** enforce layer dependencies, domain purity, module isolation, transport-at-edge, concurrency, PII erasure, data residency, cross-source specification safety, UI architecture and state-management conventions, observability alert-runbook pairing, and resilience as CI gates; as of the framework's v1.73.0 these are thin subclasses of the shared `MMCA.Common.Testing.Architecture` rule library (ADR-015; **90 of the 91 methods are now inherited**, the §13 alert-runbook pairing gate having been lifted upstream so `ObservabilityConventionTests` is a bare subclass, leaving the TD-14 Profile-form guard as the single ADC-local method), so ADC, Store, and Common run the *same* rules rather than parallel copies. Capacity/cost decisions are sized to *measured* conference load (~67 peak) rather than guesswork. +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 monolith into four independently-hosted microservices (Identity, Conference, Engagement, Notification) behind a YARP gateway. Services collaborate synchronously via Result-over-the-wire gRPC contracts and asynchronously via MassTransit integration events on the outbox pattern, run database-per-service, and consume shared framework primitives as versioned `MMCA.Common.*` NuGet packages pinned uniformly at **v1.160.0** (all 15, `Directory.Packages.props:92-110`). The standout characteristic is that architectural intent is not just documented but executable: **29 architecture-test classes (31 .cs files; 91 executed methods, re-run green 2026-07-28)** enforce layer dependencies, domain purity, module isolation, transport-at-edge, concurrency, PII erasure, data residency, cross-source specification safety, UI architecture and state-management conventions, observability alert-runbook pairing, and resilience as CI gates; as of the framework's v1.73.0 these are thin subclasses of the shared `MMCA.Common.Testing.Architecture` rule library (ADR-015; **90 of the 91 methods are now inherited**, the §13 alert-runbook pairing gate having been lifted upstream so `ObservabilityConventionTests` is a bare subclass, leaving the TD-14 Profile-form guard as the single ADC-local method), so ADC, Store, and Common run the *same* rules rather than parallel copies. Capacity/cost decisions are sized to *measured* conference load (~67 peak) rather than guesswork. **This re-verification (2026-06-20) found the prior report materially stale.** A remediation wave landed on 2026-06-19/06-20 that closes several of the previously-flagged top risks, each verified against source: (1) **the disaster-recovery restore has now been drilled**: `DISASTER-RECOVERY.md:140-144` records a 2026-06-20 PITR restore of `ADC_Conference` in 2.6 min (well within the 2 h RTO), automated via `dr-drill.yml`/`scripts/dr-restore-drill.ps1`, and **TD-10 is closed**: eliminating the rubric's #1 §29 red flag (untested restore); (2) **the SessionSpeakers GetAll gap is closed**: `SpeakerSelfServiceTests.cs:48-98` is now an active `[Fact]` (the `Assert.Skip` is gone) backed by a dedicated `SessionIncludeChildrenRegressionTests`; (3) **a v2 API endpoint now exists**: `ServiceInfoController` carries `[ApiVersion("1.0", Deprecated)]` + `[ApiVersion("2.0")]`, demonstrating the versioning machinery beyond a single version; (4) **`EnableInbox=true`** on both consumers (Conference + Identity), activating Common's idempotent at-least-once delivery; (5) **the PRIVACY.md residency contradiction is resolved**: it now reads East US 2 apps / West US 2 SQL, matching the deployment, and is guarded by a new `DataResidencyTests` fitness function. @@ -53,7 +53,7 @@ The headline finding remains a system whose backend and governance are near-exem | 1 | SOLID Principles | 3 | 4 | 9 | 12/27 | SRP/OCP/DIP exemplary and machine-enforced: tiny cohesive handlers, Strategy for variation, abstraction-only injection (now incl. `TimeProvider` over ambient `DateTime.UtcNow` in `RefreshFromSessionizeHandler`/`AuthenticationService`, v1.80.0), NetArchTest layer rules gated in CI; narrow ISP interfaces. Minor LSP/ISP nuances (a few `Delete()` overrides) keep it off 10. Evidence: `Conference.Application/Events/UseCases/Publish/PublishEventHandler.cs:14-15` (ctor = IUnitOfWork+ILogger only); `Conference.API/Controllers/EventQuestionAnswersController.cs:56-63` (injects handlers/`IEntityQueryService<>`, never repos); `.../RefreshFromSessionize/{ISessionizeSyncStrategy,SpeakerSyncStrategy}.cs` (per-entity Strategy, no growing switch); now also a ctor-dependency-ceiling fitness function `Tests/Architecture/MMCA.ADC.Architecture.Tests/ConstructorDependencyCountTests.cs` (≤7, with `AuthenticationService` at the 7 high-water mark) | | 2 | Design Patterns | 2 | 4 | 9 | 8/18 | Creational (entity factories/Options), structural (decorator pipeline, manual mapper adapter, gRPC adapters), behavioral (Strategy, dispatcher, Specification, domain events), and domain patterns (Result, Repository/UoW, Outbox) all present, idiomatic, ADR-justified, consistently named. Evidence: `Identity.Domain/Users/User.cs:130` (Factory Create→Result); `Conference.Application/Events/Specifications/OwnEventQuestionAnswerSpecification.cs:15-16` (authz scoping); exactly 5× `{Category,Room,Speaker,Session,Question}SyncStrategy.cs` | | 3 | Clean Architecture | 3 | 4 | 9 | 12/27 | Dependency rule inward-pointing, domain framework-pure, ports in Application/adapters in Infrastructure, rules enforced automatically by NetArchTest gated in CI: textbook; only the rich per-layer project sprawl keeps it shy of 10. Evidence: `Tests/Architecture/MMCA.ADC.Architecture.Tests/{LayerDependencyTests,DomainPurityTests,MicroserviceExtractionTests}.cs` (thin sealed subclasses of the shared `MMCA.Common.Testing.Architecture` rule library, ADR-015, Domain↛App/Infra/API and App↛Infra/API per module, domain framework-purity, transport-at-edge; all in `MMCA.ADC.CI.slnf`) | -| 4 | Domain-Driven Design | 3 | 4 | 9 | 12/27 | Aggregates enforce invariants internally, reference each other by ID, use value objects (Email) + identifier aliases, raise domain events, expose rich behavior with Result-returning factories: no anemic model; ubiquitous language (BR-### business rules) pervasive. Money/Address VOs live in Common rather than ADC, a minor locality nit. Evidence: `Identity.Domain/Users/User.cs:16,20,130` (rich aggregate, IAnonymizable, Email VO, Result factory, domain events :267/:305); `Conference.Domain/Events/Event.cs:55-71,424,445` (encapsulated child collections, by-ID refs, EventSpeakerChanged events); `Conference.Domain/Speakers/Speaker.cs:7,24` (Email VO from Common) | +| 4 | Domain-Driven Design | 3 | 4 | 8 | 12/24 | **↓ Implementation 9→8 (twenty-seventh cycle): three minor-but-real gaps against the rubric's own aggregate criteria, none individually a red flag, together the Strong 7-8 band rather than reference quality.** (1) Aggregate roots carry cross-aggregate object navigations with **public setters** alongside the by-ID FKs, the only public setters in the domain layer (`Conference.Domain/Sessions/Session.cs:71` `Event`/`Room`; `Conference.Domain/Sponsors/Sponsor.cs:49`; `Conference.Domain/Activities/Activity.cs:58`), so "references between aggregates by ID, not object graph" is partial. (2) `Event.Create` validates only name/timezone/date range (`Event.cs:180`) and `Event.Update` repeats the same three-invariant check then assigns organizerContactEmail/sponsorshipPacketUrl/ticketingUrl unchecked (`Event.cs:244`); their email/URL rules live in the Application layer (`EventValidationRules.cs:65`), inconsistent with the repo's own convention (`Sponsor.Create` enforces its URL + booth-number invariants in-aggregate, `Sponsor.cs:120`). (3) `Event.OrganizerContactEmail` is a raw `string?` (`Event.cs:56`) while the `Email` VO covers the same concept on `User` (`Identity.Domain/Users/User.cs:38`) and `Speaker` (`Conference.Domain/Speakers/Speaker.cs:31`); ADC defines no VOs of its own. Holding at 8, not lower: rich Result-returning factories with combined invariants (`User.cs:163`), domain events raised by the aggregates (`User.cs:332`, `Event.cs:561`), encapsulated child collections behind `IReadOnlyCollection` with a documented include policy (`Event.cs:85`), and the newest aggregate is reference quality (alias-typed ID refs, invariants, Result factory, event in the same transaction: `Engagement.Domain/CheckIns/CheckIn.cs:89`; `AttendeeBadge.cs:13` documents why it deliberately raises no event). Maturity 4 independently re-derived: `EntityConventionTests.cs:3` + `ImmutabilityTests.cs:3` (sealed subclasses of the shared bases over `AdcArchitectureMap.cs:22`, covering every domain-bearing module) gate CI via `MMCA.ADC.CI.slnf:58` + `deploy.yml:219` | | 5 | Vertical Slice Architecture | 2 | 4 | 8 | 8/16 | **↑ maturity 3→4 (this cycle): slice cohesion is now an enforced CI merge gate, so process maturity is Optimized (impl held at 8).** Within Application, features are genuine cohesive slices (command+validator+handler+DTO together, low inter-slice coupling, pipeline-handled cross-cutting), and slice cohesion is **machine-enforced**: ADC subclasses the shared `SliceCohesionTestsBase` fitness function (`MMCA.Common.Testing.Architecture`), which passes across all three modules (verified), failing the build if a handler/validator is stranded from its same-assembly contract; the test runs in `MMCA.ADC.CI.slnf` so it gates every push/PR (the rubric's M4 "enforced automatically by tests/CI"). Held at impl 8 (not 9; a proposed 8→9 was adversarially rejected in the twenty-fifth cycle, 2026-08-01, on three verified grounds): the overall solution is deliberately layered-by-project Clean Architecture, so a feature still spreads Domain/Application/Infrastructure/API across assemblies (a conscious hybrid), and a new use case still edits the per-aggregate controller plus the module's EF configuration rather than only adding a slice; the rubric's first §5 criterion also wants the **DTO** inside the slice, while ADC's DTOs live in the Shared assembly (`Conference.Shared/Events/EventDTO.cs`) with their mappers in a horizontal `Application/Events/DTOs/`, beside horizontal `Validation/`, `Specifications/` and `DomainEventHandlers/` folders; and enforcement covers 3 of the 4 modules, since `AdcArchitectureMap.cs:12-43` registers the Identity/Conference/Engagement Application assemblies but omits `MMCA.ADC.Notification.Application` (benign today, that project holds three files and no use cases, but not reference-quality enforcement). Evidence: `Tests/Architecture/MMCA.ADC.Architecture.Tests/SliceCohesionTests.cs:8`; `MMCA.ADC.CI.slnf:58` (the prior `:54` anchor was drifted; `:54` is now Engagement.Infrastructure.Tests); `deploy.yml:219` (the arch gate executes the filter); `ArchitectureRules.Slices.cs:13,31`; `AdcArchitectureMap.cs:12-43`; `Conference.Application/Events/UseCases/AddEventQuestionAnswer/`; `Application/{Aggregate}/UseCases/{Operation}/` uniform across modules | | 6 | CQRS & Event-Driven | 2 | 4 | 9 | 8/18 | **↓ Implementation 10→9 (fourteenth-cycle recalibration): the rubric reserves 10 for 'nothing left to improve' (`ArchitectureEvaluationCriteria.md:50`) and real levers remain, so exemplary-9 is the accurate score (Maturity holds at 4).** Commands (Result, `ITransactional`) and queries cleanly separated via a documented, enforced decorator pipeline; outbox gives atomic persist-then-publish with smart-wait/retry/dead-letter; the integration-event wire contract is frozen by a CI fitness function; events carry a Common-supplied `SchemaVersion`. Consumer idempotency is infrastructure-backed (EfInboxStore dedup) via **`EnableInbox=true`** on **all four services** now (Conference/Identity/Engagement/Notification; Engagement + Notification joined on the wave-6 TD-02 close, `appsettings.json`), but the versioning path is present-but-unexercised (`SchemaVersion` is `virtual … => 1`, no event has bumped to a v2), and the genuine broker round-trip covered by `MMCA.ADC.CrossService.IntegrationTests` (TD-02) runs only in a non-gating nightly (`cross-service-tests.yml`) whose recency is enforced by a `cross-service-freshness` deploy check rather than the tier itself gating, so real improvement remains and it sits at exemplary 9 not perfect 10. Evidence: `Tests/Architecture/MMCA.ADC.Architecture.Tests/IntegrationEventContractTests.cs:9-14` (frozen contract, CI-gated via `MMCA.ADC.CI.slnf:58`); `EnableInbox=true` on all four service `appsettings.json` (Identity/Conference/Engagement/Notification); Common `BaseIntegrationEvent.cs:22` (`SchemaVersion` virtual, default 1); Common `OutboxProcessor.cs`; ADR-003 | | 7 | Microservices Readiness | 3 | 4 | 8 | 12/24 | Service boundaries align with bounded contexts, each owns its own DB (no shared writable schema), integration async-via-outbox + sync-via-versioned-gRPC, resilience a CI-enforced invariant, transport-at-edge keeps modules extractable/reversible. Only smell: the bidirectional Conference↔Engagement gRPC pair (documented, self-healing via the resilience pipeline; AppHost deliberately omits the reciprocal `WaitFor`)., ADR-008/006/007; `Conference.Contracts/Protos/session_bookmark_validation.proto` + `Engagement.Contracts/Protos/bookmark_count.proto` (the pair) + `Identity.Contracts/Protos/attendee_query.proto`; `Tests/Architecture/MMCA.ADC.Architecture.Tests/MicroserviceExtractionTests.cs` (transport-at-edge guard, thin subclass of the shared rule library) | @@ -71,7 +71,7 @@ The headline finding remains a system whose backend and governance are near-exem | 19 | State Management & Data Flow | 3 | 4 | 9 | 12/27 | **↑ Maturity 3→4 (twentieth cycle): the §19 fitness gate whose absence drove the fifteenth-cycle D12 recalibration now exists and gates CI.** `StateManagementConventionTests.cs:11` (a sealed subclass of the shared `StateManagementConventionTestsBase`, shipped in the pinned v1.116.0 package) machine-enforces the two §19 rules: UI assemblies carry no mutable static state (base `:27`; a static member is shared across every Blazor Server circuit) and stateful `*StateService`/`*StateContainer` services stay scoped, never singleton (base `:65`); the ADC subclass declares no exemptions, and the arch-test project runs in the merge/deploy gate (`MMCA.ADC.CI.slnf:58`), so the per-circuit ownership model is Optimized (4), the same rationale as §18. Implementation holds at 9 (a first-pass 9→8 proposal was adversarially rejected as an unsupported downgrade; no new §19 red flag exists): single-source-of-truth state with explicit ownership (component-local vs per-circuit scoped service), zero mutable `static` user/session fields (now machine-proven, not just swept), unidirectional flow with immutable records, `StateHasChanged`/`InvokeAsync` used intentionally, the stale-IsDirty red flag pre-empted via `IsDirtyAccessor`; `PersistentComponentState` avoids per-render refetch; the disposed-CTS race is tolerated. Evidence: `Tests/Architecture/MMCA.ADC.Architecture.Tests/StateManagementConventionTests.cs:11` (the §19 gate); Common `StateManagementConventionTestsBase.cs:27,65` (the two rules); `MMCA.ADC.CI.slnf:58`; Common `ListPageStateService.cs:9,81` (per-circuit scoped, immutable record `with`); Common `DependencyInjection.cs:55` (TryAddScoped); `UnsavedChangesGuard.razor:32`; `DataGridListPageBase.cs:538-560` | | 20 | Design System & UI Consistency | 2 | 4 | 9 | 8/18 | **↑ from impl 8.** MudBlazor used consistently with a single centralized theme (palette/typography/spacing from one `MMCATheme` + CSS-variable tokens kept in sync by a test), and **dark mode is now functionally wired** (Common `MMCA.Common.UI/Layout/MainLayout.razor:17,40` bind `IsDarkMode` + the `ThemeToggle`; `Theme/MMCATheme.cs:48` supplies `PaletteDark`), so Day/Dark is a real capability, not just a token surface. The ADC landing page is now brand-token-clean: `ADCHome.razor.css:215,252,277` in **both** UI hosts use `var(--mmca-primary)`, guarded by `Tests/Architecture/MMCA.ADC.Architecture.Tests/BrandColorTokenTests.cs:26-37`. A borderline 8→9 call: the two residual deductions persist but are **Common-side** (`BrandColorTokenTests` guards Primary only, Secondary has no drift test; a few `!important` overrides + Store-specific cart CSS live in Common's shared `app.css`), and dark mode is now a functional capability., Common `MMCATheme.cs:24` (`Secondary #00796B` for WCAG 5.3:1), `:48` (`PaletteDark`); `MMCA.Common.UI/Layout/MainLayout.razor:17,40` (IsDarkMode + ThemeToggle); `ADCHome.razor.css:215,252,277` (both hosts, `var(--mmca-primary)`); `BrandColorTokenTests.cs:26-37`; `DataGridListPageBase.cs:328-388` (RowsPerPage/CurrentPage v9 guardrail, BL0005 justified); ⚠ Common `app.css:116-148` (Store cart `!important`, shared-Common, minor) | | 21 | Accessibility (a11y) | 3 | 3 | 8 | 9/24 | **↑ Maturity 2→3 + Implementation 7→8 (fifteenth cycle): the axe layer is now an enforced deploy gate.** Implementation is strong (stated WCAG 2.1 AA target, real semantic landmarks/skip-nav/focus/aria-labels, native controls, contrast tuned, axe-core in shared E2E bases ADC consumes, scans broadened to 31 axe test methods over roughly 29 distinct pages, re-counted 2026-08-14), and the chromium E2E/axe suite now blocks every production deploy (`e2e-gate` in `deploy.needs`, promoted 2026-07-02; validation run 28604877733 was fully green, satisfying the former "impl 8 pending a green run" condition). Maturity stops at 3, not 4: the rubric's §21 verification criterion pairs automated CI checks with periodic manual screen-reader/keyboard passes, and `adc-ACCESSIBILITY-SCREENREADER-PASS.md` (Website `docs-src/guides/`) still awaits a recorded dated pass: its results table is still the empty placeholder row `| _yyyy-mm-dd_ | | NVDA / Chrome | | |` at `:62`, re-confirmed 2026-08-01. Evidence: Common `MainLayout.razor:18,21,29,67,79` (skip-nav, role landmarks, role=alert); `EventList.razor:12,55,125` (aria-label icon buttons; **64 aria attributes across 18 ADC `.razor` files**); `Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/AccessibilityTests.cs:21-365` (31 axe test methods over roughly 29 distinct pages, including the conference-day surfaces; the prior 17-page figure was stale); `deploy.yml:531,866` (e2e-gate job, in deploy.needs); `adc-ACCESSIBILITY-SCREENREADER-PASS.md:62` (centralized in Website `docs-src/guides/` since 2026-07-20; runbook ready, dated pass pending, the maturity-4 lever) | -| 22 | Responsive & Cross-Browser | 2 | 3 | 8 | 6/16 | **↓ Maturity 4→3 (twenty-second cycle): the deploy gate was cut to a single engine, so cross-browser verification is no longer enforced.** The twenty-first cycle scored M4 on a deploy-gating `e2e-gate` that passed chromium + firefox + webkit; the 2026-07-18 Actions-minute reduction narrowed it to `browsers: '["chromium"]'` (`deploy.yml:541`, in the `e2e-gate` job at `:531`, still in `deploy.needs` at `:866`). Firefox and webkit now run only on the nightly, and since 2026-07-29 that nightly was thinned further to **alternating single-engine legs**: two separate crons, Monday firefox and Thursday webkit (`e2e.yml:49,:50`, rationale `:44-48`), so each non-chromium engine is verified once a week rather than both twice; on the scheduled event they are `continue-on-error` (`e2e.yml:144`). Cross-engine coverage is therefore nightly-advisory and thinner than when the maturity dropped, enforced by convention rather than automatically, which is the rubric's Consistent (3), not Optimized (4). This is a deliberate cost trade-off, recorded in the backlog's Deliberate/accepted section, not a regression in the responsive work. Implementation holds 8 on unchanged substance: fluid layouts via MudBlazor grid/breakpoints, data grids degrading to card lists on mobile (no horizontal-scroll/unusable-grid red flag), the shared `.mmca-touch-target` 48px affordance (Common, v1.94.0), and a documented Chromium/Firefox/WebKit matrix that still runs, just off the gate. Maturity 4 lever: re-add the two legs to the deploy gate, or add a `cross-browser-freshness` job to `deploy.needs` mirroring the dr/load/cross-service freshness pattern (`deploy.yml:549,606,663`) so a stale or red nightly leg blocks the deploy at near-zero runner cost. Evidence: `deploy.yml:531,541,866`; `e2e.yml:44-50,144`; Common `BreakpointConstants.cs:16-17` (<960px drives grid→card); `EventList.razor:28-60` (`MobileInfiniteScrollList`) + `EventCreate.razor:39-56` (MudGrid xs/sm); Common `app.css:195-200` (hide-below-desktop columns) | +| 22 | Responsive & Cross-Browser | 2 | 3 | 7 | 6/14 | **↓ Implementation 8→7 (twenty-seventh cycle): the rubric's density-options criterion has zero adoption in ADC, and content reflow is only partially met on the 17 non-DataGrid table pages, including the data-dense conference-day surfaces; this names the implementation lever the backlog had carried as "not yet identified" and places the substance at the bottom of the Strong band.** **↓ Maturity 4→3 (twenty-second cycle): the deploy gate was cut to a single engine, so cross-browser verification is no longer enforced.** The twenty-first cycle scored M4 on a deploy-gating `e2e-gate` that passed chromium + firefox + webkit; the 2026-07-18 Actions-minute reduction narrowed it to `browsers: '["chromium"]'` (`deploy.yml:541`, in the `e2e-gate` job at `:531`, still in `deploy.needs` at `:866`). Firefox and webkit now run only on the nightly, and since 2026-07-29 that nightly was thinned further to **alternating single-engine legs**: two separate crons, Monday firefox and Thursday webkit (`e2e.yml:49,:50`, rationale `:44-48`), so each non-chromium engine is verified once a week rather than both twice; on the scheduled event they are `continue-on-error` (`e2e.yml:144`). Cross-engine coverage is therefore nightly-advisory and thinner than when the maturity dropped, enforced by convention rather than automatically, which is the rubric's Consistent (3), not Optimized (4). This is a deliberate cost trade-off, recorded in the backlog's Deliberate/accepted section, not a regression in the responsive work. Implementation 7: the strong substance stands (fluid layouts via MudBlazor grid/breakpoints, data grids degrading to card lists on mobile with no horizontal-scroll/unusable-grid red flag, the shared `.mmca-touch-target` 48px affordance (Common, v1.94.0), and a documented Chromium/Firefox/WebKit matrix that still runs, just off the gate), but two rubric criteria are now verified short: density options have zero adoption anywhere in ADC, and content reflow is only partial on the 17 table pages that do not use the DataGrid's card-list degradation, including the conference-day surfaces. Maturity 4 lever: re-add the two legs to the deploy gate, or add a `cross-browser-freshness` job to `deploy.needs` mirroring the dr/load/cross-service freshness pattern (`deploy.yml:549,606,663`) so a stale or red nightly leg blocks the deploy at near-zero runner cost. Evidence: `deploy.yml:531,541,866`; `e2e.yml:44-50,144`; Common `BreakpointConstants.cs:16-17` (<960px drives grid→card); `EventList.razor:28-60` (`MobileInfiniteScrollList`) + `EventCreate.razor:39-56` (MudGrid xs/sm); Common `app.css:195-200` (hide-below-desktop columns) | | 23 | Front-End Performance | 2 | 4 | 8 | 8/16 | **↑ Maturity 3→4 (twentieth cycle): the Core Web Vitals budgets are now enforced, not advisory.** Server-side paging/filtering/sorting (no load-everything-then-page-in-memory), bounded virtualized infinite scroll with `@key`, debounced search, request cancellation, SSR prerender + `PersistentComponentState` handoff, and output caching all present: strong client-side hygiene. The nineteenth cycle correctly rejected a maturity lift because the vitals budgets were advisory by design; on 2026-07-11 they were recalibrated into real failing assertions (`WebVitalsTests.cs:76` hard-asserts LCP/FCP/TTFB/CLS budgets, `:17` the recalibration) and the suite runs unfiltered inside the deploy-gating chromium e2e-gate (`deploy.yml:531` e2e-gate job, in `deploy.needs` at `:866`), so a red vitals assertion blocks the production deploy: the same deploy-gate = M4 standard already applied to §28/§29/§31. It is a deploy gate, not a PR merge gate, which meets the rubric's automatically-enforced bar (`ArchitectureEvaluationCriteria.md:29`). Implementation holds at 8. Evidence: Common `EntityServiceBase.cs:51` (server-side paging/sort/filter via query params); `DataGridListPageBase.cs:403-496` (CTS-cancelled ServerData + prerender-bounded fetch, `:116-138,411-428` persist/restore); `MobileInfiniteScrollList.razor:16,61,165-167` | | 24 | Forms, Validation & UX Safety | 2 | 4 | 8 | 8/16 | **↑ Implementation 7→8 (twentieth cycle): TD-14 shipped, closing the sixteenth-cycle recalibration's sole systematic gap; Maturity holds 4 on the extended CI-gated fitness function.** Client validation mirrors server FluentValidation rules, the unsaved-changes guard reads current dirty state via a live accessor (avoids the stale-IsDirty red flag), submit is disabled while saving (no double-submit), `_isDirty` clears before NavigateTo on success (no false prompt), and destructive actions are confirmed via `MudMessageBox`. The former deduction is closed: all six Conference create forms now render the per-form `MudAlert` error summary with a localized heading (`EventCreate.razor:108` `@if (_form is { Errors.Length: > 0 })` + `Validation.CorrectFollowing`), and the gate enforces it: `FormsConventionTests.cs:18` extends `RequiredMarkers` with the `MudAlert Severity="Severity.Error"` + `Validation.CorrectFollowing` markers across `MinimumCreateForms >= 6`, and the former Profile-form exclusion is covered by the dedicated `ProfileForm_KeepsErrorSummaryAndPasswordValidation` fact (`FormsConventionTests.cs:32`), all in the CI.slnf arch gate (`MMCA.ADC.CI.slnf:58`). The formerly-raw exception snackbars stay localized whole-sentence keys (`Profile.razor.cs:235` `Snackbar.ChangePasswordFailed`, no `{ex.Message}` interpolation). **Held at 8 (a proposed 8→9 was adversarially rejected in the twenty-fifth cycle, 2026-08-01).** The prior cycle's named 8→9 lever, a bUnit render-level assertion of the summary's error items, genuinely shipped and is CI-gated, but a fresh read finds two criteria only partially met, so the 9-10 band's "all criteria met to a high standard, reference-quality" bar is not reached: (a) client validation does **not** mirror the server's cross-field and format rules, which is the category's first criterion and its first red flag, and (b) the form-level error summary is present on **7 of the 15** MudForm forms, so the create-form set that TD-14 covered is done while the rest of the authoring surface is not. Both are named as the new 8→9 levers in the backlog's implementation band. Evidence: `Tests/Architecture/MMCA.ADC.Architecture.Tests/FormsConventionTests.cs:18,32`; Common `FormsConventionTestsBase.cs:29`; `MMCA.ADC.CI.slnf:58`; `EventCreate.razor:8,108` (guard + per-form summary); `EventCreate.razor.cs:57,89,125` (client validation before submit; `_isDirty=false` before NavigateTo; Disabled while IsSaving); `Profile.razor.cs:235,251` (localized snackbar keys; MudMessageBox delete confirm); `Conference.Application/Events/UseCases/Create/EventCreateRequestValidator.cs:9` (server-side parity) | | 25 | Navigation & Information Arch | 2 | 4 | 8 | 8/16 | All criteria met and enforced: clean bookmarkable/typed routes, route-level `[Authorize]` guards (server-enforced via SSR cookie auth, not just UI hiding), role-aware menu filtering, documented per-actor `NavigationFlow.md` matching code, 404 (NotFoundPage) and 403 (dedicated `` page, `Routes.razor:26`) handled in the shell; bUnit route-authorization tests (per-PR gated via `MMCA.ADC.CI.slnf`) prevent role-guard regression. Impl below 10 only because some auth pages still require client-side nav (the prior "generic 403 alert" deduction is closed: a dedicated `` 403 page now renders in the shell)., Common `Routes.razor:6-31`; Common `NavMenu.razor:151-159` (RequiredRole/RequiredClaim filtering); `EventCreate.razor:2` ([Authorize(Roles="Organizer")]); **21 admin pages identically gated** + `Conference.UI.Tests/ManagementRouteAuthorizationTests.cs` + `Identity.UI.Tests/IdentityRouteAuthorizationTests.cs` (bUnit tier) | @@ -85,20 +85,20 @@ The headline finding remains a system whose backend and governance are near-exem | 33 | Developer Experience & Inner Loop | 2 | 3 | 8 | 6/16 | **Held at 3/8 for a second consecutive cycle: the proposed impl 8→9 was adversarially rejected again (twentieth and twenty-fifth cycles).** Inner loop hits nearly every criterion: genuine one-command Aspire orchestration, a documented gitignored `local.props` cross-repo override, consistent analyzer/editorconfig tooling shared with CI, a fast CI solution filter, and the former README-stub friction is resolved (`README.md:1` is now a full getting-started guide: prereqs, GITHUB_TOKEN/local.props bootstrap, run/test commands). The remaining friction keeps it in the Strong band: **broker parity is mitigated, not closed.** The AppHost provisions RabbitMQ only while prod runs Azure Service Bus (both via MassTransit), which matches the rubric's local-diverges-from-prod red flag (`ArchitectureEvaluationCriteria.md:838`). The 2026-07-29 restoration of the `servicebus-emulator-smoke` tier to the weekday nightly (`cross-service-tests.yml:145-147`, `workflow_dispatch` at `:26` plus cron `'0 6 * * 1-5'` at `:31`) is real progress but is **not** closure: the job is `continue-on-error: true` (`:150`, `timeout-minutes: 10` at `:149`) and gates nothing, since `cross-service-freshness` keys off the `cross-service` job (`:126-129`; gate at `deploy.yml:663`), so the Service Bus signal is advisory. The README sentence offered as evidence for the lift is itself inaccurate about that gate. Impl 9 requires closing (not documenting, and not advisory-testing) the parity gap; TD-17 in the backlog tracks the remaining half. Evidence: `README.md:1,74`; `cross-service-tests.yml:145-150` (anchors refreshed 2026-08-14 from the drifted `:144-149`/`:26,:30`); `deploy.yml:663`; `CLAUDE.md` (one-command `dotnet run --project Source/Hosting/MMCA.ADC.AppHost` → SQL/Redis/RabbitMQ/MailDev + 4 services + Gateway + UI); `AppHost/Program.cs:224-282` (health-based WaitFor ordering, deliberate no-WaitFor reverse gRPC edge); `local.props.template:5` (UseLocalMMCA) | | 34 | Architecture Governance & Docs | 2 | 4 | 9 | 8/18 | Reference-quality governance: a maintained ADR set (**001-078**, canonical in `../adr/`, indexed in `../adr/README.md`; the range advanced from 001-064 via the 2026-08-07 ADR audit and the 2026-08-14 enterprise wave) for every non-obvious decision, executable fitness functions (**29 architecture-test classes** (31 .cs files; 91 executed methods, last re-run green 2026-07-28) gating CI, incl. resilience/PII/concurrency/microservice-extraction/data-residency/cross-source-specification/UI-architecture/state-management/observability-pairing, **90 of the 91 methods inherited** from the shared `MMCA.Common.Testing.Architecture` rule library after the §13 pairing gate was lifted upstream, leaving one ADC-local method (the TD-14 Profile-form guard, `FormsConventionTests.cs:31`), ADR-015) rather than prose, a living architecture map + scored backlog, and documented conventions linking to the enforcing tests. The arch-test doc-comment caveat **is fixed** this cycle (`SpecificationConventionTests.cs:3-7` now frames the reverted polyglot layout as a forward safeguard). Governance improved this cycle: **this `ArchitectureScorecard.md` is now the canonical two-axis evaluation, versioned in-repo** (the rubric + framework facts moved to MMCA.Common, mirroring the ADR §34 pattern), and the prior ledger-lag is resolved (`RemediationBacklog.md` marks TD-01/TD-05 (#32/#14) done). The backlog is the single remediation + tech-debt ledger (the former `TECHDEBT.md` `TD-NN` register was folded into it 2026-06-26, matching Common/Store). Caveat (minor): ArchitecturalAnalysis.md still lives at the untracked workspace root. Evidence: `../adr/001-078` (indexed in `../adr/README.md`); `Tests/Architecture/MMCA.ADC.Architecture.Tests/` (31 .cs files: 29 test classes + map + global-usings); this `ArchitectureScorecard.md` (canonical two-axis) + `RemediationBacklog.md` (single ledger; TD-NN folded in: done TD-01/03/04/05/09/10, open TD-02/06/07/08) + workspace `ArchitecturalAnalysis.md` | -> **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 when the chromium E2E/axe suite became a deploy gate (§21 now M3/I8; the recorded screen-reader pass is the remaining maturity-4 lever). **The 2026-06-30 enforcement-gate wave lifted §16/§24/§27/§29/§31 maturity 3→4**, making each category's already-strong implementation enforced by a CI gate (three new fitness tests in the CI.slnf arch gate, plus the cost-guard and dr-freshness deploy gates), which closed most of the Maturity-vs-Implementation inversion. The prior 2026-06-29 re-score had **corrected §29 maturity 4→3** (the DR restore drill was then a scheduled-but-non-gating cron; this wave's `dr-freshness` deploy gate now makes it an actual gate, so §29 is back to maturity 4 on substantively different evidence) and **lifted §32 implementation 8→9** (CI restore runs `--locked-mode` in both gating jobs); §5/§7/§13/§25 were adversarially FLAG-re-checked and confirmed unchanged. **The subsequent v1.93.0 sweep (2026-06-30) then lifted §5 Vertical Slice Architecture maturity 3→4** (the slice-cohesion fitness function `SliceCohesionTests` is confirmed a CI merge gate in `MMCA.ADC.CI.slnf`), and re-confirmed §7 at M4/I8 (a proposed impl 8→9 lift adversarially rejected over the bidirectional Conference↔Engagement gRPC pair). Earlier waves had flipped §27 i18n from N/A to scored (M3/I8), moved §20/§24 implementation 8→9, lifted §29 impl 8→9 (graceful-shutdown failure test CI-gated), closed §32 *mature-but-not-locked* (lock files committed, M3→4/I7→8), §29 recovery (drilled), and §30 (residency matched). **The sixteenth-cycle full re-score (2026-07-03) recalibrated §24 Implementation 9→7** (the per-form error summary exists only on the Profile form, the six create forms surface a generic validation snackbar, and the Profile handlers show raw exception text), the only score move of that cycle; §24 Maturity holds 4 on the `FormsConventionTests` gate and the residual is tracked as TD-14. **The eighteenth (2026-07-06, pin v1.106.0) and nineteenth (2026-07-10, pin v1.110.0) full re-scores moved no score; the nineteenth adversarially rejected three proposed moves (§12 impl 8→9 on the Notification single-replica pin, §23 maturity 3→4 on the advisory-by-design vitals budgets, §34 impl 9→8 as unsupported), each a verified non-move.** **The twentieth-cycle full re-score (2026-07-15, pin v1.116.0) lifted §18/§19/§23 maturity 3→4 (the §18/§19 fitness gates now exist in the CI.slnf arch gate; the §23 vitals budgets became enforced assertions inside the deploy-gating e2e-gate on 2026-07-11) and §13/§24 implementation (8→9 on the shipped SLO workbook + OPERATIONS.md runbook; 7→8 on TD-14's per-form error summaries), while re-rejecting the §34 9→8 downgrade, rejecting a §33 impl 8→9 on the open broker-parity red flag, and correcting §28's false "E2E #5 un-skipped" claim in place (score held).** This closes the §18/§19 half of the Maturity-vs-Implementation inversion; the below-maturity-4 set narrows to §12/§13/§21/§22/§33. **The twenty-first-cycle full re-score (2026-07-17, pin v1.117.0) lifted §13 and §22 maturity 3→4** (the ADC-local alert-runbook pairing fitness gate and the fully gating three-browser e2e-gate, both shipped 2026-07-16), adversarially rejected the §27 impl 8→9 pseudo-loc candidacy (3 pages of 30+, partial), re-confirmed §12 at M3/I8, and corrected the stale nineteenth-cycle draft prose that PR #15 accidentally committed; the below-maturity-4 set narrows to §12/§21/§33. **The twenty-second-cycle full re-score (2026-07-21, pin v1.121.0) moved two scores down, neither on a quality regression: §22 maturity 4→3** (the 2026-07-18 CI-minute reduction cut the deploy `e2e-gate` to chromium only, `deploy.yml:488`, leaving firefox/webkit nightly-advisory, `e2e.yml:119`, a deliberate cost trade-off now recorded as such) **and §18 implementation 9→8** (the largest code-behind is flush at the enforced 400-line cap with zero headroom, TD-16), while re-rejecting the §27 impl 8→9 pseudo-loc candidacy a second time on unchanged evidence. The below-maturity-4 set widens to §12/§21/§22/§33, and this reopens the Maturity-vs-Implementation picture on the front-end operational side: the §22 gap is not "the tests do not exist" but "the tests no longer gate." **The twenty-third-cycle full re-score (2026-07-23, pin v1.123.0) moved no score** (both proposed maturity lifts, §12 and §21, adversarially rejected as verified non-moves). **The twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0) moved one score, down: §15 Best Practices & Code Quality implementation 8→7**, on suppression/NoWarn hygiene drift rather than any code-quality regression (an audit suppression expired by its own written removal condition, three unjustified global NoWarn codes, and the MAUI project sitting outside every CI build and outside the audited dependency graph). This is a third distinct shape of axis gap: not "no gate exists" (§18/§19 pre-2026-07-15) and not "the gate stopped gating" (§22), but **the enforced perimeter has a documented hole in it**, since maturity 4 is measured on the `CI.slnf` graph while one shipped project sits outside that graph entirely. §15 keeps maturity 4 and so stays in the protect set while sitting at the top of the implementation band, which is exactly the two-axis behaviour the bands exist to make visible. The same cycle adjudicated the §27 pseudo-localization lift **DEFERRED** after a third rejection on unchanged evidence, so it is no longer carried as an open candidacy. **The twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0) moved no score**, and all six adjudications were rejected implementation lifts (§5, §13, §24, §27, §31, §33). That is the cycle's actual finding and a fourth shape of axis gap: not "no gate exists", not "the gate stopped gating", not "the enforced perimeter has a hole", but **one unmet criterion each, in six independent categories**, where the work that would close them is named and small but has not shipped. Two of the six (§27 and §33) are now on their fourth and second consecutive rejection respectively, which is the signal that they need a decision (schedule it or record it as deliberate) rather than another re-proposal. +> **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 when the chromium E2E/axe suite became a deploy gate (§21 now M3/I8; the recorded screen-reader pass is the remaining maturity-4 lever). **The 2026-06-30 enforcement-gate wave lifted §16/§24/§27/§29/§31 maturity 3→4**, making each category's already-strong implementation enforced by a CI gate (three new fitness tests in the CI.slnf arch gate, plus the cost-guard and dr-freshness deploy gates), which closed most of the Maturity-vs-Implementation inversion. The prior 2026-06-29 re-score had **corrected §29 maturity 4→3** (the DR restore drill was then a scheduled-but-non-gating cron; this wave's `dr-freshness` deploy gate now makes it an actual gate, so §29 is back to maturity 4 on substantively different evidence) and **lifted §32 implementation 8→9** (CI restore runs `--locked-mode` in both gating jobs); §5/§7/§13/§25 were adversarially FLAG-re-checked and confirmed unchanged. **The subsequent v1.93.0 sweep (2026-06-30) then lifted §5 Vertical Slice Architecture maturity 3→4** (the slice-cohesion fitness function `SliceCohesionTests` is confirmed a CI merge gate in `MMCA.ADC.CI.slnf`), and re-confirmed §7 at M4/I8 (a proposed impl 8→9 lift adversarially rejected over the bidirectional Conference↔Engagement gRPC pair). Earlier waves had flipped §27 i18n from N/A to scored (M3/I8), moved §20/§24 implementation 8→9, lifted §29 impl 8→9 (graceful-shutdown failure test CI-gated), closed §32 *mature-but-not-locked* (lock files committed, M3→4/I7→8), §29 recovery (drilled), and §30 (residency matched). **The sixteenth-cycle full re-score (2026-07-03) recalibrated §24 Implementation 9→7** (the per-form error summary exists only on the Profile form, the six create forms surface a generic validation snackbar, and the Profile handlers show raw exception text), the only score move of that cycle; §24 Maturity holds 4 on the `FormsConventionTests` gate and the residual is tracked as TD-14. **The eighteenth (2026-07-06, pin v1.106.0) and nineteenth (2026-07-10, pin v1.110.0) full re-scores moved no score; the nineteenth adversarially rejected three proposed moves (§12 impl 8→9 on the Notification single-replica pin, §23 maturity 3→4 on the advisory-by-design vitals budgets, §34 impl 9→8 as unsupported), each a verified non-move.** **The twentieth-cycle full re-score (2026-07-15, pin v1.116.0) lifted §18/§19/§23 maturity 3→4 (the §18/§19 fitness gates now exist in the CI.slnf arch gate; the §23 vitals budgets became enforced assertions inside the deploy-gating e2e-gate on 2026-07-11) and §13/§24 implementation (8→9 on the shipped SLO workbook + OPERATIONS.md runbook; 7→8 on TD-14's per-form error summaries), while re-rejecting the §34 9→8 downgrade, rejecting a §33 impl 8→9 on the open broker-parity red flag, and correcting §28's false "E2E #5 un-skipped" claim in place (score held).** This closes the §18/§19 half of the Maturity-vs-Implementation inversion; the below-maturity-4 set narrows to §12/§13/§21/§22/§33. **The twenty-first-cycle full re-score (2026-07-17, pin v1.117.0) lifted §13 and §22 maturity 3→4** (the ADC-local alert-runbook pairing fitness gate and the fully gating three-browser e2e-gate, both shipped 2026-07-16), adversarially rejected the §27 impl 8→9 pseudo-loc candidacy (3 pages of 30+, partial), re-confirmed §12 at M3/I8, and corrected the stale nineteenth-cycle draft prose that PR #15 accidentally committed; the below-maturity-4 set narrows to §12/§21/§33. **The twenty-second-cycle full re-score (2026-07-21, pin v1.121.0) moved two scores down, neither on a quality regression: §22 maturity 4→3** (the 2026-07-18 CI-minute reduction cut the deploy `e2e-gate` to chromium only, `deploy.yml:488`, leaving firefox/webkit nightly-advisory, `e2e.yml:119`, a deliberate cost trade-off now recorded as such) **and §18 implementation 9→8** (the largest code-behind is flush at the enforced 400-line cap with zero headroom, TD-16), while re-rejecting the §27 impl 8→9 pseudo-loc candidacy a second time on unchanged evidence. The below-maturity-4 set widens to §12/§21/§22/§33, and this reopens the Maturity-vs-Implementation picture on the front-end operational side: the §22 gap is not "the tests do not exist" but "the tests no longer gate." **The twenty-third-cycle full re-score (2026-07-23, pin v1.123.0) moved no score** (both proposed maturity lifts, §12 and §21, adversarially rejected as verified non-moves). **The twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0) moved one score, down: §15 Best Practices & Code Quality implementation 8→7**, on suppression/NoWarn hygiene drift rather than any code-quality regression (an audit suppression expired by its own written removal condition, three unjustified global NoWarn codes, and the MAUI project sitting outside every CI build and outside the audited dependency graph). This is a third distinct shape of axis gap: not "no gate exists" (§18/§19 pre-2026-07-15) and not "the gate stopped gating" (§22), but **the enforced perimeter has a documented hole in it**, since maturity 4 is measured on the `CI.slnf` graph while one shipped project sits outside that graph entirely. §15 keeps maturity 4 and so stays in the protect set while sitting at the top of the implementation band, which is exactly the two-axis behaviour the bands exist to make visible. The same cycle adjudicated the §27 pseudo-localization lift **DEFERRED** after a third rejection on unchanged evidence, so it is no longer carried as an open candidacy. **The twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0) moved no score**, and all six adjudications were rejected implementation lifts (§5, §13, §24, §27, §31, §33). That is the cycle's actual finding and a fourth shape of axis gap: not "no gate exists", not "the gate stopped gating", not "the enforced perimeter has a hole", but **one unmet criterion each, in six independent categories**, where the work that would close them is named and small but has not shipped. Two of the six (§27 and §33) are now on their fourth and second consecutive rejection respectively, which is the signal that they need a decision (schedule it or record it as deliberate) rather than another re-proposal. **The twenty-sixth-cycle full re-score (2026-08-14, pin v1.152.0) moved no score** (all eight adjudications rejected lifts). **The twenty-seventh-cycle full re-score (2026-08-23, pin v1.160.0) moved two scores, both down on the implementation axis: §4 9→8** (public-setter cross-aggregate navigations, aggregate-external validation of Event's newer optional fields, and primitive obsession on `OrganizerContactEmail` where the `Email` VO covers the same concept elsewhere) **and §22 8→7** (zero density-option adoption plus partial content reflow on the non-DataGrid table pages, naming the lever the backlog had carried as "not yet identified"). Both are fresh-read recalibrations against drifted prior citations, not code regressions; §4 enters the implementation band for the first time, and §22 rises to its joint top alongside §15. The same cycle rejected all eight proposed lifts and recorded the conditionality of the e2e-gate (UI-affecting diffs only, skipped gate accepted by `deploy`) as **TD-20** rather than a score move. ## Indices -- **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 4→3 (-2 weighted) after the 2026-07-18 Actions-minute reduction cut the deploy `e2e-gate` to chromium only (`deploy.yml:488`), leaving firefox/webkit on the nightly matrix where they are `continue-on-error` (`e2e.yml:119`), so cross-engine verification is no longer automatically enforced. The open maturity-3 set widens to §12/§21/§22/§33. The twenty-first-cycle §13 3→4 lift and the twentieth-cycle §18/§19/§23 3→4 lifts stand.) Re-confirmed unchanged by the twenty-third-cycle full re-score (2026-07-23, pin v1.123.0, no moves; the §12 and §21 maturity 3→4 proposals were adversarially rejected) and again by the **twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0)**, where the single score move was on the implementation axis (§15 8→7) and no category crossed a maturity band, so the maturity numerator, denominator and open maturity-3 set (§12/§21/§22/§33) are all unchanged. Re-confirmed again by the **twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0, HEAD `995a7886`)**: no score moved on either axis, the six adversarial adjudications were all rejected implementation lifts, and the sum was re-derived independently this run; re-confirmed once more by the **twenty-sixth-cycle full re-score (2026-08-14, pin v1.152.0, HEAD `19021d93`)**, where all eight adjudications were again rejected lifts and the numerator was re-summed unchanged (Σ(M×w) = 311, Σ(w) = 80). -- **Implementation index** = Σ(impl×weight) ÷ Σ(weight×10) = 685 ÷ 800 = **85.6%** (down from 85.9%, 687/800: the twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0) corrected **§15 8→7** (-2 weighted) on suppression/NoWarn hygiene drift plus the MAUI project sitting outside the CI-audited graph, the only score move of that cycle and the only rank change on either band. The §27 impl 8→9 pseudo-loc candidacy was adversarially rejected for a **third** consecutive cycle on byte-identical evidence and is now adjudicated DEFERRED rather than carried open. Prior basis follows: down from 86.3%, 690/800: §18 impl 9→8 (-3 weighted) on the code-behind flush at the enforced 400-line cap with six more files in the 360-379 band, TD-16. The §27 impl 8→9 pseudo-loc candidacy was adversarially rejected for a second consecutive cycle on unchanged evidence, 3 public pages of 36 routable. The twentieth-cycle §13 8→9 and §24 7→8 lifts, the seventeenth-cycle §27 7→8 lift, and the fourteenth-cycle §6 10→9 correction stand.) Re-confirmed unchanged by the twenty-third-cycle full re-score (2026-07-23, pin v1.123.0, no moves; every implementation score was then 8 or higher). As of the twenty-fourth cycle §15 at 7 is the **only** implementation score below 8; the other 33 remain 8 or higher. Re-confirmed again by the **twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0)**: no score moved, and the six proposed lifts (§5, §13, §24, §27, §31, §33) were each adversarially rejected against current source, so the numerator was re-derived rather than inherited (Σ(I×w) = 685 re-summed this run); the **twenty-sixth-cycle full re-score (2026-08-14, pin v1.152.0, HEAD `19021d93`)** rejected all eight proposed lifts (§5, §7, §12 as an M3→4, §13 as a 9→10, §15, §23, §28, §31) and re-summed the same 685. The gap to a full 800 is **35 weighted points** spread across 15 categories, which is what the backlog's implementation band ranks. +- **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 4→3 (-2 weighted) after the 2026-07-18 Actions-minute reduction cut the deploy `e2e-gate` to chromium only (`deploy.yml:488`), leaving firefox/webkit on the nightly matrix where they are `continue-on-error` (`e2e.yml:119`), so cross-engine verification is no longer automatically enforced. The open maturity-3 set widens to §12/§21/§22/§33. The twenty-first-cycle §13 3→4 lift and the twentieth-cycle §18/§19/§23 3→4 lifts stand.) Re-confirmed unchanged by the twenty-third-cycle full re-score (2026-07-23, pin v1.123.0, no moves; the §12 and §21 maturity 3→4 proposals were adversarially rejected) and again by the **twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0)**, where the single score move was on the implementation axis (§15 8→7) and no category crossed a maturity band, so the maturity numerator, denominator and open maturity-3 set (§12/§21/§22/§33) are all unchanged. Re-confirmed again by the **twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0, HEAD `995a7886`)**: no score moved on either axis, the six adversarial adjudications were all rejected implementation lifts, and the sum was re-derived independently this run; re-confirmed once more by the **twenty-sixth-cycle full re-score (2026-08-14, pin v1.152.0, HEAD `19021d93`)**, where all eight adjudications were again rejected lifts and the numerator was re-summed unchanged (Σ(M×w) = 311, Σ(w) = 80); and again by the **twenty-seventh-cycle full re-score (2026-08-23, pin v1.160.0, HEAD `96f0919a`)**, whose two score moves were both on the implementation axis, so the maturity numerator and the open maturity-3 set (§12/§21/§22/§33) are unchanged (Σ(M×w) = 311 re-summed this run). +- **Implementation index** = Σ(impl×weight) ÷ Σ(weight×10) = 680 ÷ 800 = **85.0%** (down from 85.6%, 685/800: the **twenty-seventh-cycle full re-score (2026-08-23, pin v1.160.0, HEAD `96f0919a`)** corrected **§4 9→8** (-3 weighted: public-setter cross-aggregate navigations, aggregate-external validation of Event's newer optional fields, primitive obsession on `OrganizerContactEmail`) and **§22 8→7** (-2 weighted: zero density-option adoption, partial content reflow on the 17 non-DataGrid table pages), both fresh-read recalibrations against drifted prior citations; the same cycle rejected all eight proposed lifts (§5, §7, §15, §17, §18, §21, §28, §31) and the sum was re-derived independently this run (Σ(I×w) = 680). The gap to a full 800 is **40 weighted points** spread across 16 categories, which is what the backlog's implementation band ranks; §15 at 7 and now §22 at 7 are the two implementation scores below 8. Prior basis follows: down from 85.9%, 687/800: the twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0) corrected **§15 8→7** (-2 weighted) on suppression/NoWarn hygiene drift plus the MAUI project sitting outside the CI-audited graph, the only score move of that cycle and the only rank change on either band. The §27 impl 8→9 pseudo-loc candidacy was adversarially rejected for a **third** consecutive cycle on byte-identical evidence and is now adjudicated DEFERRED rather than carried open. Prior basis follows: down from 86.3%, 690/800: §18 impl 9→8 (-3 weighted) on the code-behind flush at the enforced 400-line cap with six more files in the 360-379 band, TD-16. The §27 impl 8→9 pseudo-loc candidacy was adversarially rejected for a second consecutive cycle on unchanged evidence, 3 public pages of 36 routable. The twentieth-cycle §13 8→9 and §24 7→8 lifts, the seventeenth-cycle §27 7→8 lift, and the fourteenth-cycle §6 10→9 correction stand.) Re-confirmed unchanged by the twenty-third-cycle full re-score (2026-07-23, pin v1.123.0, no moves; every implementation score was then 8 or higher). From the twenty-fourth through the twenty-sixth cycle §15 at 7 was the **only** implementation score below 8. Re-confirmed again by the **twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0)**: no score moved, and the six proposed lifts (§5, §13, §24, §27, §31, §33) were each adversarially rejected against current source, so the numerator was re-derived rather than inherited (Σ(I×w) = 685 re-summed that run); the **twenty-sixth-cycle full re-score (2026-08-14, pin v1.152.0, HEAD `19021d93`)** rejected all eight proposed lifts (§5, §7, §12 as an M3→4, §13 as a 9→10, §15, §23, §28, §31) and re-summed the same 685. - **The implementation index reads directly against 100%** (recalibrated 2026-08-01): a 10 is awardable when an implementation is *almost perfect* (every criterion met at reference quality, no red flags, at most trivial polish left), superseding the rubric's literal "nothing left to improve" wording. The prior "9 is the top attainable rung / 90% ceiling" line is therefore retired and removed from this block. The denominators were never scaled (they stay ×10), so these percentages remain directly comparable to every pre-recalibration cycle. Backlog scheduling still targets 9, because ranking against 10 would put nearly every strong category in the band and drown the real gaps: the 9→10 rung is recognition earned at re-score time, not scheduled work. -- **Weaker axis:** Implementation (execution quality), by ~11.6 points +- **Weaker axis:** Implementation (execution quality), by ~12.2 points - **N/A (excluded from denominators):** none: §27 Internationalization is scored (M4/I8) as of ADR-027 (which supersedes the single-locale ADR-011), so no category is excluded from the denominators. - **§32 weight = 2** (the default; raised to 3 only for the published framework MMCA.Common) ## Top 5 strengths -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): `Tests/Architecture/MMCA.ADC.Architecture.Tests/` with `LayerDependencyTests`, `DomainPurityTests`, `MicroserviceExtractionTests` (thin subclasses of the shared rule library), plus PiiConvention/Concurrency/IntegrationEventContract/Specification/SliceCohesion/**DataResidency**/**ConstructorDependencyCount**/**BrandColorToken**/**UIArchitectureConvention**/**StateManagementConvention**/**ObservabilityConvention**: 90 of the 91 methods inherited from the shared `MMCA.Common.Testing.Architecture` bases (ADR-015), leaving one ADC-local method (the TD-14 Profile-form guard); ADRs 001-078 (canonical in `../adr/`) capture the 'why'. The counts are the 2026-07-28 measured snapshot; the twenty-fifth cycle did not re-run the suite. +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): `Tests/Architecture/MMCA.ADC.Architecture.Tests/` with `LayerDependencyTests`, `DomainPurityTests`, `MicroserviceExtractionTests` (thin subclasses of the shared rule library), plus PiiConvention/Concurrency/IntegrationEventContract/Specification/SliceCohesion/**DataResidency**/**ConstructorDependencyCount**/**BrandColorToken**/**UIArchitectureConvention**/**StateManagementConvention**/**ObservabilityConvention**: 90 of the 91 methods inherited from the shared `MMCA.Common.Testing.Architecture` bases (ADR-015), leaving one ADC-local method (the TD-14 Profile-form guard); ADRs 001-096 (canonical in `../adr/`) capture the 'why'. The counts are the 2026-07-28 measured snapshot; the later cycles did not re-run the suite. - _Remediation:_ Resolve the provenance nit, `ArchitecturalAnalysis.md` still lives at the untracked workspace root outside all three git repos (the arch-test doc-comment drift was already fixed in `SpecificationConventionTests.cs`). - _Expected delta:_ Tracking the governance docs in-repo lifts §34 impl 9→10. @@ -114,7 +114,7 @@ The headline finding remains a system whose backend and governance are near-exem - _Remediation:_ Convert the conference-day surge/revert from a manual play with a drift alarm into a scheduled, automated scale event with an automatic revert. - _Expected delta:_ Automating the surge/revert lifts §31 impl 8→9. -5. **Non-inverted test pyramid with a deploy-gated integration tier AND a deploy-gated chromium E2E/axe suite**: §14 (mat 4 / impl 9) and §28 (mat 4 / impl 8): 1507 unit/UI Fact/Theory across 223 files; 91 architecture-test methods across 29 test classes; 303 gating integration methods across four per-service WAF tiers; the integration tier, a 55.5% unit-tier coverage floor, cost-guard, dr-freshness, and the chromium E2E gate all block deploy (`deploy.yml:866,:254`). E2E #5 is active again (un-quarantined 2026-07-19, plain `[Fact]` at `SpeakerSelfServiceTests.cs:58`) while the underlying split-query fix stays guarded by the active `SessionIncludeChildrenRegressionTests`. +5. **Non-inverted test pyramid with a deploy-gated integration tier AND a deploy-gated chromium E2E/axe suite**: §14 (mat 4 / impl 9) and §28 (mat 4 / impl 8): 1507 unit/UI Fact/Theory across 223 files; 91 architecture-test methods across 29 test classes; 303 gating integration methods across four per-service WAF tiers; the integration tier gates every PR as a required check, and the coverage floor, cost-guard, dr-freshness, and the chromium E2E gate sit in `deploy.needs` (`deploy.yml:866,:254`), though the E2E/axe/CWV gate is **conditional**: it runs only when the diff is UI-affecting and `deploy` accepts a skipped gate (`deploy.yml:538,:896`, recorded as TD-20). E2E #5 is active again (un-quarantined 2026-07-19, plain `[Fact]` at `SpeakerSelfServiceTests.cs:58`) while the underlying split-query fix stays guarded by the active `SessionIncludeChildrenRegressionTests`. - _Remediation:_ Record the manual screen-reader pass (§21 maturity 4 lever). The firefox/webkit gating half regressed on 2026-07-18: the deploy gate now runs chromium only (`deploy.yml:541`), and the nightly was thinned again on 2026-07-29 to alternating single-engine legs (`e2e.yml:49,:50`), so §22 stays at maturity 3 and needs either the two legs restored to the gate or a `cross-browser-freshness` job. - _Expected delta:_ SR pass lifts §21 maturity 3→4 (the last weight-3 maturity gap); a cross-browser freshness gate lifts §22 back to maturity 4. @@ -130,7 +130,7 @@ The headline finding remains a system whose backend and governance are near-exem - _Remediation:_ Move the SQL data plane onto private endpoints (disable public network access, drop the 0.0.0.0 rule). This is the VNet + private-endpoint epic, which requires recreating the Container Apps environment, hence deferred. - _Expected delta:_ The managed-identity switch is DONE (§17 impl 8→9 realized). Closing the remaining public-network-access flag would lift §11 impl 9→10. -3. **The enforced-analyzer perimeter has a documented hole, and one suppression has outlived its own removal condition:** §15 (mat 4 / impl 7, weight 2), the **top implementation-band item** at implPriority 4 and unchanged at this cycle's HEAD. Enforcement inside `CI.slnf` is strong (five analyzers at error, TWAE, AnalysisMode=All, `--locked-mode`), but the MAUI `MMCA.ADC.UI` project sits outside every CI build (`MMCA.ADC.CI.slnf` lists only the two web UI hosts at `:25,:26`; no runner installs the `maui-android` workload), so analyzers and TWAE are review-only there and the gating vulnerable-package scan (`deploy.yml:319`, exit at `:328`) never audits that graph, which is precisely the graph the `Directory.Build.props:8-12` suppressions exist for. Separately, the `GHSA-2m69-gcr7-jv3q` SQLite suppression (`Directory.Build.props:54`) is expired by its own written condition (`:45-52`): ADC now pins **v1.152.0**, Common removed its own entry and pins the patched bundle directly (3.0.5), and ADR-038 already records the accepted-advisory list as empty. Three of the four global NoWarn codes (`:26`) carry no justification or date (S8970, the fourth, does). This is hygiene drift and a scope gap, not a code-quality regression. +3. **The enforced-analyzer perimeter has a documented hole, and one suppression has outlived its own removal condition:** §15 (mat 4 / impl 7, weight 2), the **joint-top implementation-band item** at implPriority 4 (alongside §22, whose impl dropped to 7 this cycle) and unchanged at this cycle's HEAD. Enforcement inside `CI.slnf` is strong (five analyzers at error, TWAE, AnalysisMode=All, `--locked-mode`), but the MAUI `MMCA.ADC.UI` project sits outside every CI build (`MMCA.ADC.CI.slnf` lists only the two web UI hosts at `:25,:26`; no runner installs the `maui-android` workload), so analyzers and TWAE are review-only there and the gating vulnerable-package scan (`deploy.yml:319`, exit at `:328`) never audits that graph, which is precisely the graph the `Directory.Build.props:8-12` suppressions exist for. Separately, the `GHSA-2m69-gcr7-jv3q` SQLite suppression (`Directory.Build.props:54`) is expired by its own written condition (`:45-52`): ADC now pins **v1.160.0**, twenty-five releases past the v1.121.0 SQLite sweep, Common removed its own entry and pins the patched bundle directly (3.0.5), and ADR-038 already records the accepted-advisory list as empty. Three of the four global NoWarn codes (`:26`) carry no justification or date (S8970, the fourth, does). This is hygiene drift and a scope gap, not a code-quality regression. - _Remediation:_ Delete the expired suppression and justify-or-drop the three NoWarn codes (effort S), verified by a **full-solution package-mode restore**, not `CI.slnf`, since the MAUI graph is exactly what `CI.slnf` omits. If MAUI genuinely still needs the entry, narrow it to that project with a fresh dated justification rather than keeping a stale global one. Store carries the identical entry and should be swept in the same pass. - _Expected delta:_ The two hygiene items lift §15 impl 7→8 (+2 weighted, restoring the index to 85.9%). Bringing MAUI inside a CI build is the 8→9 lever, recorded as **TD-18** and deliberately deferred against the 2026-07-18 Actions-minute reduction. - _(Prior occupant, retained for the audit trail: ~~No lock files for the consumer apps~~ RESOLVED, TD-01 closed, §32 now mat 4 / impl 9. ADC commits **66** `packages.lock.json` (`RestorePackagesWithLockFile=true`; initially 58 at commit `6248273`), its vuln-audit + SBOM are blocking PR gates, and CI restore runs `--locked-mode` in both gating jobs (`deploy.yml:199`, `:299`), so lock-file drift is tamper-enforced at restore. Residual: MassTransit v8 is pinned only transitively, inherited from Common, not in ADC's own props.)_ diff --git a/docs-src/governance/adc-RemediationBacklog.md b/docs-src/governance/adc-RemediationBacklog.md index 68738fb..bb343fc 100644 --- a/docs-src/governance/adc-RemediationBacklog.md +++ b/docs-src/governance/adc-RemediationBacklog.md @@ -1,6 +1,6 @@ # MMCA.ADC: Architecture Remediation Backlog -Derived from `ArchitectureScorecard.md` (single-axis 0-4, baseline **75%**, 241/320, dated 2026-06-08). **Current authoritative two-axis scores (twenty-sixth-cycle full re-score, 2026-08-14, pin v1.152.0, HEAD `19021d93`): Maturity 97.2% (311/320) / Implementation 85.6% (685/800), no score move on either axis.** All 34 categories were re-confirmed from evidence read this run, and all eight adversarial adjudications (§5, §7, §12, §13, §15, §23, §28, §31) were proposed lifts that were **rejected**, each one criterion short. See the Index note for the cycle record. +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, 2026-08-23, pin v1.160.0, HEAD `96f0919a`): Maturity 97.2% (311/320) / Implementation 85.0% (680/800), two scores moved, both down on the implementation axis (§4 9→8, §22 8→7).** All eight adversarial adjudications of proposed lifts (§5, §7, §15, §17, §18, §21, §28, §31) were **rejected**, each one criterion short. See the Index note for the cycle record. Tasks are ranked on **both scorecard axes**, one band per axis (two-axis policy adopted 2026-07-28): - **Maturity band:** every category scoring **maturity < 4**, ranked by **priority = (4 − maturity) × weight**. - **Implementation band:** every category scoring **implementation <= 8**, ranked by **implPriority = max(0, 9 − implementation) × weight**. The scheduling target stays **9, not 10**, but the reason changed on 2026-08-01: a 10 is now awardable for an *almost perfect* implementation, so it is no longer unreachable. Ranking against 10 would instead put nearly every strong category in the band and drown the real gaps, so the 9→10 rung is recognition earned at re-score time, never scheduled work. 9 mirrors maturity's target of 4 for scheduling purposes. @@ -9,9 +9,9 @@ Higher priority = bigger weighted gap = more index points per unit of effort. A > **This is the single remediation ledger.** The former `TECHDEBT.md` tactical register is **folded in here** (2026-06-26): each deferred sub-item keeps its `TD-NN` ID and lives under its `#NN` category with its blocker, resolution path, and effort estimate; the recorded-but-not-scheduled choices live in the **Deliberate / accepted** section below. There is no separate tech-debt file (matching MMCA.Common and MMCA.Store). **Effort key:** **S** ≈ hours · **M** ≈ ~1 day · **L** ≈ multi-day. -> **⚠️ Index note (2026-06-27).** The 75% / 241-320 figure is the **2026-06-08 single-axis baseline** and is **not recomputed** as items below are ticked: many already-`RESOLVED` rows (#11, #14, #26, #29, #30, #32, …) have moved the real total well past it. For the **current, authoritative** scores use the **canonical, in-repo** [`ArchitectureScorecard.md`](../governance/adc-ArchitectureScorecard.md) (two-axis, at framework **v1.152.0**: **Maturity 97.2% (311/320) / Implementation 85.6% (685/800)**). This backlog remains the living *what-to-do-next* checklist; trust the scorecard for scores. **Closed 2026-06-26/27:** #32 (TD-01 lock files + blocking supply-chain gates), the #14 coverage floor (TD-05), #26 (Gateway header-regression test), the #29 graceful-shutdown test (scorecard §29 impl 8→9), and **#5** (slice-cohesion fitness function, scorecard §5 impl 7→8, on the v1.85.0 sweep). **Closed on the v1.86.0 i18n + dark-mode sweep (2026-06-27):** the **#29 scheduled DR-drill gate** (scorecard §29 maturity 3→4), **#24** change-password client validation (scorecard §24 impl 8→9), the **#20** landing-page brand-token dedupe (scorecard §20 impl 8→9), and **#27** i18n flips from N/A to scored (M3/I8, ADR-027 supersedes 011). **Activated 2026-06-28:** managed-identity SQL DB auth in production (`useManagedIdentitySql=true`, scorecard §17 impl 8→9; #11 holds at 9, now capped only by the deferred public-network-access epic). The last big open lever is the **E2E/axe merge gate** (TD-06/07). **Reconciled 2026-06-29 (re-score, pin v1.92.0):** §29 was **REOPENED** (scorecard §29 corrected maturity 4→3: the `dr-drill.yml` cron is scheduled but gates nothing, so it is Consistent/M3 not an automatic CI gate, the same standard §28 is held to), and the prior "#29 DR-drill gate closed maturity 3→4" claim above is withdrawn; **#16** was also reopened (scorecard §16 is maturity 3; deleting the orphan-test folder did not by itself reach 4); **§32** moved impl 8→9 (CI restore already runs `--locked-mode` in both gating jobs, `deploy.yml:40`/`:119`); and the backlog was caught up to the scorecard by closing #6/#8/#17/#18/#20/#26/#30 (all already at maturity 4). **Reconciled 2026-06-30 (enforcement-gate wave):** #16/#24/#27/#29/#31 lifted maturity 3→4 by adding CI-enforced governance over already-strong implementation: #24 `FormsConventionTests`, #27 `TranslationCompletenessTests`, and #16 `FrameworkVersionConsistencyTests` run in the CI.slnf arch gate (locally verified green, 74/74 arch tests pass); #31 cost-guard and #29 dr-freshness are wired into `deploy.needs` (committed, activate on the next push). **Reconciled 2026-06-30 (v1.92.0→v1.93.0 sweep, the Common tenth-wave):** §5 Vertical Slice Architecture lifted maturity 3→4 (the slice-cohesion fitness function `SliceCohesionTests` is confirmed a CI merge gate in `MMCA.ADC.CI.slnf`), and §7 was adversarially FLAG-re-checked (a proposed impl 8→9 lift rejected) and confirmed unchanged at M4/I8. Scorecard now **Maturity 94.1% / Implementation 85.9%** (HEAD `89d8439`, pin v1.93.0); the §21 a11y axe scans were broadened 10→17 pages (impl 7→8 pending a green nightly), and the recorded screen-reader pass remains the §21 maturity lever. **Reconciled 2026-07-02 (re-score, pin v1.99.0):** three honest recalibrations, no code regressions. **§18 UI Architecture was REOPENED** (scorecard §18 maturity 4→3: no automated §18 UI-architecture fitness gate exists, so the container/presentational + code-behind conventions are review-enforced only, making §18 Consistent/M3 not Optimized/M4; its prior maturity-4 "UI convention test" basis was actually the route-authorization tests, a §25 gate). **§6 impl was corrected 10→9** (the idempotent inbox covers only 2 of 4 consumer services: Conference `appsettings.json:32`, Identity `:29`; Engagement/Notification carry none, so real levers remain and 10 was overstated). **§27 impl was corrected 8→7** (residual hard-coded English is broader than exception-path only, plus no text-expansion test). Scorecard now **Maturity 93.1% (298/320) / Implementation 85.8% (686/800)** (pin v1.99.0); the "Scorecard now Maturity 94.1% / Implementation 85.9%" figure above is the frozen v1.93.0 provenance. **Reconciled 2026-07-03 (sixteenth-cycle full re-score, pin v1.101.0, HEAD `ac43c8d8`, all 34 categories CONFIRMED):** the 2026-07-02 e2e-gate promotion is now reflected in this ledger: **#28 is CLOSED** (scorecard §28 maturity 4: the chromium E2E/axe suite is an enforced deploy gate, `deploy.yml:303-308` `e2e-gate` job + `:343` in `deploy.needs`; **TD-06 and TD-07 ticked**), **#21 re-ranked priority 6→3** (scorecard §21 M3/I8 via the same gate; the recorded SR pass remains the cheapest maturity lever), and **#19 is REOPENED** (scorecard §19 M3/I9: review-enforced conventions, no §19 fitness gate in `Tests/Architecture/`). One implementation recalibration: **§24 impl 9→7** (per-form error summary only on the Profile form; the six create forms surface a generic validation snackbar; raw `{ex.Message}` in Profile snackbars), tracked as new **TD-14** under #24 (the category header stays closed: maturity holds 4 on `FormsConventionTests`). Scorecard now **Maturity 94.1% (301/320) / Implementation 85.6% (685/800)** (pin v1.101.0); the 93.1%/85.8% figures in this note are the frozen v1.99.0 provenance. **Reconciled 2026-07-03 (same-day i18n completion sweep, ADR-027 Decision 9):** **#27's impl lever CLOSED** (scorecard §27 impl 7→8: zero residual literals, dual CI gates incl. the new `LocalizedTextConventionTests`, MudBlazor chrome + nav localized; a new impl 8→9 sub-item tracks extending the pseudo-loc text-expansion evidence to ADC pages), and **TD-14 NARROWED** (raw `{ex.Message}` snackbars eliminated; the Profile-form gate exclusion + per-form error summaries remain). Scorecard now **Maturity 94.1% (301/320) / Implementation 85.8% (686/800)**. **Reconciled 2026-07-06 (eighteenth-cycle full re-score, pin v1.106.0, HEAD `8fc9e0d2`, all 34 categories CONFIRMED):** every category re-confirmed at its prior score from evidence read this run (no moves). **#8's TD-03 CLOSED:** the optimistic-concurrency API round-trip is implemented and deploy-gated (`EventDTO.cs:16` carries the RowVersion token via `IConcurrencyAware`, `UpdateEventHandler.cs:34` stamps it with `SetOriginalRowVersion`, `OrganizerConcurrencyTests.cs:26` asserts a stale token returns 409 inside the deploy-gating `MMCA.ADC.Integration.slnf`); scorecard §8 holds impl 9 because the round-trip is Conference-only. **#6/TD-02 partially addressed:** the genuine broker round-trip test landed as the non-gating nightly `MMCA.ADC.CrossService.IntegrationTests` (9 tests, Testcontainers RabbitMQ+SQL), so scorecard §6 holds impl 9; the 9→10 lever is now gating it plus enabling the inbox on all 4 consumer services. Evidence counts refreshed: arch-tests **23 classes / 25 files / 74 methods** (all thin subclasses, 0 ADC-local), §14 unit **1507/223** plus integration **303 gating methods / four tiers + 9 non-gating CrossService**, coverage floor **38→55.5%** (actual ~57%), ADR set **001-038**, §27 resx **40 base + 40 es**. Scorecard indices hold **Maturity 94.1% (301/320) / Implementation 85.8% (686/800)**. **Reconciled 2026-07-10 (nineteenth-cycle full re-score, pin v1.110.0, HEAD `246a24dc`, all 34 categories held):** every category re-confirmed at its prior score from evidence read this run (no moves, no closures, no re-ranks; the below-4 set stays §12/§13/§18/§19/§21/§22/§23/§33 with priorities recomputed byte-identical, and every TD status is unchanged: done TD-01/03/04/05/09/10, open TD-02/06/07/08/13/14). Three first-pass move proposals were adversarially rejected as verified non-moves: **§12 impl 8→9** (the Notification app stays pinned `maxReplicas: 1`, `infra/main.bicep:1113`, even though the v1.110.0 wave provisioned Azure Managed Redis Balanced B0 and scaled the REST services to `maxReplicas: 2`), **§23 maturity 3→4** (the WebVitals budgets are advisory by design and no §23 fitness gate exists), and **§34 impl 9→8** (no governance regression; the untracked workspace-root `ArchitecturalAnalysis.md` remains the already-weighed 9-not-10 lever). Evidence refresh: ADR set **001-041**, pin **v1.110.0**, arch tests re-run green this cycle (74/74); a contradictory `main.bicep` Notification scale-pin comment (claiming no Redis backplane while the backplane key is injected at `:1056`) was corrected in place. Scorecard indices hold **Maturity 94.1% (301/320) / Implementation 85.8% (686/800)**. **Reconciled 2026-07-15 (twentieth-cycle full re-score, pin v1.116.0, HEAD `913d088a`, five scores up):** the remediation-wave candidacies recorded below were adjudicated. **Accepted:** **#18 CLOSED** (scorecard §18 maturity 3→4: `UIArchitectureConventionTests` in the CI.slnf arch gate), **#19 CLOSED** (scorecard §19 maturity 3→4: `StateManagementConventionTests` in the same gate, impl held at 9 after a first-pass 9→8 proposal was adversarially rejected as unsupported), **#23 CLOSED for maturity** (scorecard §23 maturity 3→4: the CWV budgets became enforced assertions inside the deploy-gating chromium `e2e-gate` on 2026-07-11, superseding the nineteenth-cycle advisory-by-design rejection; the WASM code-split/image sub-item stays open as impl polish), **#13's impl half** (scorecard §13 impl 8→9 on the SLO workbook + `infra/OPERATIONS.md` day-2 runbooks), and **TD-14 confirmed** (scorecard §24 impl 7→8). **Rejected, headers corrected below:** the **#13** maturity 3→4 candidacy (runbooks/dashboards are review-enforced conventions and IaC, not CI-gated fitness functions, so §13 holds M3/I9 and REOPENS), the **#22** maturity 3→4 candidacy (the firefox/webkit legs added to the e2e-gate run `continue-on-error: true` per `e2e.yml:74`, i.e. advisory inside the gate, so §22 holds M3/I8 and REOPENS), and the **#33** impl 8→9 candidacy (broker parity local-RabbitMQ vs prod-Service-Bus is mitigated, not closed, per `README.md:74`, a live rubric red flag, so §33 holds M3/I8 and REOPENS). Also corrected in the scorecard: §28's false "E2E #5 un-skipped" claim (the test is re-quarantined at `SpeakerSelfServiceTests.cs:57`; score held M4/I8) and the §34 impl 9→8 downgrade re-rejected. Scorecard indices move to **Maturity 96.6% (309/320) / Implementation 86.3% (690/800)**; the below-4 set narrows to §12/§13/§21/§22/§33. **Reconciled 2026-07-17 (twenty-first-cycle full re-score, pin v1.117.0, HEAD `c4c01aa5`, two scores up):** the two 2026-07-16 gate candidacies were adjudicated ACCEPTED. **#13 CLOSED** (scorecard §13 maturity 3→4: `ObservabilityConventionTests` machine-enforces the alert-to-runbook pairing in the CI.slnf arch gate, `MMCA.ADC.CI.slnf:56` + `deploy.yml:57,417`; impl holds 9) and **#22 CLOSED** (scorecard §22 maturity 3→4: the deploy-gating `e2e-gate` passes all three engines, `deploy.yml:309`, and `e2e.yml:78` scopes `continue-on-error` to scheduled nightly non-chromium legs, so every invoked engine can fail a deploy; impl holds 8). **Rejected:** the **#27** impl 8→9 pseudo-loc candidacy (`PseudoLocalizationTests.cs:51` covers 3 public pages of 30+, a partial extension; §27 holds M4/I8 as a verified non-move). **Corrected:** a stale nineteenth-cycle draft accidentally committed via PR #15 (2026-07-17) had relabeled the #12 header "RESOLVED M4/I8" and added a mislabeled "2026-07-12 twentieth-cycle" update paragraph; both are reverted below, and **§12 stays M3/I8 open** per the twentieth-cycle adjudication (re-confirmed this run: the k6 tier is freshness-gated via `load-freshness`, `deploy.yml:348,417`, but executes monthly/dispatch out of band, and Notification stays pinned `maxReplicas: 1`). **#33 re-confirmed M3/I8** (the 2026-07-16 Service Bus emulator tier candidacy stands recorded for a future cycle; the tier is nightly, riding the freshness gate rather than in-band). Scorecard indices move to **Maturity 97.8% (313/320) / Implementation 86.3% (690/800)**; the below-4 set narrows to **§12/§21/§33**. **Reconciled 2026-07-21 (twenty-second-cycle full re-score, pin v1.121.0, HEAD `8509a05d`, two scores down, neither a quality regression):** **#22 REOPENED** (scorecard §22 maturity 4→3: the 2026-07-18 Actions-minute reduction cut the deploy `e2e-gate` to `browsers: '["chromium"]'`, `deploy.yml:488` with its rationale comment at `:478-480`, so firefox/webkit run only on the weeknight nightly `schedule` where `e2e.yml:119` keeps them `continue-on-error`; cross-engine verification is nightly-advisory again, which is M3, and the trade-off is recorded in Deliberate / accepted with its scoring cost stated plainly). **§18 implementation 9→8** (the category header stays closed, maturity holds 4 on the `UIArchitectureConventionTests` gate, but the largest code-behind sits flush at the enforced 400-line cap with zero headroom, `HappeningNow.razor.cs:400` vs `UIArchitectureConventionTestsBase.cs:22`, plus six files in the 360-379 band; tracked as new **TD-16** under #18, effort S). **Rejected for a second consecutive cycle:** the **#27** impl 8→9 pseudo-loc candidacy (`PseudoLocalizationTests.cs:51` still covers exactly 3 public pages of 36 routable pages, unchanged since the twenty-first-cycle rejection; §27 holds M4/I8). **Sub-item closed:** #12's deferred prod-Redis provisioning (Redis Enterprise is provisioned, `infra/main.bicep:740,753,771`), though the SignalR fan-out stays unexercised behind the `maxReplicas: 1` pin (`:1424`), so #12 itself stays open. **Corrected:** #33's load-bearing `README.md:74` quote no longer exists (the file now states the opposite at `README.md:80-84`, the Service Bus emulator tier having landed), and drifted anchors were refreshed repo-wide (`CI.slnf:56`→`:58`, `deploy.yml:303-309/343/348/417`→`:483-489/:553/:783`, `e2e.yml:78`→`:119`, `main.bicep:1113`→`:1424`, `:341`→`:488`). Scorecard indices move to **Maturity 97.2% (311/320) / Implementation 85.9% (687/800)**; the below-4 set widens to **§12/§21/§22/§33**. **Reconciled 2026-07-23 (twenty-third-cycle full re-score, pin v1.123.0, HEAD `160f59f5`, no moves):** every category re-confirmed at its prior score from evidence read this run (no closures, no new items, no re-ranks, no TD changes; the below-4 set stays §12/§21/§22/§33 with priorities unchanged: #21 at 3, #12/#22/#33 at 2). Two first-pass maturity-lift proposals were adversarially rejected as verified non-moves: **§12 M3→4 rejected** (the k6 tier still runs monthly/dispatch out of band, `load-test.yml:8`, with `load-freshness` a recency-only deploy check, `deploy.yml:553`, and Notification pinned `maxReplicas: 1`, `infra/main.bicep:1424`) and **§21 M3→4 rejected** (the recorded manual screen-reader pass is still the empty placeholder in `ACCESSIBILITY-SCREENREADER-PASS.md`, remaining the cheapest maturity lever). The v1.122.0/v1.123.0 lockstep sweeps moved no score. Scorecard indices hold **Maturity 97.2% (311/320) / Implementation 85.9% (687/800)**, ADR set **001-051**. **Verification pass 2026-07-23 (post-cycle, no score claims):** stale claims corrected in place across this ledger and the scorecard: the #6 header's "2 of 4 services" inbox basis (all four services carry `EnableInbox=true` since the TD-02 close), #26's "pending manual Aspire verification + release" phrasing (shipped and deployed), the #29/#31 "activates on the next push" phrasing (gates live in `deploy.needs` since 2026-06-30), the scorecard's §8/§28 "re-quarantined" claim (E2E #5 was un-quarantined 2026-07-19, plain `[Fact]` at `SpeakerSelfServiceTests.cs:58`), lock-file count 58→65, resx pairs 40→53, `MMCA.Common.*` pin 1.117.0→1.123.0 in the §16/§32 rows, and drifted anchors (`deploy.needs` `:783`→`:791`, `load-freshness` `:548`→`:553`, coverage floor `:83`→`:210-212`, `sloWorkbook` `:278`→`:425`, Notification pin `:1113`→`:1424`, `CI.slnf:56`→`:58`, `OrganizerConcurrencyTests.cs:26`→`:27`). The genuinely-open TD set today is **TD-08, TD-15, TD-16** (older per-cycle "open TD-..." snapshots above are frozen provenance). The screen-reader-pass runbook lives centralized as `adc-ACCESSIBILITY-SCREENREADER-PASS.md` in Website `docs-src/guides/` (2026-07-20 centralization); bare-name references below predate that move. **Reconciled 2026-07-28 (twenty-fourth-cycle full re-score, pin v1.131.0, HEAD `2ec77796`, one score down):** **§15 Best Practices & Code Quality implementation 8→7** (weight 2), the only score move and the only rank change on either axis; maturity holds 4, independently re-derived, so #15 stays in the protect set while taking the **top row of the implementation band at implPriority 4**. The basis is hygiene drift, not a code-quality regression: an audit suppression expired by its own written removal condition (`Directory.Build.props:49-51` vs its comment at `:41-48`), three undated global `NoWarn` codes (`:22`), and the MAUI `MMCA.ADC.UI` project sitting outside every CI build and outside the CI-audited dependency graph (`MMCA.ADC.CI.slnf:25`, `deploy.yml:288`), which is precisely the graph the `:8-12` suppressions exist for. Band totals move to **15 categories / 35 gap points** (count unchanged, §15 was already in the band) and **95.1% of the 90% attainable ceiling**. **No closures:** closure needs maturity 4 AND implementation >= 9 independently, and all four maturity-band items are still M3 and all four still I8, with none of the 15 implementation-band categories reaching 9, so nothing moves to the protect list and the maturity band is byte-identical (#21 at 3, #12/#22/#33 at 2, 4 categories / 9 points). Re-verified still-open levers: #21's screen-reader results log is still the empty `_yyyy-mm-dd_` placeholder (`adc-ACCESSIBILITY-SCREENREADER-PASS.md:60-62`), #22 is still chromium-only gating (`deploy.yml:505`) with firefox/webkit advisory on the Mon/Thu schedule (`e2e.yml:131`, cron `:43`), #12 is still scale-pinned (`infra/main.bicep:1447`) with a monthly out-of-band capacity proof (`load-test.yml:18`). **Adjudicated DEFERRED, not open:** the **#27** impl 8→9 pseudo-loc candidacy, rejected for a third time (21st, 22nd, 24th) on byte-identical evidence, is now recorded in Deliberate / accepted with its cost and explicit re-open triggers rather than carried as a live candidacy to re-reject a fourth time. Also re-rejected and recorded so they are not re-proposed: #24 impl 8→9 and #33 M3→4 / I8→9; #6 and #30 sit at I9, already at the scheduling target, so their recorded "9→10" candidacies are out of scope for both bands. **New: TD-17** under #33 (the Service Bus emulator parity tier is now dispatch-only after hanging to its 8-minute timeout on 7 of 7 runs, so #33's header basis is corrected from "nightly plus recency gate" to "no schedule, no gate") and **TD-18** under #15 (the MAUI CI-enforcement gap, recorded rather than fixed because a MAUI CI build cuts against the 2026-07-18 Actions-minute reduction). **TD-16 refreshed, worse:** `HappeningNow.razor.cs` is now exactly 400 lines against the enforced cap, and the recorded 360-379 band was stale (`SpeakerDetail.razor.cs` is 386, not 365); current measured set 400/386/379/376/367/365/362 with two recorded paths corrected. **TD-15 was NOT re-verified this run** and is left exactly as written; its figures are not restated as re-confirmed. **Evidence refresh (no score move):** the arch-test suite is now **29 test classes / 31 `.cs` files / 91 executed methods, re-run green 2026-07-28 (91/91)**, up from 26/28/82, and **90 of the 91 are inherited** from the shared rule library after the §13 alert-runbook pairing gate was lifted upstream (`ObservabilityConventionTests.cs:7` is now a bare thin subclass), leaving the TD-14 Profile-form guard (`FormsConventionTests.cs:31`) as the single ADC-local method; ADR set **001-060**. Anchors refreshed repo-wide: `deploy.yml` e2e-gate `:488`→job at `:500` with `browsers: '["chromium"]'` at `:505` and rationale `:478-480`→`:493-499`, `deploy.needs` `:791`→`:829`, the freshness jobs re-split (`cost-guard :488`, `dr-freshness :513`, `load-freshness :570`, `cross-service-freshness :627`) with their skip checks at `:526/:583/:642`, `e2e.yml:119`→`:131`, `infra/main.bicep:1424`→`:1447`, `load-test.yml:8`→`:18`, `cross-service-tests.yml` emulator job at `:142` with its dispatch-only condition at `:144`. The genuinely-open TD set today is **TD-08, TD-15, TD-16, TD-17, TD-18**. **Reconciled 2026-08-01 (twenty-fifth-cycle full re-score, pin v1.135.0, HEAD `995a7886`, no moves):** every category re-confirmed at its prior score from evidence read this run, so there are **no closures, no new items and no re-ranks**: both bands are byte-identical (maturity 4 categories / 9 points, #21 at 3 and #12/#22/#33 at 2; implementation 15 categories / 35 points). Closure needs maturity 4 AND implementation >= 9 independently, and all four maturity-band items are still M3/I8 while none of the 15 implementation-band categories reached 9. All six adversarial adjudications this cycle were proposed implementation lifts and **all six were rejected**: **§5 8→9** (DTOs live in the Shared assembly with horizontal mapper/validation/specification folders, the layered-by-project hybrid is unchanged, and `AdcArchitectureMap.cs:12-44` omits `MMCA.ADC.Notification.Application` from the enforced set), **§13 9→10** (three ENABLED production alerts have no runbook triage section and sit outside the pairing gate's scope, including the sev-1 gateway-availability alert at `infra/main.bicep:481`), **§24 8→9** (the named bUnit lever shipped, but client validation does not mirror the server's cross-field and format rules and the error summary covers 7 of 15 MudForm forms: both are now recorded as §24's levers, replacing "not yet identified"), **§27 8→9** (fourth rejection, byte-identical evidence plus one new culture-formatting violation), **§31 8→9** (the surge/revert automation is not pulled), and **§33 8→9** (second rejection: see the rewritten TD-17 below). **TD-17 is HALF CLOSED and its blocker text was invalid:** the `servicebus-emulator-smoke` job is **back on the weekday nightly** since 2026-07-29 (`cross-service-tests.yml:144-146` `needs: should-run` + `if: needs.should-run.outputs.run == 'true'` under `cron: '0 6 * * 1-5'` at `:26,:30`, `timeout-minutes: 10` at `:148`), and the recorded root cause was wrong: the comment at `:130-143` records per-test bus re-provisioning against an admin plane throttled at roughly 1 op/sec (IAsyncLifetime plus xUnit per-Fact class instantiation), fixed by hoisting the bus to the collection fixture and wall-clock bounding both startup phases, **not** the companion SQL image. The remaining half is open: the tier is `continue-on-error: true` (`:149`) and gates nothing, since `cross-service-freshness` keys off the `cross-service` job (`:124-128`, gate at `deploy.yml:663`). **TD-16 re-measured, and the headline is no longer true:** `HappeningNow.razor.cs` is **394**, not 400, and the high-water mark moved to `SessionSelectionDashboard.razor.cs` at **395**, so the "flush at the cap, zero headroom" framing is retired in favour of 5 lines of headroom; current measured set 395/394/386/376/367/365/362 at HEAD `995a7886`, seven files within 38 lines of the 400 cap, still effort S. **TD-08 and TD-18 re-confirmed open** (TD-18's gating-scan anchor drifted `deploy.yml:288`→`:319`). **TD-15 was NOT re-verified for a second consecutive cycle** and is left exactly as written; its cost figures are not restated as re-confirmed. **Anchors refreshed repo-wide:** `deploy.yml` e2e-gate `:500`→`:531` with `browsers` `:505`→`:541`, `deploy.needs` `:829`→`:866`, the freshness jobs re-split again (`cost-guard :519`, `dr-freshness :549`, `load-freshness :606`, `cross-service-freshness :663`) with their skip checks at `:562/:619/:678`, coverage floor `:210-212`→`:254`, the gating vuln scan `:288`→`:319`, `--locked-mode` restores at `:199`/`:299`, `e2e.yml:131`→`:144` with the nightly matrix replaced by **alternating** single-engine crons at `:49,:50`, `infra/main.bicep:1447`→`:1530` (and the SLO/budget/SQL anchors re-derived), `Directory.Build.props` suppression `:49-51`→`:54` and NoWarn `:22`→`:26`, ADC pin `:123`→`:139` at **1.135.0**, lock files **65→66**, ADR set **001-064**. **Reconciled 2026-08-14 (twenty-sixth-cycle full re-score, pin v1.152.0, HEAD `19021d93`, no moves):** every category was re-confirmed at its prior score from evidence read this run, so again there are **no closures, no new items and no re-ranks**: both bands are byte-identical (maturity 4 categories / 9 points, §21 at 3 and §12/§22/§33 at 2; implementation 15 categories / 35 points). All eight adversarial adjudications were proposed lifts and **all eight were rejected**: **§5 8→9** (DTOs and their mappers still outside the slice, and `AdcArchitectureMap.cs:12-43` still has no `Module("Notification", ...)` entry, now named as **TD-19**), **§7 8→9** (the bidirectional sync-gRPC red flag broadened to a second pair, Identity-Notification), **§12 M3→4** (zero commits touched `load-test.yml`, `deploy.yml` or `Tests/Load/` since the prior HEAD), **§13 9→10** (three of the six ENABLED production alerts still have no runbook, including the sev-1 gateway-availability alert, whose anchor moves `infra/main.bicep:481`→`:496-502`, `severity: 1` at `:502`; §13 sits at I9, outside both bands), **§15 7→8** (all three downgrade grounds intact, and the expired suppression is further past its removal condition now that the pin is v1.152.0), **§23 8→9** (WASM code-split and image optimization both still open), **§28 8→9** (the new state-management bUnit coverage is a within-band improvement) and **§31 8→9** (the surge/revert automation is still not pulled, `cost-guard.yml:4,:12,:17,:59,:83`). **New: TD-19** under §5 (the Notification module is absent from the enforced architecture map, effort S), which replaces §5's "lever not yet identified" band row. **TD-16 re-measured, and the headroom narrowed:** the high-water code-behind rose 395→**398** of the 400 cap, leaving 2 lines rather than 5. **TD-17 unchanged in substance, anchors corrected:** job `:145`, `needs` `:146`, `if` `:147`, `timeout-minutes` `:149`, `continue-on-error` `:150`, schedule `workflow_dispatch` `:26` + cron `'0 6 * * 1-5'` `:31` (the recorded `:26,:30` was wrong), gate-keying comment `:126-129`. **TD-15 was NOT re-verified for a third consecutive cycle** and is left exactly as written. **Evidence refresh (no score move):** axe coverage is **31 test methods over roughly 29 distinct pages** (`AccessibilityTests.cs:21-365`), not 17 pages; the routable-page denominator is **49** `@page` files under `Source` (48 excluding the MAUI-only `DeviceSettings.razor`), not 37, so §27's deferred lift now costs roughly 45 pages; the Notification scale pin moves `infra/main.bicep:1530`→`:1616`; §24's error-summary ratio is **8 of 18 MudForm-bearing pages** (19 forms), not 7 of 15; TD-18's MAUI `NoWarn CA5392` anchor moves `MMCA.ADC.UI.csproj:131`→`:143`. Indices hold **Maturity 97.2% (311/320) / Implementation 85.6% (685/800)**, ADR set **001-078**. +> **⚠️ Index note (2026-06-27).** The 75% / 241-320 figure is the **2026-06-08 single-axis baseline** and is **not recomputed** as items below are ticked: many already-`RESOLVED` rows (#11, #14, #26, #29, #30, #32, …) have moved the real total well past it. For the **current, authoritative** scores use the **canonical, in-repo** [`ArchitectureScorecard.md`](../governance/adc-ArchitectureScorecard.md) (two-axis, at framework **v1.160.0**: **Maturity 97.2% (311/320) / Implementation 85.0% (680/800)**). This backlog remains the living *what-to-do-next* checklist; trust the scorecard for scores. **Closed 2026-06-26/27:** #32 (TD-01 lock files + blocking supply-chain gates), the #14 coverage floor (TD-05), #26 (Gateway header-regression test), the #29 graceful-shutdown test (scorecard §29 impl 8→9), and **#5** (slice-cohesion fitness function, scorecard §5 impl 7→8, on the v1.85.0 sweep). **Closed on the v1.86.0 i18n + dark-mode sweep (2026-06-27):** the **#29 scheduled DR-drill gate** (scorecard §29 maturity 3→4), **#24** change-password client validation (scorecard §24 impl 8→9), the **#20** landing-page brand-token dedupe (scorecard §20 impl 8→9), and **#27** i18n flips from N/A to scored (M3/I8, ADR-027 supersedes 011). **Activated 2026-06-28:** managed-identity SQL DB auth in production (`useManagedIdentitySql=true`, scorecard §17 impl 8→9; #11 holds at 9, now capped only by the deferred public-network-access epic). The last big open lever is the **E2E/axe merge gate** (TD-06/07). **Reconciled 2026-06-29 (re-score, pin v1.92.0):** §29 was **REOPENED** (scorecard §29 corrected maturity 4→3: the `dr-drill.yml` cron is scheduled but gates nothing, so it is Consistent/M3 not an automatic CI gate, the same standard §28 is held to), and the prior "#29 DR-drill gate closed maturity 3→4" claim above is withdrawn; **#16** was also reopened (scorecard §16 is maturity 3; deleting the orphan-test folder did not by itself reach 4); **§32** moved impl 8→9 (CI restore already runs `--locked-mode` in both gating jobs, `deploy.yml:40`/`:119`); and the backlog was caught up to the scorecard by closing #6/#8/#17/#18/#20/#26/#30 (all already at maturity 4). **Reconciled 2026-06-30 (enforcement-gate wave):** #16/#24/#27/#29/#31 lifted maturity 3→4 by adding CI-enforced governance over already-strong implementation: #24 `FormsConventionTests`, #27 `TranslationCompletenessTests`, and #16 `FrameworkVersionConsistencyTests` run in the CI.slnf arch gate (locally verified green, 74/74 arch tests pass); #31 cost-guard and #29 dr-freshness are wired into `deploy.needs` (committed, activate on the next push). **Reconciled 2026-06-30 (v1.92.0→v1.93.0 sweep, the Common tenth-wave):** §5 Vertical Slice Architecture lifted maturity 3→4 (the slice-cohesion fitness function `SliceCohesionTests` is confirmed a CI merge gate in `MMCA.ADC.CI.slnf`), and §7 was adversarially FLAG-re-checked (a proposed impl 8→9 lift rejected) and confirmed unchanged at M4/I8. Scorecard now **Maturity 94.1% / Implementation 85.9%** (HEAD `89d8439`, pin v1.93.0); the §21 a11y axe scans were broadened 10→17 pages (impl 7→8 pending a green nightly), and the recorded screen-reader pass remains the §21 maturity lever. **Reconciled 2026-07-02 (re-score, pin v1.99.0):** three honest recalibrations, no code regressions. **§18 UI Architecture was REOPENED** (scorecard §18 maturity 4→3: no automated §18 UI-architecture fitness gate exists, so the container/presentational + code-behind conventions are review-enforced only, making §18 Consistent/M3 not Optimized/M4; its prior maturity-4 "UI convention test" basis was actually the route-authorization tests, a §25 gate). **§6 impl was corrected 10→9** (the idempotent inbox covers only 2 of 4 consumer services: Conference `appsettings.json:32`, Identity `:29`; Engagement/Notification carry none, so real levers remain and 10 was overstated). **§27 impl was corrected 8→7** (residual hard-coded English is broader than exception-path only, plus no text-expansion test). Scorecard now **Maturity 93.1% (298/320) / Implementation 85.8% (686/800)** (pin v1.99.0); the "Scorecard now Maturity 94.1% / Implementation 85.9%" figure above is the frozen v1.93.0 provenance. **Reconciled 2026-07-03 (sixteenth-cycle full re-score, pin v1.101.0, HEAD `ac43c8d8`, all 34 categories CONFIRMED):** the 2026-07-02 e2e-gate promotion is now reflected in this ledger: **#28 is CLOSED** (scorecard §28 maturity 4: the chromium E2E/axe suite is an enforced deploy gate, `deploy.yml:303-308` `e2e-gate` job + `:343` in `deploy.needs`; **TD-06 and TD-07 ticked**), **#21 re-ranked priority 6→3** (scorecard §21 M3/I8 via the same gate; the recorded SR pass remains the cheapest maturity lever), and **#19 is REOPENED** (scorecard §19 M3/I9: review-enforced conventions, no §19 fitness gate in `Tests/Architecture/`). One implementation recalibration: **§24 impl 9→7** (per-form error summary only on the Profile form; the six create forms surface a generic validation snackbar; raw `{ex.Message}` in Profile snackbars), tracked as new **TD-14** under #24 (the category header stays closed: maturity holds 4 on `FormsConventionTests`). Scorecard now **Maturity 94.1% (301/320) / Implementation 85.6% (685/800)** (pin v1.101.0); the 93.1%/85.8% figures in this note are the frozen v1.99.0 provenance. **Reconciled 2026-07-03 (same-day i18n completion sweep, ADR-027 Decision 9):** **#27's impl lever CLOSED** (scorecard §27 impl 7→8: zero residual literals, dual CI gates incl. the new `LocalizedTextConventionTests`, MudBlazor chrome + nav localized; a new impl 8→9 sub-item tracks extending the pseudo-loc text-expansion evidence to ADC pages), and **TD-14 NARROWED** (raw `{ex.Message}` snackbars eliminated; the Profile-form gate exclusion + per-form error summaries remain). Scorecard now **Maturity 94.1% (301/320) / Implementation 85.8% (686/800)**. **Reconciled 2026-07-06 (eighteenth-cycle full re-score, pin v1.106.0, HEAD `8fc9e0d2`, all 34 categories CONFIRMED):** every category re-confirmed at its prior score from evidence read this run (no moves). **#8's TD-03 CLOSED:** the optimistic-concurrency API round-trip is implemented and deploy-gated (`EventDTO.cs:16` carries the RowVersion token via `IConcurrencyAware`, `UpdateEventHandler.cs:34` stamps it with `SetOriginalRowVersion`, `OrganizerConcurrencyTests.cs:26` asserts a stale token returns 409 inside the deploy-gating `MMCA.ADC.Integration.slnf`); scorecard §8 holds impl 9 because the round-trip is Conference-only. **#6/TD-02 partially addressed:** the genuine broker round-trip test landed as the non-gating nightly `MMCA.ADC.CrossService.IntegrationTests` (9 tests, Testcontainers RabbitMQ+SQL), so scorecard §6 holds impl 9; the 9→10 lever is now gating it plus enabling the inbox on all 4 consumer services. Evidence counts refreshed: arch-tests **23 classes / 25 files / 74 methods** (all thin subclasses, 0 ADC-local), §14 unit **1507/223** plus integration **303 gating methods / four tiers + 9 non-gating CrossService**, coverage floor **38→55.5%** (actual ~57%), ADR set **001-038**, §27 resx **40 base + 40 es**. Scorecard indices hold **Maturity 94.1% (301/320) / Implementation 85.8% (686/800)**. **Reconciled 2026-07-10 (nineteenth-cycle full re-score, pin v1.110.0, HEAD `246a24dc`, all 34 categories held):** every category re-confirmed at its prior score from evidence read this run (no moves, no closures, no re-ranks; the below-4 set stays §12/§13/§18/§19/§21/§22/§23/§33 with priorities recomputed byte-identical, and every TD status is unchanged: done TD-01/03/04/05/09/10, open TD-02/06/07/08/13/14). Three first-pass move proposals were adversarially rejected as verified non-moves: **§12 impl 8→9** (the Notification app stays pinned `maxReplicas: 1`, `infra/main.bicep:1113`, even though the v1.110.0 wave provisioned Azure Managed Redis Balanced B0 and scaled the REST services to `maxReplicas: 2`), **§23 maturity 3→4** (the WebVitals budgets are advisory by design and no §23 fitness gate exists), and **§34 impl 9→8** (no governance regression; the untracked workspace-root `ArchitecturalAnalysis.md` remains the already-weighed 9-not-10 lever). Evidence refresh: ADR set **001-041**, pin **v1.110.0**, arch tests re-run green this cycle (74/74); a contradictory `main.bicep` Notification scale-pin comment (claiming no Redis backplane while the backplane key is injected at `:1056`) was corrected in place. Scorecard indices hold **Maturity 94.1% (301/320) / Implementation 85.8% (686/800)**. **Reconciled 2026-07-15 (twentieth-cycle full re-score, pin v1.116.0, HEAD `913d088a`, five scores up):** the remediation-wave candidacies recorded below were adjudicated. **Accepted:** **#18 CLOSED** (scorecard §18 maturity 3→4: `UIArchitectureConventionTests` in the CI.slnf arch gate), **#19 CLOSED** (scorecard §19 maturity 3→4: `StateManagementConventionTests` in the same gate, impl held at 9 after a first-pass 9→8 proposal was adversarially rejected as unsupported), **#23 CLOSED for maturity** (scorecard §23 maturity 3→4: the CWV budgets became enforced assertions inside the deploy-gating chromium `e2e-gate` on 2026-07-11, superseding the nineteenth-cycle advisory-by-design rejection; the WASM code-split/image sub-item stays open as impl polish), **#13's impl half** (scorecard §13 impl 8→9 on the SLO workbook + `infra/OPERATIONS.md` day-2 runbooks), and **TD-14 confirmed** (scorecard §24 impl 7→8). **Rejected, headers corrected below:** the **#13** maturity 3→4 candidacy (runbooks/dashboards are review-enforced conventions and IaC, not CI-gated fitness functions, so §13 holds M3/I9 and REOPENS), the **#22** maturity 3→4 candidacy (the firefox/webkit legs added to the e2e-gate run `continue-on-error: true` per `e2e.yml:74`, i.e. advisory inside the gate, so §22 holds M3/I8 and REOPENS), and the **#33** impl 8→9 candidacy (broker parity local-RabbitMQ vs prod-Service-Bus is mitigated, not closed, per `README.md:74`, a live rubric red flag, so §33 holds M3/I8 and REOPENS). Also corrected in the scorecard: §28's false "E2E #5 un-skipped" claim (the test is re-quarantined at `SpeakerSelfServiceTests.cs:57`; score held M4/I8) and the §34 impl 9→8 downgrade re-rejected. Scorecard indices move to **Maturity 96.6% (309/320) / Implementation 86.3% (690/800)**; the below-4 set narrows to §12/§13/§21/§22/§33. **Reconciled 2026-07-17 (twenty-first-cycle full re-score, pin v1.117.0, HEAD `c4c01aa5`, two scores up):** the two 2026-07-16 gate candidacies were adjudicated ACCEPTED. **#13 CLOSED** (scorecard §13 maturity 3→4: `ObservabilityConventionTests` machine-enforces the alert-to-runbook pairing in the CI.slnf arch gate, `MMCA.ADC.CI.slnf:56` + `deploy.yml:57,417`; impl holds 9) and **#22 CLOSED** (scorecard §22 maturity 3→4: the deploy-gating `e2e-gate` passes all three engines, `deploy.yml:309`, and `e2e.yml:78` scopes `continue-on-error` to scheduled nightly non-chromium legs, so every invoked engine can fail a deploy; impl holds 8). **Rejected:** the **#27** impl 8→9 pseudo-loc candidacy (`PseudoLocalizationTests.cs:51` covers 3 public pages of 30+, a partial extension; §27 holds M4/I8 as a verified non-move). **Corrected:** a stale nineteenth-cycle draft accidentally committed via PR #15 (2026-07-17) had relabeled the #12 header "RESOLVED M4/I8" and added a mislabeled "2026-07-12 twentieth-cycle" update paragraph; both are reverted below, and **§12 stays M3/I8 open** per the twentieth-cycle adjudication (re-confirmed this run: the k6 tier is freshness-gated via `load-freshness`, `deploy.yml:348,417`, but executes monthly/dispatch out of band, and Notification stays pinned `maxReplicas: 1`). **#33 re-confirmed M3/I8** (the 2026-07-16 Service Bus emulator tier candidacy stands recorded for a future cycle; the tier is nightly, riding the freshness gate rather than in-band). Scorecard indices move to **Maturity 97.8% (313/320) / Implementation 86.3% (690/800)**; the below-4 set narrows to **§12/§21/§33**. **Reconciled 2026-07-21 (twenty-second-cycle full re-score, pin v1.121.0, HEAD `8509a05d`, two scores down, neither a quality regression):** **#22 REOPENED** (scorecard §22 maturity 4→3: the 2026-07-18 Actions-minute reduction cut the deploy `e2e-gate` to `browsers: '["chromium"]'`, `deploy.yml:488` with its rationale comment at `:478-480`, so firefox/webkit run only on the weeknight nightly `schedule` where `e2e.yml:119` keeps them `continue-on-error`; cross-engine verification is nightly-advisory again, which is M3, and the trade-off is recorded in Deliberate / accepted with its scoring cost stated plainly). **§18 implementation 9→8** (the category header stays closed, maturity holds 4 on the `UIArchitectureConventionTests` gate, but the largest code-behind sits flush at the enforced 400-line cap with zero headroom, `HappeningNow.razor.cs:400` vs `UIArchitectureConventionTestsBase.cs:22`, plus six files in the 360-379 band; tracked as new **TD-16** under #18, effort S). **Rejected for a second consecutive cycle:** the **#27** impl 8→9 pseudo-loc candidacy (`PseudoLocalizationTests.cs:51` still covers exactly 3 public pages of 36 routable pages, unchanged since the twenty-first-cycle rejection; §27 holds M4/I8). **Sub-item closed:** #12's deferred prod-Redis provisioning (Redis Enterprise is provisioned, `infra/main.bicep:740,753,771`), though the SignalR fan-out stays unexercised behind the `maxReplicas: 1` pin (`:1424`), so #12 itself stays open. **Corrected:** #33's load-bearing `README.md:74` quote no longer exists (the file now states the opposite at `README.md:80-84`, the Service Bus emulator tier having landed), and drifted anchors were refreshed repo-wide (`CI.slnf:56`→`:58`, `deploy.yml:303-309/343/348/417`→`:483-489/:553/:783`, `e2e.yml:78`→`:119`, `main.bicep:1113`→`:1424`, `:341`→`:488`). Scorecard indices move to **Maturity 97.2% (311/320) / Implementation 85.9% (687/800)**; the below-4 set widens to **§12/§21/§22/§33**. **Reconciled 2026-07-23 (twenty-third-cycle full re-score, pin v1.123.0, HEAD `160f59f5`, no moves):** every category re-confirmed at its prior score from evidence read this run (no closures, no new items, no re-ranks, no TD changes; the below-4 set stays §12/§21/§22/§33 with priorities unchanged: #21 at 3, #12/#22/#33 at 2). Two first-pass maturity-lift proposals were adversarially rejected as verified non-moves: **§12 M3→4 rejected** (the k6 tier still runs monthly/dispatch out of band, `load-test.yml:8`, with `load-freshness` a recency-only deploy check, `deploy.yml:553`, and Notification pinned `maxReplicas: 1`, `infra/main.bicep:1424`) and **§21 M3→4 rejected** (the recorded manual screen-reader pass is still the empty placeholder in `ACCESSIBILITY-SCREENREADER-PASS.md`, remaining the cheapest maturity lever). The v1.122.0/v1.123.0 lockstep sweeps moved no score. Scorecard indices hold **Maturity 97.2% (311/320) / Implementation 85.9% (687/800)**, ADR set **001-051**. **Verification pass 2026-07-23 (post-cycle, no score claims):** stale claims corrected in place across this ledger and the scorecard: the #6 header's "2 of 4 services" inbox basis (all four services carry `EnableInbox=true` since the TD-02 close), #26's "pending manual Aspire verification + release" phrasing (shipped and deployed), the #29/#31 "activates on the next push" phrasing (gates live in `deploy.needs` since 2026-06-30), the scorecard's §8/§28 "re-quarantined" claim (E2E #5 was un-quarantined 2026-07-19, plain `[Fact]` at `SpeakerSelfServiceTests.cs:58`), lock-file count 58→65, resx pairs 40→53, `MMCA.Common.*` pin 1.117.0→1.123.0 in the §16/§32 rows, and drifted anchors (`deploy.needs` `:783`→`:791`, `load-freshness` `:548`→`:553`, coverage floor `:83`→`:210-212`, `sloWorkbook` `:278`→`:425`, Notification pin `:1113`→`:1424`, `CI.slnf:56`→`:58`, `OrganizerConcurrencyTests.cs:26`→`:27`). The genuinely-open TD set today is **TD-08, TD-15, TD-16** (older per-cycle "open TD-..." snapshots above are frozen provenance). The screen-reader-pass runbook lives centralized as `adc-ACCESSIBILITY-SCREENREADER-PASS.md` in Website `docs-src/guides/` (2026-07-20 centralization); bare-name references below predate that move. **Reconciled 2026-07-28 (twenty-fourth-cycle full re-score, pin v1.131.0, HEAD `2ec77796`, one score down):** **§15 Best Practices & Code Quality implementation 8→7** (weight 2), the only score move and the only rank change on either axis; maturity holds 4, independently re-derived, so #15 stays in the protect set while taking the **top row of the implementation band at implPriority 4**. The basis is hygiene drift, not a code-quality regression: an audit suppression expired by its own written removal condition (`Directory.Build.props:49-51` vs its comment at `:41-48`), three undated global `NoWarn` codes (`:22`), and the MAUI `MMCA.ADC.UI` project sitting outside every CI build and outside the CI-audited dependency graph (`MMCA.ADC.CI.slnf:25`, `deploy.yml:288`), which is precisely the graph the `:8-12` suppressions exist for. Band totals move to **15 categories / 35 gap points** (count unchanged, §15 was already in the band) and **95.1% of the 90% attainable ceiling**. **No closures:** closure needs maturity 4 AND implementation >= 9 independently, and all four maturity-band items are still M3 and all four still I8, with none of the 15 implementation-band categories reaching 9, so nothing moves to the protect list and the maturity band is byte-identical (#21 at 3, #12/#22/#33 at 2, 4 categories / 9 points). Re-verified still-open levers: #21's screen-reader results log is still the empty `_yyyy-mm-dd_` placeholder (`adc-ACCESSIBILITY-SCREENREADER-PASS.md:60-62`), #22 is still chromium-only gating (`deploy.yml:505`) with firefox/webkit advisory on the Mon/Thu schedule (`e2e.yml:131`, cron `:43`), #12 is still scale-pinned (`infra/main.bicep:1447`) with a monthly out-of-band capacity proof (`load-test.yml:18`). **Adjudicated DEFERRED, not open:** the **#27** impl 8→9 pseudo-loc candidacy, rejected for a third time (21st, 22nd, 24th) on byte-identical evidence, is now recorded in Deliberate / accepted with its cost and explicit re-open triggers rather than carried as a live candidacy to re-reject a fourth time. Also re-rejected and recorded so they are not re-proposed: #24 impl 8→9 and #33 M3→4 / I8→9; #6 and #30 sit at I9, already at the scheduling target, so their recorded "9→10" candidacies are out of scope for both bands. **New: TD-17** under #33 (the Service Bus emulator parity tier is now dispatch-only after hanging to its 8-minute timeout on 7 of 7 runs, so #33's header basis is corrected from "nightly plus recency gate" to "no schedule, no gate") and **TD-18** under #15 (the MAUI CI-enforcement gap, recorded rather than fixed because a MAUI CI build cuts against the 2026-07-18 Actions-minute reduction). **TD-16 refreshed, worse:** `HappeningNow.razor.cs` is now exactly 400 lines against the enforced cap, and the recorded 360-379 band was stale (`SpeakerDetail.razor.cs` is 386, not 365); current measured set 400/386/379/376/367/365/362 with two recorded paths corrected. **TD-15 was NOT re-verified this run** and is left exactly as written; its figures are not restated as re-confirmed. **Evidence refresh (no score move):** the arch-test suite is now **29 test classes / 31 `.cs` files / 91 executed methods, re-run green 2026-07-28 (91/91)**, up from 26/28/82, and **90 of the 91 are inherited** from the shared rule library after the §13 alert-runbook pairing gate was lifted upstream (`ObservabilityConventionTests.cs:7` is now a bare thin subclass), leaving the TD-14 Profile-form guard (`FormsConventionTests.cs:31`) as the single ADC-local method; ADR set **001-060**. Anchors refreshed repo-wide: `deploy.yml` e2e-gate `:488`→job at `:500` with `browsers: '["chromium"]'` at `:505` and rationale `:478-480`→`:493-499`, `deploy.needs` `:791`→`:829`, the freshness jobs re-split (`cost-guard :488`, `dr-freshness :513`, `load-freshness :570`, `cross-service-freshness :627`) with their skip checks at `:526/:583/:642`, `e2e.yml:119`→`:131`, `infra/main.bicep:1424`→`:1447`, `load-test.yml:8`→`:18`, `cross-service-tests.yml` emulator job at `:142` with its dispatch-only condition at `:144`. The genuinely-open TD set today is **TD-08, TD-15, TD-16, TD-17, TD-18**. **Reconciled 2026-08-01 (twenty-fifth-cycle full re-score, pin v1.135.0, HEAD `995a7886`, no moves):** every category re-confirmed at its prior score from evidence read this run, so there are **no closures, no new items and no re-ranks**: both bands are byte-identical (maturity 4 categories / 9 points, #21 at 3 and #12/#22/#33 at 2; implementation 15 categories / 35 points). Closure needs maturity 4 AND implementation >= 9 independently, and all four maturity-band items are still M3/I8 while none of the 15 implementation-band categories reached 9. All six adversarial adjudications this cycle were proposed implementation lifts and **all six were rejected**: **§5 8→9** (DTOs live in the Shared assembly with horizontal mapper/validation/specification folders, the layered-by-project hybrid is unchanged, and `AdcArchitectureMap.cs:12-44` omits `MMCA.ADC.Notification.Application` from the enforced set), **§13 9→10** (three ENABLED production alerts have no runbook triage section and sit outside the pairing gate's scope, including the sev-1 gateway-availability alert at `infra/main.bicep:481`), **§24 8→9** (the named bUnit lever shipped, but client validation does not mirror the server's cross-field and format rules and the error summary covers 7 of 15 MudForm forms: both are now recorded as §24's levers, replacing "not yet identified"), **§27 8→9** (fourth rejection, byte-identical evidence plus one new culture-formatting violation), **§31 8→9** (the surge/revert automation is not pulled), and **§33 8→9** (second rejection: see the rewritten TD-17 below). **TD-17 is HALF CLOSED and its blocker text was invalid:** the `servicebus-emulator-smoke` job is **back on the weekday nightly** since 2026-07-29 (`cross-service-tests.yml:144-146` `needs: should-run` + `if: needs.should-run.outputs.run == 'true'` under `cron: '0 6 * * 1-5'` at `:26,:30`, `timeout-minutes: 10` at `:148`), and the recorded root cause was wrong: the comment at `:130-143` records per-test bus re-provisioning against an admin plane throttled at roughly 1 op/sec (IAsyncLifetime plus xUnit per-Fact class instantiation), fixed by hoisting the bus to the collection fixture and wall-clock bounding both startup phases, **not** the companion SQL image. The remaining half is open: the tier is `continue-on-error: true` (`:149`) and gates nothing, since `cross-service-freshness` keys off the `cross-service` job (`:124-128`, gate at `deploy.yml:663`). **TD-16 re-measured, and the headline is no longer true:** `HappeningNow.razor.cs` is **394**, not 400, and the high-water mark moved to `SessionSelectionDashboard.razor.cs` at **395**, so the "flush at the cap, zero headroom" framing is retired in favour of 5 lines of headroom; current measured set 395/394/386/376/367/365/362 at HEAD `995a7886`, seven files within 38 lines of the 400 cap, still effort S. **TD-08 and TD-18 re-confirmed open** (TD-18's gating-scan anchor drifted `deploy.yml:288`→`:319`). **TD-15 was NOT re-verified for a second consecutive cycle** and is left exactly as written; its cost figures are not restated as re-confirmed. **Anchors refreshed repo-wide:** `deploy.yml` e2e-gate `:500`→`:531` with `browsers` `:505`→`:541`, `deploy.needs` `:829`→`:866`, the freshness jobs re-split again (`cost-guard :519`, `dr-freshness :549`, `load-freshness :606`, `cross-service-freshness :663`) with their skip checks at `:562/:619/:678`, coverage floor `:210-212`→`:254`, the gating vuln scan `:288`→`:319`, `--locked-mode` restores at `:199`/`:299`, `e2e.yml:131`→`:144` with the nightly matrix replaced by **alternating** single-engine crons at `:49,:50`, `infra/main.bicep:1447`→`:1530` (and the SLO/budget/SQL anchors re-derived), `Directory.Build.props` suppression `:49-51`→`:54` and NoWarn `:22`→`:26`, ADC pin `:123`→`:139` at **1.135.0**, lock files **65→66**, ADR set **001-064**. **Reconciled 2026-08-14 (twenty-sixth-cycle full re-score, pin v1.152.0, HEAD `19021d93`, no moves):** every category was re-confirmed at its prior score from evidence read this run, so again there are **no closures, no new items and no re-ranks**: both bands are byte-identical (maturity 4 categories / 9 points, §21 at 3 and §12/§22/§33 at 2; implementation 15 categories / 35 points). All eight adversarial adjudications were proposed lifts and **all eight were rejected**: **§5 8→9** (DTOs and their mappers still outside the slice, and `AdcArchitectureMap.cs:12-43` still has no `Module("Notification", ...)` entry, now named as **TD-19**), **§7 8→9** (the bidirectional sync-gRPC red flag broadened to a second pair, Identity-Notification), **§12 M3→4** (zero commits touched `load-test.yml`, `deploy.yml` or `Tests/Load/` since the prior HEAD), **§13 9→10** (three of the six ENABLED production alerts still have no runbook, including the sev-1 gateway-availability alert, whose anchor moves `infra/main.bicep:481`→`:496-502`, `severity: 1` at `:502`; §13 sits at I9, outside both bands), **§15 7→8** (all three downgrade grounds intact, and the expired suppression is further past its removal condition now that the pin is v1.152.0), **§23 8→9** (WASM code-split and image optimization both still open), **§28 8→9** (the new state-management bUnit coverage is a within-band improvement) and **§31 8→9** (the surge/revert automation is still not pulled, `cost-guard.yml:4,:12,:17,:59,:83`). **New: TD-19** under §5 (the Notification module is absent from the enforced architecture map, effort S), which replaces §5's "lever not yet identified" band row. **TD-16 re-measured, and the headroom narrowed:** the high-water code-behind rose 395→**398** of the 400 cap, leaving 2 lines rather than 5. **TD-17 unchanged in substance, anchors corrected:** job `:145`, `needs` `:146`, `if` `:147`, `timeout-minutes` `:149`, `continue-on-error` `:150`, schedule `workflow_dispatch` `:26` + cron `'0 6 * * 1-5'` `:31` (the recorded `:26,:30` was wrong), gate-keying comment `:126-129`. **TD-15 was NOT re-verified for a third consecutive cycle** and is left exactly as written. **Evidence refresh (no score move):** axe coverage is **31 test methods over roughly 29 distinct pages** (`AccessibilityTests.cs:21-365`), not 17 pages; the routable-page denominator is **49** `@page` files under `Source` (48 excluding the MAUI-only `DeviceSettings.razor`), not 37, so §27's deferred lift now costs roughly 45 pages; the Notification scale pin moves `infra/main.bicep:1530`→`:1616`; §24's error-summary ratio is **8 of 18 MudForm-bearing pages** (19 forms), not 7 of 15; TD-18's MAUI `NoWarn CA5392` anchor moves `MMCA.ADC.UI.csproj:131`→`:143`. Indices hold **Maturity 97.2% (311/320) / Implementation 85.6% (685/800)**, ADR set **001-078**. **Reconciled 2026-08-23 (twenty-seventh-cycle full re-score, pin v1.160.0, HEAD `96f0919a`, two scores down):** **§4 Domain-Driven Design implementation 9→8** (weight 3; public-setter cross-aggregate navigations on `Session`/`Sponsor`/`Activity`, aggregate-external validation of `Event`'s newer optional fields against the repo's own Sponsor convention, and `Event.OrganizerContactEmail` as a raw string where the `Email` VO covers the same concept on `User`/`Speaker`) and **§22 Responsive & Cross-Browser implementation 8→7** (weight 2; the rubric's density-options criterion has zero adoption and content reflow is only partial on the 17 non-DataGrid table pages), so the implementation band grows to **16 categories / 40 gap points**: §4 enters the band for the first time (implPriority 3, maturity 4 holds, so #4 stays in the protect set) and §22 rises to the joint top at implPriority 4 alongside §15. **No closures** (all four maturity-band items still M3 with their levers re-verified open: the SR-pass log still the empty placeholder at `adc-ACCESSIBILITY-SCREENREADER-PASS.md:62`, #12 still scale-pinned at `infra/main.bicep:1648` with its rationale at `:1643-1647`, #22 still chromium-only at `deploy.yml:541`, #33's parity tier still advisory at `cross-service-tests.yml:150`), and the maturity band is byte-identical for a fourth consecutive cycle (4 categories / 9 points). All eight adversarial adjudications were proposed lifts and **all eight were rejected** (§5, §7, §15, §17 as a 9→10, §18, §21 as an M3→4 + I8→9 pair, §28, §31). **New: TD-20** under #28 (the deploy-gating chromium E2E/axe/CWV suite is CONDITIONAL: `e2e-gate` runs only when the `changes` job marks the diff UI-affecting, `deploy.yml:538` with rationale `:533-537`, and the `deploy` job accepts a skipped gate, `:896` with comment `:880-883`, so a backend-only, infra-only or script-only merge deploys with no browser, axe or CWV run; a matching amendment is recorded in Deliberate / accepted), which also names §28's previously unidentified band lever. **Wording corrected ledger-wide:** the `integration-tests` job is **PR-only** (`if: github.event_name == 'pull_request'`, `deploy.yml:389`) and is NOT in `deploy.needs` (`:866`), so the "gates every deploy" / "deploy-gating `MMCA.ADC.Integration.slnf`" phrasing under #30/#14/#11/#8/#9 is rewritten to "gates every PR (required check on an up-to-date branch)"; TD-03's closure itself stands. **TD-16 re-measured, unchanged at the top but wider:** high-water 398/394/386 identical to 2026-08-14, but the within-38-lines set grew from seven to **eight** files (three grew: `PublicSessionList.razor.cs` 367→398, `ADCHome.razor.cs` 341→380, `EventDetail.razor.cs` 365→377), so TWO files now sit at 398. **TD-17/TD-18/TD-19 re-confirmed open** on current anchors; **TD-15 NOT re-verified for a fourth consecutive cycle** (no billing read; figures stand as written). **Provenance:** the §15 band row's "pins v1.135.0 at `Directory.Packages.props:139`" is doubly stale, now **v1.160.0** at `Directory.Packages.props:92-110`, twenty-five releases past the v1.121.0 SQLite sweep. Indices move to **Maturity 97.2% (311/320) / Implementation 85.0% (680/800)**, ADR set **001-096**. -**Scope:** 4 categories remain below maturity 4 (§12/§21/§22/§33; the 2026-07-21 twenty-second-cycle reconciliation **REOPENED §22** after the 2026-07-18 CI-minute reduction cut the deploy `e2e-gate` to chromium only, `deploy.yml:541`, leaving firefox/webkit nightly-advisory, `e2e.yml:144`, and thinner still since the 2026-07-29 move to alternating single-engine legs, `e2e.yml:49,:50`); 30 categories score maturity 4 (protect, don't regress); none are N/A. On the implementation axis, **15 categories score implementation <= 8** and are ranked in their own band below (35 gap points); **19 categories sit at maturity 4 AND implementation >= 9**, which is the only combination that reaches the protect list. +**Scope:** 4 categories remain below maturity 4 (§12/§21/§22/§33; the 2026-07-21 twenty-second-cycle reconciliation **REOPENED §22** after the 2026-07-18 CI-minute reduction cut the deploy `e2e-gate` to chromium only, `deploy.yml:541`, leaving firefox/webkit nightly-advisory, `e2e.yml:144`, and thinner still since the 2026-07-29 move to alternating single-engine legs, `e2e.yml:49,:50`); 30 categories score maturity 4 (protect, don't regress); none are N/A. On the implementation axis, **16 categories score implementation <= 8** and are ranked in their own band below (40 gap points; §4 joined and §22 deepened on 2026-08-23); **18 categories sit at maturity 4 AND implementation >= 9**, which is the only combination that reaches the protect list. > **High-leverage fixes that each clear or relieve several items: do them once:** > - ~~**Rework the orphaned WebAPI integration tier**~~ → **DONE (#14):** per-service `WebApplicationFactory` tiers, ~345 tests gating every deploy, also closed #11's authz-gate and #16's non-building projects, and advanced #8. See `IntegrationTestReworkPlan.md`. @@ -40,7 +40,7 @@ Token handling uses two rubric-named anti-patterns, with no CSP defense-in-depth - [x] Add an integration/E2E test **asserting header presence** so it can't regress. → **DONE (2026-06-27):** `MMCA.ADC.Gateway.Tests/SecurityHeadersTests` boots the real Gateway via `WebApplicationFactory` (no SQL, runs in the fast CI tier / `CI.slnf`) and asserts `/alive` carries `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Referrer-Policy`, `Permissions-Policy`, CSP `frame-ancestors 'none'`, and HSTS (Production env). A refactor dropping `UseCommonSecurityHeaders()` now fails CI. - [x] Shorten the 30-day refresh cookie; consider `SameSite=Strict`. → **DONE (2026-06-27, with recorded `SameSite` decision):** the session/refresh cookie is already **7 days** (not 30): `SessionCookieJar` (MMCA.Common.API) pins `Lifetime = TimeSpan.FromDays(7)`, "aligned to the refresh-token lifetime so a cookie never outlives the credential it carries." **`SameSite=Strict` is deliberately NOT adopted:** `SameSite=Lax` is load-bearing for the SSR-prerender path (`[Authorize]` pages opened in a new tab / on F5 / following an external link are cross-site top-level navigations that Strict would strip the cookie from, forcing a spurious /login bounce, the exact scenario ADR-022's cookie scheme exists to serve); CSRF is covered defense-in-depth by the `/auth/session/token` endpoint's `Sec-Fetch-Site` check + POST-only + `SameSite=Lax`. -### [x] #28 · Front-End Testing & Quality · 3 → 4 (weight 3) · *RESOLVED 2026-07-02, reconciled here 2026-07-03 (scorecard §28 maturity 4 / impl 8): the chromium E2E/axe suite is an enforced deploy gate (`e2e-gate` in `deploy.needs`, `deploy.yml:303-308,:343`; `e2e.yml:31` `workflow_call`), closing TD-06 and TD-07. Firefox/webkit stay advisory nightly (#22); visual-regression snapshots remain optional polish* +### [x] #28 · Front-End Testing & Quality · 3 → 4 (weight 3) · *RESOLVED 2026-07-02, reconciled here 2026-07-03 (scorecard §28 maturity 4 / impl 8): the chromium E2E/axe suite is an enforced deploy gate (`e2e-gate` in `deploy.needs`, `deploy.yml:303-308,:343`; `e2e.yml:31` `workflow_call`), closing TD-06 and TD-07. Firefox/webkit stay advisory nightly (#22); visual-regression snapshots remain optional polish. **Qualified 2026-08-23 (TD-20):** the gate is conditional since 2026-07-29: `e2e-gate` runs only when the diff is UI-affecting (`deploy.yml:538`) and `deploy` accepts a skipped gate (`:896`), so a backend-only merge deploys with no browser run* Only one UI test level exists (manual, non-gated E2E). - **(Medium)** UI E2E suite **excluded from CI**: no front-end merge gate. `deploy.yml:40-48` runs only `CI.slnf`; E2E needs the full Aspire stack and is run manually, so UI regressions can merge to prod undetected. - **(Medium)** Accessibility untested: no axe/Lighthouse anywhere. @@ -49,7 +49,7 @@ Only one UI test level exists (manual, non-gated E2E). **Fix** - [x] Add a **bUnit** component-test project (conditional rendering / edge states). → **DONE (3 module projects):** `MMCA.ADC.Conference.UI.Tests` (bUnit v2 harness, MudServices + loose JSInterop + permissive-auth doubles so `` renders), in `CI.slnf`, covering the three **public detail** pages (Event/Speaker/Session: loaded vs not-found) plus the Session page's `` action bar (hidden anonymous / shown authenticated); **`Identity.UI.Tests`** (a mutable-auth harness, since Identity pages inject `AuthenticationStateProvider` directly): `Profile` loaded/error-state bUnit tests + the `/users` authz fitness test; and **`Engagement.UI.Tests`** covering **both feedback forms**: `EventFeedbackTests` (dynamic question render by type + per-question upsert skipping unanswered) and now **`SessionFeedbackTests` (2026-06-27)**: precondition gating (BR-16 unscheduled / BR-91 service / BR-49 status block the form), session-not-found error state, question render by type, and upsert-only-answered. **List pages deliberately skipped for bUnit**: `DataGridListPageBase` is infra-heavy (7 injected services + JS interop/PersistentComponentState); its plumbing belongs to MMCA.Common's own tests, the derived page logic is thin. - [x] Add a **route-authorization fitness test**, `ManagementRouteAuthorizationTests` (reflection over Conference.UI): admin-namespace pages must keep `[Authorize(Roles="Organizer")]`, the set is asserted non-empty (no vacuous pass), and public pages must stay anonymous at the page level. Closes the #25 residual. -- [x] Wire **axe-core** (`Deque.AxeCore.Playwright`) + ≥1 a11y assertion (**TD-06**) → **DONE (2026-07-02, ticked on the 2026-07-03 reconciliation):** the axe-core `AccessibilityTests` (17 pages, `Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/AccessibilityTests.cs`) run inside the deploy-gating chromium `e2e-gate` job (`e2e.yml:236` runs the whole E2E project; `deploy.yml:343` puts e2e-gate in `deploy.needs`), so the a11y assertions now gate every deploy. +- [x] Wire **axe-core** (`Deque.AxeCore.Playwright`) + ≥1 a11y assertion (**TD-06**) → **DONE (2026-07-02, ticked on the 2026-07-03 reconciliation):** the axe-core `AccessibilityTests` (17 pages, `Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/AccessibilityTests.cs`) run inside the deploy-gating chromium `e2e-gate` job (`e2e.yml:236` runs the whole E2E project; `deploy.yml:343` puts e2e-gate in `deploy.needs`), so the a11y assertions gate every UI-affecting deploy (conditionality recorded 2026-08-23 as TD-20: a skipped gate does not block a non-UI merge, `deploy.yml:538,:896`). - [x] Make a **smoke E2E subset an automatic merge gate** (**TD-07**) → **DONE (2026-07-02, exceeded):** the **full** chromium suite (not just a smoke subset) is the deploy-gating `e2e-gate` job (`deploy.yml:303-308` `uses: ./.github/workflows/e2e.yml` with `browsers='["chromium"]'`; `e2e.yml:31` `workflow_call`), promoted after validation run 28604877733 (first fully green three-browser matrix). The former Blazor-Server-under-load blocker was resolved by the `E2E_FORCE_SERVER` pin + reload-and-rewait fixes (see the 2026-07-02 notes below). - [ ] Add Playwright **visual-regression** snapshots for key pages. @@ -93,7 +93,7 @@ Strong in-app resilience (Polly, SQL retry, outbox, health probes), now with a f - [x] Implement a **real erasure path**: `IAnonymizable` + anonymize-on-delete (immediate erasure). *(A scheduled-purge backstop for rows soft-deleted by other paths is optional now that delete erases inline.)* - [x] **Redact/tokenize PII** before logging: done in `UserRegisteredHandler`. - [x] Add an **export/access endpoint**: `GET /users/{userId}/export` (Identity-owned data); ~~cross-service bookmark/notification aggregation is the remaining piece~~ → **cross-service aggregation DONE 2026-07-11 (remediation wave 6):** the export now aggregates Engagement (session bookmarks + submitted live-Q&A questions, new `user_engagement_export.proto` rpc mirroring the bookmark-count pattern) and Notification (inbox items, new `user_notification_export.proto` rpc on the existing ADR-012 grpc ingress; a new `Notification.Shared` layer carries the boundary per module-isolation rules). Aggregation is best-effort per section (`Available=false` + empty lists when a peer is down after the Polly pipeline; the export never fails on a peer outage). Identity gains gRPC edges to both peers (AppHost `WithReference` without deadlocking `WaitFor`; bicep env mirroring the existing gRPC-edge mechanism). 9 handler unit tests + a payload-shape integration test (faked peers). *Recorded follow-up, deliberately out of scope:* event/session **feedback answers** live in the Conference DB (`EventQuestionAnswer`/`SessionQuestionAnswer`), so full-corpus export would need a third (Conference) edge; the recorded §30 residual named only bookmarks + notifications, both now covered. §30 Implementation 9→10 candidacy recorded for the next re-score. -- [x] Add a **fitness/integration test** proving an erasure path exists and that PII is not logged: domain unit tests added; the end-to-end erasure + no-PII-in-logs assertion rides the #14 integration-tier rework. **SHIPPED 2026-07-16:** `ErasureAndPiiLoggingTests` (Identity integration tier, deploy-gating): (1) a deleted account is erased from every API surface end to end (login 401, export 404, listing clean) through the real host pipeline; (2) a full register-login-delete lifecycle emits ZERO log lines carrying the account's email or names (every host log line captured via the new `PiiLogCapture` sink in the test factory, asserted against unique markers). §30 I9→10 candidacy already recorded stands on stronger evidence. +- [x] Add a **fitness/integration test** proving an erasure path exists and that PII is not logged: domain unit tests added; the end-to-end erasure + no-PII-in-logs assertion rides the #14 integration-tier rework. **SHIPPED 2026-07-16:** `ErasureAndPiiLoggingTests` (Identity integration tier, gating every PR as a required check; wording corrected 2026-08-23, the `integration-tests` job is PR-only, `deploy.yml:389`, not in `deploy.needs`): (1) a deleted account is erased from every API surface end to end (login 401, export 404, listing clean) through the real host pipeline; (2) a full register-login-delete lifecycle emits ZERO log lines carrying the account's email or names (every host log line captured via the new `PiiLogCapture` sink in the test factory, asserted against unique markers). §30 I9→10 candidacy already recorded stands on stronger evidence. ### [x] #27 · Internationalization · 3 → 4 (weight 1) · *RESOLVED 2026-06-30, scorecard §27 **maturity 4 / impl 8** as of the 2026-07-03 i18n completion sweep: dual CI gates (`TranslationCompletenessTests` floor 40 + the new `LocalizedTextConventionTests`), zero residual hard-coded literals (titles/snackbars/breadcrumbs/nav/home), MudBlazor chrome localized via the inherited `ResxMudLocalizer`, `ErrorMessages.Success` concatenation eliminated (obsoleted upstream, 28 sites swept). The impl 8→9 lever is extending the pseudo-loc no-overflow (text-expansion) E2E evidence, which today covers only the shared chrome in Common's gallery gate, to ADC's own pages* - ~~**(Low)** Hardcoded user-facing English throughout markup, e.g. `Source/Modules/Conference/.../Pages/Speaker/SpeakerDashboard.razor:7-60`; no `.resx`, no `IStringLocalizer`, `InvariantCulture` display.~~ **RESOLVED (v1.86.0 sweep, 2026-06-27):** ADC now ships real **en-US + es** i18n (36 base `.resx` + 35 `.es.resx` across the three module UIs + three API error-resource sets, `IStringLocalizer` in ~33 pages, culture-aligned SSR/Server/WASM, cross-device `User.PreferredCulture` persistence, backend error localization keyed on `Error.Code`, `SupportedCultures = [en-US, es]`). The scorecard flips §27 from **N/A** to **scored at Maturity 3 / Implementation 8**. ADR-011 (single-locale) is **superseded by ADR-027**. @@ -118,17 +118,17 @@ Strong in-app resilience (Polly, SQL retry, outbox, health probes), now with a f ## 🟡 Priority 3: score 3, weight 3 (one rung from a 4) ### [x] #14 · Testability & Test Strategy: 3 → 4 · *RESOLVED (see IntegrationTestReworkPlan.md)* -- ~~**(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 `WebApplicationFactory` tiers** (Identity/Conference/Engagement, **~345 tests**) over a SQL-service CI container, plus the revived `MMCA.Common.API` middleware unit tests. In-process JWT override (for `AddForwardedJwtBearer`), gRPC fakes, broker InProcess short-circuit, Respawn reset. Runs via `MMCA.ADC.Integration.slnf` and **gates every deploy** (`integration-tests` job in `deploy.yml`, a required dep of `deploy`). All CI-verified green. +- ~~**(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 `WebApplicationFactory` tiers** (Identity/Conference/Engagement, **~345 tests**) over a SQL-service CI container, plus the revived `MMCA.Common.API` middleware unit tests. In-process JWT override (for `AddForwardedJwtBearer`), gRPC fakes, broker InProcess short-circuit, Respawn reset. Runs via `MMCA.ADC.Integration.slnf` and **gates every PR** (required check on an up-to-date branch; wording corrected 2026-08-23: the `integration-tests` job is PR-only, `if: github.event_name == 'pull_request'` at `deploy.yml:389`, and protects production through branch protection, not `deploy.needs`). All CI-verified green. **Fix** - [x] **Rework integration tests** against the new per-service hosts and re-include them. - [x] Wire **coverage** collection (TD-05, **done 2026-06-26**): coverage is collected via `dotnet-coverage` (cobertura) and **gated** by a **55.5%** unit-tier line-coverage floor (ADC's own `+MMCA.ADC.*;-*.Tests` code, ratcheted to 55.5 after the 2026-07 coverage program, actual ~57%) that hard-fails the deploy-gating `build-and-test` PR job (`deploy.yml:210-212`). No longer report-only. -- [x] **Cross-service handler coverage (Phase 4 headline flows)**: the consumer-side logic is now re-homed as **in-process integration tests** on the per-service fixtures (resolve the real `IIntegrationEventHandler` from the booted host, assert against the real DB; runtime-gated by the SQL `integration-tests` job): `Conference.IntegrationTests/CrossService/CrossServiceUserRegisteredTests.cs` (BR-207 name-match auto-link / ambiguous-skip / no-match-skip) + `Identity.IntegrationTests/CrossService/CrossServiceSpeakerLinkTests.cs` (`SpeakerLinkedToUser`/`SpeakerUnlinkedFromUser` set/clear `User.LinkedSpeakerId`). Pairs with `OutboxFidelityTests` (which covered the producer side only). Added via a small additive `Services` accessor on both fixtures; compile 0/0. +- [x] **Cross-service handler coverage (Phase 4 headline flows)**: the consumer-side logic is now re-homed as **in-process integration tests** on the per-service fixtures (resolve the real `IIntegrationEventHandler` from the booted host, assert against the real DB; PR-gated by the SQL `integration-tests` job): `Conference.IntegrationTests/CrossService/CrossServiceUserRegisteredTests.cs` (BR-207 name-match auto-link / ambiguous-skip / no-match-skip) + `Identity.IntegrationTests/CrossService/CrossServiceSpeakerLinkTests.cs` (`SpeakerLinkedToUser`/`SpeakerUnlinkedFromUser` set/clear `User.LinkedSpeakerId`). Pairs with `OutboxFidelityTests` (which covered the producer side only). Added via a small additive `Services` accessor on both fixtures; compile 0/0. - [~] *Phase 4 broker-transport tier (TD-02), landed 2026-07-06 as a non-gating nightly:* the genuine **MassTransit broker round-trip** (Testcontainers RabbitMQ + dual-host transport/outbox fidelity, not just handler logic) now runs as `MMCA.ADC.CrossService.IntegrationTests` (9 tests) on `cross-service-tests.yml`. Optional remaining coverage: speaker **analytics** and the **Conference→Engagement bookmark-count gRPC** reads. Making the tier a deploy gate is the shared §6 impl 9→10 lever (see TD-02 under #6). ### [x] #11 · Security: 3 → 4 · *RESOLVED* - ~~**(Medium)** Rate limiter is inert: named policies but no `GlobalLimiter`/`[EnableRateLimiting]`.~~ **RESOLVED:** MMCA.Common 1.54.0's `AddCommonRateLimiting` now attaches a `GlobalLimiter` (429 over **300 req/min per authenticated user**; partition name→user_id→IP). Anonymous traffic is deliberately unlimited (public endpoints output-cached, login has its own protection, and Blazor-Server anonymous traffic shares the UI host IP); health/`/alive`/JWKS/`application/grpc` bypassed. Swept to all 7 services (ADC + Store) on the 1.54.0 bump; CLAUDE.md "100 req/min" claims corrected. -- ~~**(Medium)** No automated **server-side authorization gate**.~~ **RESOLVED:** the #14 per-service tier includes the access-denied authz matrices (anonymous→401, attendee→403 across all services, ~55 tests), **gating every deploy**. +- ~~**(Medium)** No automated **server-side authorization gate**.~~ **RESOLVED:** the #14 per-service tier includes the access-denied authz matrices (anonymous→401, attendee→403 across all services, ~55 tests), **gating every PR** (required check; wording corrected 2026-08-23). - **(Medium)** Prod secrets in Container App secrets + ACR admin password: **not a vault/managed identity**. **Fix** @@ -147,11 +147,11 @@ Strong in-app resilience (Polly, SQL retry, outbox, health probes), now with a f - **(Low)** a11y is implemented (aria-labels, alt text, real links/buttons) but **never auto-verified**: no axe/Lighthouse in CI, no `AccessibilityTests`, no stated WCAG target. **Fix** -- [x] Add automated a11y checks and a stated **WCAG 2.1 AA** target → **DONE:** `Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/AccessibilityTests.cs` runs axe-core WCAG 2.1 AA scans (broadened to 17 pages on 2026-06-30; **31 axe test methods over roughly 29 distinct pages** as re-counted 2026-08-14, `:21-365`); the target is stated in `CLAUDE.md` and `ACCESSIBILITY-SCREENREADER-PASS.md`. *(Deploy-gated since 2026-07-02: the scans ride the chromium `e2e-gate` job in `deploy.needs`.)* +- [x] Add automated a11y checks and a stated **WCAG 2.1 AA** target → **DONE:** `Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/AccessibilityTests.cs` runs axe-core WCAG 2.1 AA scans (broadened to 17 pages on 2026-06-30; **31 axe test methods over roughly 29 distinct pages** as re-counted 2026-08-14, `:21-365`); the target is stated in `CLAUDE.md` and `ACCESSIBILITY-SCREENREADER-PASS.md`. *(Deploy-gated since 2026-07-02: the scans ride the chromium `e2e-gate` job in `deploy.needs`; conditional since 2026-07-29 per TD-20, so a non-UI merge deploys without an axe run. Coverage note 2026-08-23: four routable pages shipped 2026-08-19 with no axe coverage yet.)* - [x] **(impl 7→8 lever) Stand up a backend-less in-process axe merge-gate**, mirroring MMCA.Common's gallery-host pattern → **SUPERSEDED (2026-07-02):** the full axe suite became the deploy-gating `e2e-gate`, which delivered the impl 8 and the enforcement this scoped backend-less host targeted, so the separate host is no longer needed for the score. *(Still available as an architecture option if the full-suite gate ever has to be demoted.)* - [ ] **(maturity 3→4, cheapest open win)** Record a dated manual **screen-reader pass** in `ACCESSIBILITY-SCREENREADER-PASS.md` (needs a human + NVDA/VoiceOver against the running Aspire app; cannot be done headless, so it stays pending a human run). - [x] **(NEW 2026-07-12, latent contrast in state-gated Warning-outlined surfaces, effort S.)** Store's gated axe scan caught that an OUTLINED `MudAlert Severity="Severity.Warning"` renders its text in the Warning amber (#F57F17, ~2.6:1 on white, AA fail) the moment a state-gated banner actually rendered during a scan (Store run 29191273727; fixed there by switching to `Severity.Info` outlined). ADC carries the same latent pattern in at least `SpeakerDashboard.razor:37` and `SessionFeedback.razor:29` (plus amber `Variant.Outlined` `Color.Warning` buttons on `EventDetail.razor:141` and the bookmarked-state toggle on `PublicSessionDetail.razor:136`); the 17-page axe gate is green only because those states are not exercised by the scans. **FIXED 2026-07-16 (all six sites, two more than recorded):** the four outlined Warning alerts switched to `Severity.Info` outlined (Store parity; the sweep also caught `PresenterView.razor:21` and `SessionLive.razor:21`), and the two outlined amber buttons moved to the AA-passing Secondary teal (`EventDetail` Unpublish, and the bookmarked state of `PublicSessionDetail`'s toggle, whose filled-star icon keeps the state signal). Repo-wide grep for outlined Warning surfaces is now zero. CI.slnf 2073 green. -- [x] **(shared with #28)** Promote the **full** axe + E2E suite to a merge gate → **DONE (2026-07-02):** promoted as the chromium `e2e-gate` in `deploy.needs` after validation run 28604877733 (the first fully green three-browser matrix); firefox/webkit stay advisory on the nightly (#22). +- [x] **(shared with #28)** Promote the **full** axe + E2E suite to a merge gate → **DONE (2026-07-02):** promoted as the chromium `e2e-gate` in `deploy.needs` after validation run 28604877733 (the first fully green three-browser matrix); firefox/webkit stay advisory on the nightly (#22). *(Conditional since 2026-07-29, TD-20: runs only on UI-affecting diffs.)* ### [x] #18 · UI Architecture & Component Design · 3 → 4 (weight 3) · *RESOLVED 2026-07-15 (twentieth-cycle re-score: scorecard §18 maturity 3→4 CONFIRMED on the `UIArchitectureConventionTests` CI.slnf gate; implementation holds 9). The 2026-07-02 reopening (no §18 UI-architecture fitness gate; the route-auth tests were a §25 gate wrongly credited here) is answered by the wave-2 gate below* - **(Low)** No bUnit tests, no UI fitness function; one 425-line code-behind. *(Original 2026-06-08 finding: bUnit tests have since shipped, but a UI-architecture fitness gate never did, so the 2026-07-02 re-score withdrew the maturity-4 that had credited the route-auth tests as a §18 gate.)* @@ -160,7 +160,7 @@ Strong in-app resilience (Polly, SQL retry, outbox, health probes), now with a f - [x] Add **component tests** (shared with #28) + a **UI convention test**: bUnit projects shipped. The "UI convention test" credited here was `ManagementRouteAuthorizationTests`, which is a **route-authorization** gate (§25), not a §18 UI-architecture gate, so it did not on its own earn maturity 4 (corrected on the 2026-07-02 re-score). - [x] **(maturity 3→4 lever) DONE 2026-07-11 (remediation wave 2):** the **§18 UI-architecture fitness gate** now runs in the CI.slnf arch gate: `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. Verified non-vacuous via a seeded 402-line file. Subsumed TD-13 (below) and additionally forced conforming splits of `SessionLive.razor.cs` 648→357 (three extracted panels) and `PublicSessionList.razor.cs` 499→371 (filter bar + view components), which had grown past the cap since TD-13 was recorded. Repo-wide max code-behind is now 387 lines. **Candidacy CONFIRMED on the 2026-07-15 twentieth-cycle re-score: scorecard §18 maturity 3→4.** - [x] **TD-13 DONE 2026-07-11 (remediation wave 2, subsumed by the §18 gate above):** both named code-behinds split via presentational sub-component extraction, markup moved verbatim (rendered DOM unchanged for the E2E selectors): `SessionSelectionDashboard.razor.cs` 507→367 (extracted `SessionSelectionSpeakerOverlap`, `SessionSelectionAiScores`, and the pure-rules `SessionSelectionDisplay` helper) and `SpeakerDetail.razor.cs` 429→368 (extracted `SpeakerCategoryItemsPanel`). Conference UI bUnit suite green (105/105) after each split. -- [ ] **TD-16 (recorded 2026-07-21, the §18 impl 8→9 lever, effort S):** seven code-behinds sit within 38 lines of the convention ceiling, the `MaxCodeBehindLines => 400` cap (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/UIArchitectureConventionTestsBase.cs:22`), so a method added to any of them fails the gate rather than being caught in review. **Re-measured 2026-08-14 (twenty-sixth cycle) at HEAD `19021d93`, and the headroom is narrowing again.** The high-water mark is `Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/SessionSelection/SessionSelectionDashboard.razor.cs` at **398**, up from 395, so headroom against the 400 cap fell from 5 lines to **2**; `SessionDetail.razor.cs` also rose 376→**382**. Current measured set: SessionSelectionDashboard 398, HappeningNow 394, SpeakerDetail 386, SessionDetail 382, PublicSessionList 367 (`Pages/Public/`), EventDetail 365, SessionLive 362 (the twenty-fifth cycle read 395/394/386/376/367/365/362 at HEAD `995a7886`; the "flush at the cap, zero headroom" framing stays retired). The item stays open: seven files in a 38-line band is still one refactor away from a red gate. **Blocker:** none, this is scheduled work. **Resolution path:** presentational sub-component extraction per the TD-13 pattern above, markup moved verbatim so the rendered DOM and the E2E selectors are unchanged. **Effort:** S. This is what took scorecard §18 implementation from 9 to 8 in the twenty-second cycle; maturity holds 4 on the gate. +- [ ] **TD-16 (recorded 2026-07-21, the §18 impl 8→9 lever, effort S):** seven code-behinds sit within 38 lines of the convention ceiling, the `MaxCodeBehindLines => 400` cap (`MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/UIArchitectureConventionTestsBase.cs:22`), so a method added to any of them fails the gate rather than being caught in review. **Re-measured 2026-08-14 (twenty-sixth cycle) at HEAD `19021d93`, and the headroom is narrowing again.** The high-water mark is `Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/SessionSelection/SessionSelectionDashboard.razor.cs` at **398**, up from 395, so headroom against the 400 cap fell from 5 lines to **2**; `SessionDetail.razor.cs` also rose 376→**382**. Current measured set: SessionSelectionDashboard 398, HappeningNow 394, SpeakerDetail 386, SessionDetail 382, PublicSessionList 367 (`Pages/Public/`), EventDetail 365, SessionLive 362 (the twenty-fifth cycle read 395/394/386/376/367/365/362 at HEAD `995a7886`; the "flush at the cap, zero headroom" framing stays retired). **Re-measured 2026-08-23 (twenty-seventh cycle) at HEAD `96f0919a`: the top is unchanged (SessionSelectionDashboard 398 / HappeningNow 394 / SpeakerDetail 386, still 2 lines of headroom) but the band WIDENED from seven to eight files, three of which grew since 2026-08-14: `PublicSessionList.razor.cs` 367→398 (a second file at 398), `ADCHome.razor.cs` 341→380, and `EventDetail.razor.cs` 365→377.** The item stays open: eight files in a 38-line band is still one refactor away from a red gate. **Blocker:** none, this is scheduled work. **Resolution path:** presentational sub-component extraction per the TD-13 pattern above, markup moved verbatim so the rendered DOM and the E2E selectors are unchanged. **Effort:** S. This is what took scorecard §18 implementation from 9 to 8 in the twenty-second cycle; maturity holds 4 on the gate. ### [x] #8 · Data Architecture · 3 → 4 · *RESOLVED 2026-06-29 (scorecard §8 maturity 4 / impl 9); TD-03 concurrency round-trip CLOSED 2026-07-06 (implemented + deploy-gated, Conference-only, so impl holds 9)* - ~~**(Low)** The orphaned integration suite means soft-delete/concurrency/outbox/migration behaviors have **no ADC-repo regression coverage**.~~ per-service integration tests restored (#14) exercise CRUD/auth/ownership against real per-service SQL DBs; **migration drift + soft-delete fidelity now guarded**. @@ -170,7 +170,7 @@ Strong in-app resilience (Polly, SQL retry, outbox, health probes), now with a f - [x] **Migration model-drift gate**: `build-and-test` now runs `dotnet ef migrations has-pending-model-changes` for all four modules (Identity/Conference/Engagement/Notification) on the Release build (`--no-build`, no DB needed). Fails the build (and so the deploy) if an entity changed without a matching migration. Verified locally: all four currently report "No changes" (drift-free). - [x] **Soft-delete fidelity test**: `SoftDeleteFidelityTests` (Conference integration tier) deletes an Event via the API, asserts it's hidden by the EF global query filter (404), and reads `[Conference].[Event]` directly to prove the row survives with `IsDeleted = 1` (soft- not hard-delete). The fixture now exposes its `ConnectionString` for raw-table assertions. - [x] **Outbox-dispatch fidelity**: `OutboxFidelityTests` (Identity tier) registers a user and asserts a `UserRegistered` row landed in `[dbo].[OutboxMessages]` (confirmed `InProcessEventBus.PublishAsync` persists the row transactionally, then marks it processed, the row is retained). The Identity fixture now exposes `ConnectionString`. (**TD-04**: done 2026-06-13, effort S.) -- [x] **TD-03 RESOLVED (2026-07-06):** **optimistic-concurrency API round-trip** now implemented and deploy-gated. The Conference `EventDTO` carries the `RowVersion` token via `IConcurrencyAware` (`Conference.Shared/Events/EventDTO.cs:16`), `UpdateEventHandler.cs:34` stamps the client's last-seen token with `SetOriginalRowVersion` (a stale token then raises `DbUpdateConcurrencyException`, which `DbUpdateExceptionHandler` maps to 409), and `OrganizerConcurrencyTests.cs:27` (`Update_WithStaleRowVersion_ReturnsConflict`) asserts the 409 inside the deploy-gating `MMCA.ADC.Integration.slnf` (the `integration-tests` job is in `deploy.needs`). Round-trip is Conference-only (Identity/Engagement expose no token-carrying update endpoint), so scorecard §8 holds impl 9 (not 10). The Common extension point (`SetOriginalRowVersion` on the repository) shipped and ADC adopted it on the five Conference update handlers. +- [x] **TD-03 RESOLVED (2026-07-06):** **optimistic-concurrency API round-trip** now implemented and deploy-gated. The Conference `EventDTO` carries the `RowVersion` token via `IConcurrencyAware` (`Conference.Shared/Events/EventDTO.cs:16`), `UpdateEventHandler.cs:34` stamps the client's last-seen token with `SetOriginalRowVersion` (a stale token then raises `DbUpdateConcurrencyException`, which `DbUpdateExceptionHandler` maps to 409), and `OrganizerConcurrencyTests.cs:27` (`Update_WithStaleRowVersion_ReturnsConflict`) asserts the 409 inside the PR-gating `MMCA.ADC.Integration.slnf` (wording corrected 2026-08-23: the `integration-tests` job is a required PR check, PR-only at `deploy.yml:389`, not in `deploy.needs`). Round-trip is Conference-only (Identity/Engagement expose no token-carrying update endpoint), so scorecard §8 holds impl 9 (not 10). The Common extension point (`SetOriginalRowVersion` on the repository) shipped and ADC adopted it on the five Conference update handlers. ### [x] #1 · SOLID Principles (3 → 4 · *ctor-dependency-count fitness threshold landed (scorecard §1 stays M4/I9) protect)* - ~~**(Low)** `AuthenticationService` has 7 constructor dependencies (down from 9: validators bundled into `AuthenticationValidators`) and injects a command handler directly. `Source/Modules/Identity/.../Users/AuthenticationService.cs:21-28`.~~ **GUARDED (v1.86.0 sweep, 2026-06-27):** kept as the cohesive auth facade, but a ctor-dependency-count fitness function now holds the line: `AuthenticationService` sits at the 7 high-water mark and an 8th dependency would fail the build. @@ -186,7 +186,7 @@ Strong in-app resilience (Polly, SQL retry, outbox, health probes), now with a f - ~~**(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 service** → all four service hosts now register `AddOpenApi()` + map `/openapi/v1.json` (built-in `Microsoft.AspNetCore.OpenApi`, package wired via the `.Service` convention in `Directory.Build.props`). **Mapped outside Production only**: these are internal services reached through the Gateway, which does not route the endpoint. The ApiExplorer group (`'v'VVV` → `v1`) matches the default document name, so the controller surface populates. - [x] **Fixed the stale CLAUDE.md** OpenAPI bullet (the four advertised UIs were a carry-over from the deleted WebAPI host; corrected to the `/openapi/v1.json` document). -- [x] **Contract test** (`OpenApiContractTests` in `MMCA.ADC.Conference.IntegrationTests`) boots the real host and asserts the document is served, is well-formed OpenAPI 3.x describing ≥ 10 routes, and still exposes the core public resources (`/Events`, `/Sessions`, `/Speakers`): so an accidental route removal fails CI. Runs in the integration-tests tier, which gates deploy. +- [x] **Contract test** (`OpenApiContractTests` in `MMCA.ADC.Conference.IntegrationTests`) boots the real host and asserts the document is served, is well-formed OpenAPI 3.x describing ≥ 10 routes, and still exposes the core public resources (`/Events`, `/Sessions`, `/Speakers`): so an accidental route removal fails CI. Runs in the integration-tests tier, which gates every PR as a required check (wording corrected 2026-08-23). - [x] **Versioning proven beyond v1.0 (2026-06-19).** `ServiceInfoController` (Conference) serves `/ServiceInfo` at **v1.0 (deprecated)** and **v2.0**, selected by the `api-version` header: exercising `MapToApiVersion` routing + deprecation reporting (`ReportApiVersions`). `ApiVersioningTests` (integration tier) asserts each version returns its own shape and that the `api-supported-versions` / `api-deprecated-versions` headers are emitted, so the versioning machinery is exercised, not merely configured for a single version. - *Deferred:* interactive UI (Scalar/Swagger). The three REST services are h2c-only on cleartext, so a browser can't reach a service-hosted UI directly; a Gateway-routed UI is a small follow-up if wanted. @@ -247,11 +247,11 @@ Strong in-app resilience (Polly, SQL retry, outbox, health probes), now with a f - [x] **Idempotent inbox enabled on the consumers (2026-06-19).** `MessageBus:EnableInbox=true` in `Identity.Service` + `Conference.Service` appsettings (the two services that consume integration events; each already ships the `InboxMessages` table via its `AddInboxMessages` migration). Dedup is now verified in MMCA.Common by `EfInboxStoreTests` (real SQLite + the production unique index → a redelivered message id records exactly once). Converts consumer idempotency from convention to infrastructure. - [x] *§6 Implementation 9→10 lever, **TD-02 CLOSED 2026-07-11 (remediation wave 6)**:* both remaining pieces landed. (1) The broker round-trip now **gates the deploy via recency**: a `cross-service-freshness` job in `deploy.yml`'s `needs` fails a deploy when the latest successful nightly `cross-service-tests.yml` run is older than 3 days (the dr/load-freshness pattern; the Testcontainers workflow itself still never runs inside the deploy chain, which the Docker constraint forbids and its header comment now documents). (2) `MessageBus:EnableInbox=true` on **all four** consumer services: Engagement and Notification appsettings joined Conference + Identity (their `InboxMessages` tables shipped with the 2026-06-09 `AddInboxMessages` migrations, applied in prod by the sole-migrator startup path). §6 Implementation 9→10 candidacy recorded for the next re-score. *(Historical context: the tier landed 2026-07-06 as 9 Testcontainers RabbitMQ+SQL dual-host tests.)* -### [~] #12 · Performance & Scalability · 3 → 4 (weight 2, priority (4-3)×2=2) · *OPEN at scorecard §12 M3/I8 (twentieth-cycle adjudication, re-confirmed 2026-07-17; a stale nineteenth-cycle "RESOLVED 2026-07-12 M4/I8" header accidentally committed via PR #15 is corrected here). The k6 proof's recency gates the deploy (`load-freshness`, `deploy.yml:570`, in `deploy.needs` at `:829`) and the WebVitals budgets are enforced inside the e2e-gate (§23's credit), but the k6 tier itself executes monthly/dispatch out of band (`load-test.yml:18`) and the Notification app stays pinned `maxReplicas: 1` (`infra/main.bicep:1447`), so maturity holds 3. Re-confirmed 2026-07-21 (twenty-second cycle), with one nuance newly verified: all three recency gates accept a `skip_freshness_gates` dispatch input with a required justification (checks at `deploy.yml:526,583,642`), so the k6 recency proof is bypassable-with-justification rather than unconditional (see Deliberate / accepted). Re-confirmed again 2026-07-28 (twenty-fourth cycle) and 2026-08-01 (twenty-fifth cycle) at M3/I8, substance unchanged both times; all anchors in this header were refreshed again on 2026-08-01 (`load-freshness` `:570`→`:606`, `deploy.needs` `:829`→`:866`, the Notification pin `infra/main.bicep:1447`→`:1530`, refreshed again 2026-08-14 to `:1616` (scale block) with its right-sizing rationale in the comment ending `:1614`, the break-glass checks `:526,583,642`→`:562,619,678`)* +### [~] #12 · Performance & Scalability · 3 → 4 (weight 2, priority (4-3)×2=2) · *OPEN at scorecard §12 M3/I8 (twentieth-cycle adjudication, re-confirmed 2026-07-17; a stale nineteenth-cycle "RESOLVED 2026-07-12 M4/I8" header accidentally committed via PR #15 is corrected here). The k6 proof's recency gates the deploy (`load-freshness`, `deploy.yml:570`, in `deploy.needs` at `:829`) and the WebVitals budgets are enforced inside the e2e-gate (§23's credit), but the k6 tier itself executes monthly/dispatch out of band (`load-test.yml:18`) and the Notification app stays pinned `maxReplicas: 1` (`infra/main.bicep:1447`), so maturity holds 3. Re-confirmed 2026-07-21 (twenty-second cycle), with one nuance newly verified: all three recency gates accept a `skip_freshness_gates` dispatch input with a required justification (checks at `deploy.yml:526,583,642`), so the k6 recency proof is bypassable-with-justification rather than unconditional (see Deliberate / accepted). Re-confirmed again 2026-07-28 (twenty-fourth cycle) and 2026-08-01 (twenty-fifth cycle) at M3/I8, substance unchanged both times; all anchors in this header were refreshed again on 2026-08-01 (`load-freshness` `:570`→`:606`, `deploy.needs` `:829`→`:866`, the Notification pin `infra/main.bicep:1447`→`:1530`, refreshed again 2026-08-14 to `:1616` (scale block) with its right-sizing rationale in the comment ending `:1614`, refreshed again 2026-08-23 to `:1648` with the rationale at `:1643-1647`, the break-glass checks `:526,583,642`→`:562,619,678`)* - ~~No load testing~~ → **DONE:** the k6 `conference-read-load.js` load test runs in CI sized to the measured ~67 peak. The SignalR multi-replica/backplane risk is **resolved into a documented single-replica acceptance** (Notification pinned `maxReplicas: 1`, `main.bicep:1007-1012`). - [x] **(impl-8 lever) Add client-side Core Web Vitals measurement to the E2E suite** → **DONE 2026-06-30:** a `WebVitalsTests` Playwright tier (`Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/WebVitalsTests.cs` + `Infrastructure/WebVitalsCollector.cs`) injects `PerformanceObserver`s to capture LCP/CLS/FCP/TTFB on `/`, `/conference/events`, `/login` (plus a single-interaction INP sample on the data-grid page), asserts lenient budgets, and emits a dated `web-vitals-*.json` artifact (wired into `e2e.yml` via `WEB_VITALS_OUTPUT_DIR`). Both the backend k6 and the client-side vitals are now measured, closing the residual gap and lifting scorecard §12 Implementation 7→8. Test/CI-only (no `MMCA.Common` release); builds clean. *(Maturity held at 3: the vitals run nightly/dispatch like k6, not as a merge gate.)* - [x] *Deferred (optional), provisioning half DONE:* prod **Redis** is provisioned (`infra/main.bicep:740` `Microsoft.Cache/redisEnterprise@2024-09-01-preview`, database at `:753`, `redis-connection-string` secret injected at `:771,849-850`), so the shared cache / **SignalR backplane** substrate exists. The fan-out itself stays unexercised: Notification is still pinned `maxReplicas: 1` (`infra/main.bicep:1447`, deliberate right-sizing rationale at `:1443-1446`; anchors refreshed 2026-07-28), so a verified two-replica hub fan-out remains the §12 impl 8→9 lever and this category stays open. -- [x] **(maturity 3→4 lever) DONE 2026-07-11 (remediation wave 3):** the capacity checks 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 624ms, 4-30x headroom), asserted inside the deploy-gating chromium `e2e-gate` (e2e.yml runs the whole E2E project). **Adjudicated 2026-07-15 (twentieth-cycle re-score): the §23 half was ACCEPTED (scorecard §23 maturity 3→4 on the enforced CWV budgets) but the §12 half was REJECTED: §12 holds M3/I8 (the k6 tier is freshness-gated but still nightly/manual in execution, and the Notification app stays pinned `maxReplicas: 1`, `infra/main.bicep:1113`), so this category stays open at maturity 3.** +- [x] **(maturity 3→4 lever) DONE 2026-07-11 (remediation wave 3):** the capacity checks 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 624ms, 4-30x headroom), asserted inside the deploy-gating chromium `e2e-gate` (e2e.yml runs the whole E2E project; conditional since 2026-07-29 per TD-20). **Adjudicated 2026-07-15 (twentieth-cycle re-score): the §23 half was ACCEPTED (scorecard §23 maturity 3→4 on the enforced CWV budgets) but the §12 half was REJECTED: §12 holds M3/I8 (the k6 tier is freshness-gated but still nightly/manual in execution, and the Notification app stays pinned `maxReplicas: 1`, `infra/main.bicep:1113`), so this category stays open at maturity 3.** ### [x] #5 · Vertical Slice Architecture · 3 → 4 (weight 2) · *RESOLVED 2026-06-30 (scorecard §5 maturity 4 / impl 8): the slice-cohesion fitness function is now a confirmed CI merge gate (Optimized process maturity); the deliberate layered-by-project hybrid remains the accepted impl-8 cap* - The deliberate layered-by-project hybrid is accepted; the line is now held by a fitness test that runs in the CI arch gate. @@ -271,7 +271,7 @@ Strong in-app resilience (Polly, SQL retry, outbox, health probes), now with a f ### [x] #23 · Front-End Performance · *RESOLVED 2026-07-15 for MATURITY (twentieth-cycle re-score: scorecard §23 maturity 3→4 CONFIRMED on the enforced CWV budgets inside the deploy-gating chromium `e2e-gate`; implementation holds 8, the code-split/image polish below stays open)* - ~~No Core Web Vitals/RUM~~; WASM not code-split; images unoptimized. -- [x] Add **CWV** tracking → **DONE + GATED (2026-07-11, remediation wave 3):** CWV was measured per E2E run since 2026-06-30 (`WebVitalsTests`); the budgets are now the enforced Core Web Vitals "good" band asserted inside the deploy-gating chromium `e2e-gate` (see the #12 wave-3 note above), closing the "advisory by design" hold from the nineteenth-cycle re-score. **Candidacy CONFIRMED on the 2026-07-15 twentieth-cycle re-score: scorecard §23 maturity 3→4.** +- [x] Add **CWV** tracking → **DONE + GATED (2026-07-11, remediation wave 3):** CWV was measured per E2E run since 2026-06-30 (`WebVitalsTests`); the budgets are now the enforced Core Web Vitals "good" band asserted inside the deploy-gating chromium `e2e-gate` (see the #12 wave-3 note above; conditional since 2026-07-29 per TD-20: a non-UI merge deploys without a CWV assertion), closing the "advisory by design" hold from the nineteenth-cycle re-score. **Candidacy CONFIRMED on the 2026-07-15 twentieth-cycle re-score: scorecard §23 maturity 3→4.** - [ ] *(Impl polish, open)* **code-split** WASM; optimize images. ### [x] #31 · Cost Efficiency / FinOps · 3 → 4 (weight 2) · *RESOLVED 2026-06-30 (scorecard §31 maturity 4 / impl 8): `cost-guard.yml` is now a `workflow_call` reusable workflow invoked as a `cost-guard` job in `deploy.needs`, so a deploy is blocked while a surge is un-reverted (live in `deploy.needs`, `deploy.yml:791`, since 2026-06-30)* @@ -299,16 +299,16 @@ Strong in-app resilience (Polly, SQL retry, outbox, health probes), now with a f 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 scheduled against -each other: the maturity index reached 97.2% while implementation sits at 85.6%. Ranked from the -current scorecard (2026-08-14 twenty-sixth cycle, no score moves): **15 categories, 35 gap points**, -byte-identical to the prior cycle's band. The 35 points are what stands between 85.6% and a full +each other: the maturity index reached 97.2% while implementation sits at 85.0%. Ranked from the +current scorecard (2026-08-23 twenty-seventh cycle, two implementation down-moves): **16 categories, +40 gap points**. §4 entered the band for the first time (impl 9→8) and §22 rose to the joint top +(impl 8→7, joining §15 at implPriority 4). The 40 points are what stands between 85.0% and a full 800; the "90% attainable ceiling" framing used here before 2026-08-01 is retired, since a 10 is now awardable for an almost perfect implementation and the index reads directly against 100%. -Six of these rows were re-proposed for a lift on 2026-08-01 and all six were rejected on evidence -(§5, §13 as a 9→10, §24, §27, §31, §33); the 2026-08-14 cycle re-proposed eight (§5, §7, §12 as an -M3→4, §13 as a 9→10, §15, §23, §28, §31) and rejected all eight, which is why the band did not -move: the work is named and small in most cases, it simply has not shipped. §5 gained its name this -cycle as **TD-19**. +Eight of these rows were re-proposed for a lift on 2026-08-23 and all eight were rejected on +evidence (§5, §7, §15, §17 as a 9→10, §18, §21, §28, §31), the third consecutive all-rejected +cycle: the work is named and small in most cases, it simply has not shipped. §28 gained its name +this cycle as **TD-20**; §22's lever is now named from the down-move basis. Four of these categories (§12, §21, §22, §33) also sit in the maturity band above and keep their existing item there; this band records only their implementation half. Levers are cited only where @@ -317,15 +317,16 @@ invented here. | implPriority | # | Category | w | Impl | Recorded lever | |---|---|---|---|---|---| -| 4 | §15 | Best Practices & Code Quality | 2 | 7 | **The single highest row on this band** (impl 8→7 on 2026-07-28; unchanged 2026-08-01, anchors re-derived). Two hygiene items, effort **S**: (1) delete the expired audit suppression `GHSA-2m69-gcr7-jv3q` (`Directory.Build.props:54`, in the ItemGroup at `:53-55`), whose own comment (`:45-52`, updated 2026-07-20) says it "stays only until the next MMCA.Common release and consumer pin sweep carry the fix through the published package graph, then it must be removed": that condition is met and has strengthened (ADC pins **v1.135.0** at `Directory.Packages.props:139`, fourteen releases past the v1.121.0 SQLite sweep; MMCA.Common removed its own entry and pins the patched bundle **3.0.5** directly at `MMCA.Common/Directory.Packages.props:42`; and ADR-038 already records the accepted-advisory list as empty); (2) justify-or-drop the three undated global `NoWarn` codes CS1591/RMG020/EXTEXP0001 (`:26`, where a fourth code S8970 *is* dated and justified at `:22-25`, which is the standard the other three fail). **Verify with a full-solution package-mode restore, not `CI.slnf`**, since the MAUI graph is exactly what `CI.slnf` omits; if MAUI still needs the entry, narrow it to that project with a fresh dated justification rather than keeping a stale global one. Store carries the identical entry (`MMCA.Store/Directory.Build.props:54`) and should be swept in the same pass. The structural half is **TD-18** below | +| 4 | §15 | Best Practices & Code Quality | 2 | 7 | **Joint-top row on this band** (with §22 since 2026-08-23; impl 8→7 on 2026-07-28, re-verified open 2026-08-23 with all three grounds byte-intact). Two hygiene items, effort **S**: (1) delete the expired audit suppression `GHSA-2m69-gcr7-jv3q` (`Directory.Build.props:54`, in the ItemGroup at `:53-55`), whose own comment (`:45-52`, updated 2026-07-20) says it "stays only until the next MMCA.Common release and consumer pin sweep carry the fix through the published package graph, then it must be removed": that condition is met and has strengthened (ADC pins **v1.160.0** at `Directory.Packages.props:92-110`, twenty-five releases past the v1.121.0 SQLite sweep; the recorded "v1.135.0 at `:139`" was doubly stale; MMCA.Common removed its own entry and pins the patched bundle **3.0.5** directly at `MMCA.Common/Directory.Packages.props:42`; and ADR-038 already records the accepted-advisory list as empty); (2) justify-or-drop the three undated global `NoWarn` codes CS1591/RMG020/EXTEXP0001 (`:26`, where a fourth code S8970 *is* dated and justified at `:22-25`, which is the standard the other three fail). **Verify with a full-solution package-mode restore, not `CI.slnf`**, since the MAUI graph is exactly what `CI.slnf` omits; if MAUI still needs the entry, narrow it to that project with a fresh dated justification rather than keeping a stale global one. Store carries the identical entry (`MMCA.Store/Directory.Build.props:54`) and should be swept in the same pass. The structural half is **TD-18** below | +| 4 | §22 | Responsive & Cross-Browser | 2 | 7 | **Entered the top block 2026-08-23** (impl 8→7). **Lever named from the down-move basis** (replacing "not yet identified"): adopt the rubric's density-options criterion, which has **zero** adoption anywhere in ADC, and complete content reflow on the **17 non-DataGrid table pages**, including the data-dense conference-day surfaces (the DataGrid pages already degrade to card lists). The chromium-only gate remains the maturity half (see the open #22 item above). Effort M | +| 3 | §4 | Domain-Driven Design | 3 | 8 | **Entered the band 2026-08-23** (impl 9→8; maturity 4 holds, so #4 stays in the protect set and this is an implementation-only entrant). Lever not yet identified, name it at the next re-score: the down-move basis (public-setter cross-aggregate navigations, aggregate-external validation of `Event`'s optional fields, `OrganizerContactEmail` as a raw string) is recorded in the scorecard §4 row, but which of those to schedule first is not yet adjudicated. Do not promote the old "Money/Address VOs live in Common" nit into the lever: it was already priced into the prior 9 | | 3 | §7 | Microservices Readiness | 3 | 8 | not yet identified | -| 3 | §18 | UI Architecture & Components | 3 | 8 | **TD-16** under #18 above, **re-measured 2026-08-14**: seven code-behinds sit within 38 lines of the enforced 400-line cap, led by `SessionSelectionDashboard.razor.cs` at **398** (up from 395, so 2 lines of headroom) and `HappeningNow.razor.cs` at **394**. Effort S | +| 3 | §18 | UI Architecture & Components | 3 | 8 | **TD-16** under #18 above, **re-measured 2026-08-23**: EIGHT code-behinds now sit within 38 lines of the enforced 400-line cap (was seven), TWO of them at **398** (`SessionSelectionDashboard.razor.cs` and the newly grown `PublicSessionList.razor.cs`, 367→398), with `ADCHome.razor.cs` (341→380) and `EventDetail.razor.cs` (365→377) also growing since 2026-08-14. Effort S | | 3 | §21 | Accessibility (a11y) | 3 | 8 | not yet identified (the recorded SR-pass lever is the maturity half) | -| 3 | §28 | Front-End Testing & Quality | 3 | 8 | not yet identified | +| 3 | §28 | Front-End Testing & Quality | 3 | 8 | **TD-20** below (named 2026-08-23, replacing "not yet identified"), two halves: (1) ADC has **zero** visual-regression/snapshot tests while the reusable `MarkupSnapshot` helper already ships in `MMCA.Common.Testing.UI` for consumer reuse and is consumed only by Common's own `PrimitivesSnapshotTests` (the workspace's own precedent for a Front-End-Testing 8→9, Common's, was earned by closing exactly this gap); (2) the E2E/axe layer is a CONDITIONAL deploy gate, not a merge gate (TD-20). Effort S-M | | 2 | §5 | Vertical Slice Architecture | 2 | 8 | **TD-19** below (named 2026-08-14, replacing "not yet identified"): the enforced architecture map covers 3 of the 4 modules, so `MMCA.ADC.Notification.Application/.API/.Shared` sit outside every map-driven fitness rule. Effort S | -| 2 | §12 | Performance & Scalability | 2 | 8 | see the open #12 item above (Notification pinned `maxReplicas: 1`, `infra/main.bicep:1616` with its right-sizing rationale in the comment ending `:1614`, anchor refreshed 2026-08-14 from the drifted `:1530`); re-confirmed open this cycle | +| 2 | §12 | Performance & Scalability | 2 | 8 | see the open #12 item above (Notification pinned `maxReplicas: 1`, `infra/main.bicep:1648` with its right-sizing rationale at `:1643-1647`, anchor refreshed 2026-08-23 from the drifted `:1616`); re-confirmed open this cycle | | 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 half) | | 2 | §23 | Front-End Performance | 2 | 8 | the WASM code-split / image sub-item recorded under #23 | | 2 | §24 | Forms, Validation & UX Safety | 2 | 8 | **Two levers named 2026-08-01** (replacing "not yet identified"; the prior bUnit-assertion lever shipped and is CI-gated, and the 8→9 was still rejected because these two criteria are only partially met): (1) **client validation does not mirror the server's cross-field and format rules**, which is the category's first criterion and its first red flag; (2) the **form-level error summary is present on 8 of the 18 MudForm-bearing pages** (19 forms; `ConferenceCategoryDetail.razor` carries two), re-measured 2026-08-14 from the recorded 7-of-15 as the authoring surface grew with the Sponsor pages (`SponsorCreate.razor` shipped the summary, `SponsorDetail.razor` did not), so the six Conference create forms plus SponsorCreate and Identity Profile are done while the rest of the authoring surface is not. The first lever was not re-derived this run and stands as written. Effort M for the pair | | 2 | §25 | Navigation & Information Arch | 2 | 8 | not yet identified | @@ -333,10 +334,11 @@ invented here. | 2 | §33 | Developer Experience & Inner Loop | 2 | 8 | see the open #33 item above. **Basis corrected 2026-08-01:** the Service Bus emulator parity tier is **back on the weekday nightly** (`cross-service-tests.yml:145-147`, `workflow_dispatch` `:26` + cron `'0 6 * * 1-5'` `:31`), so the "dispatch-only" line recorded on 2026-07-28 is superseded. It still rides **no gate**: `continue-on-error: true` at `:150`, and `cross-service-freshness` keys off the `cross-service` job (`:126-129`, gate at `deploy.yml:663`); anchors corrected 2026-08-14. The lever is now the remaining half of **TD-17** (make the tier authoritative, or gate on it) plus the underlying divergence that the AppHost provisions RabbitMQ only | | 1 | §27 | Internationalization (i18n) | 1 | 8 | **DEFERRED 2026-07-28, do not re-propose without new evidence** (it was re-proposed anyway on 2026-08-01 and rejected a **fourth** time, which is the cost this entry exists to prevent; that run also surfaced one new culture-aware-formatting violation, so the evidence moved slightly against the lift). Pseudo-loc breadth: `PseudoLocalizationTests.cs:51-56` covers 3 public pages (public by design, `:31`) of **49** routable `@page` files, re-counted 2026-08-14 (48 excluding the MAUI-only `DeviceSettings.razor`; the recorded **37** was stale, so the denominator moved further against the lift). Proposed and rejected in **four** cycles (21st, 22nd, 24th, 25th; the 23rd rejected §12/§21, not this) on byte-identical evidence: the tier is untouched since `c5e6f653` on 2026-07-11 and no `.resx` has landed since 2026-07-20. Worth 1 weighted point of 800 against authenticated-login plumbing plus expansion assertions on roughly 45 pages, the weakest cost-to-benefit ratio on either band. Re-open triggers and full rationale in Deliberate / accepted below | -**Tactical sub-items on this band** (§15 and §5 have no maturity-band item to nest under: both score maturity 4 and sit in the protect list, so their `TD-NN` items live here with their rows): +**Tactical sub-items on this band** (§15, §5 and §28 have no maturity-band item to nest under: all three score maturity 4 and sit in the protect list, so their `TD-NN` items live here with their rows): - [ ] **TD-18** (recorded 2026-07-28, under §15, effort **L**) · **the MAUI app is outside every CI build and outside the CI-audited dependency graph.** `MMCA.ADC.CI.slnf:25` lists only `UI.Web` and `UI.Web.Client`; no workflow installs the `maui-android` workload, so `MMCA.ADC.UI` is never compiled in CI and its analyzers, `TreatWarningsAsErrors` and its own `NoWarn CA5392` (`Source/Hosts/UI/MMCA.ADC.UI/MMCA.ADC.UI.csproj:143`, its comment at `:142`; anchor refreshed 2026-08-14 from the drifted `:131`) are review-enforced only. The gating vulnerable-package scan runs against `CI.slnf` too (`deploy.yml:319`, exit at `:328`; anchor refreshed 2026-08-01 from the drifted `:288`), so the MAUI graph that the `Directory.Build.props:8-12` `System.Private.Uri` suppressions exist for is the one graph never audited. This is what caps §15 at implementation 8 even after the two effort-S hygiene items land. **Blocker (and why this is recorded, not scheduled):** adding a MAUI leg means installing the `maui-android` workload on a runner, which is a multi-minute install on every run and cuts directly against the deliberate 2026-07-18 Actions-minute reduction that also unscheduled the emulator tier (TD-17) and cut the E2E gate to chromium (#22). A cheaper partial is auditing the MAUI graph alone (`dotnet list package --vulnerable` over that project, no build), which would close the supply-chain half without the workload cost. **Resolution path:** either the cheap audit-only step, or a scheduled (not per-PR) MAUI build leg; then re-propose §15 impl 8→9. Do not describe §15's maturity-4 enforcement as repo-wide while this is open: it is `CI.slnf`-wide. - [ ] **TD-19** (recorded 2026-08-14, under §5, effort **S**) · **the enforced architecture map covers 3 of the 4 modules.** `AdcArchitectureMap.DefineLayers()` declares Framework + Identity + Conference + Engagement only and carries no `Module("Notification", ...)` entry at all (`Tests/Architecture/MMCA.ADC.Architecture.Tests/AdcArchitectureMap.cs:12-43`; its doc comment at `:4-5` names only Identity, Conference and Engagement), so `MMCA.ADC.Notification.Application` / `.API` / `.Shared` sit outside every map-driven fitness rule (slice cohesion, layer dependency, transport-at-the-edge) even though all three build in the CI gate (`MMCA.ADC.CI.slnf:30-32`). This is the same omission the 2026-08-01 and 2026-08-14 §5 8→9 rejections cited, and it is the named lever for that row. **Blocker:** none, this is scheduled work. **Resolution path:** add the Notification module entries to the map (Application, API and Shared anchors, mirroring the Identity/Conference/Engagement blocks) and fix whatever the rules then catch. **Effort:** S. The deliberate layered-by-project hybrid stays the accepted impl-8 cap and does not cover this: an enforcement-coverage gap is not an accepted trade-off. +- [ ] **TD-20** (recorded 2026-08-23, under §28, effort **S-M**) · **the deploy-gating chromium E2E/axe/CWV suite is CONDITIONAL, not unconditional.** `e2e-gate` runs only when the diff is UI-affecting (`if: github.event_name != 'pull_request' && needs.changes.outputs.ui == 'true'`, `deploy.yml:538`, rationale `:533-537`, the `ui` output described at `:53-55`), and the `deploy` job explicitly accepts a skipped gate (`needs.e2e-gate.result == 'success' || needs.e2e-gate.result == 'skipped'`, `:896`, comment `:880-883`). A backend-only, infra-only or script-only merge therefore reaches production with **no browser run at all**: no chromium E2E, no axe scan, no Core Web Vitals assertion. Until this cycle the conditionality was unrecorded in ADC governance (no hit for the `ui` scoping anywhere in the ledger or scorecard), while several entries called the gate unconditional; that language is now qualified in place (#12/#21/#23/#28). **Blocker (deliberate):** the 2026-07-29 Actions-minute saving; the workflow names the post-deploy smoke gate as the intended backstop (`deploy.yml:535-537`). **Resolution path:** either add a cheap UI-independent smoke leg that always runs, or accept and record the conditionality permanently; in both cases keep the ledger's gate claims accurate. **Effort:** S-M. Paired with the Deliberate / accepted amendment below; this also names §28's implementation-band lever (with the zero-visual-regression half in its row above). ## 🟢 Resolved 2026-07-25 (performance program 2) @@ -369,18 +371,18 @@ Conscious, recorded choices, not pending work (the former `TECHDEBT.md` accepted - **#1 SOLID (`AuthenticationService` 7-ctor-dependency cohesive auth facade**) accepted as-is; the ctor-count fitness threshold (`ConstructorDependencyCountTests`, ≤7) is now landed on the v1.86.0 sweep, so #1 is closed (scorecard §1 stays M4/I9). - **#5 Vertical Slice, deliberate layered-by-project hybrid:** cross-cutting handled in the decorator pipeline; the hybrid is the accepted choice that caps implementation at 8, and the slice-cohesion line is held by a CI-gated fitness test (`SliceCohesionTests`, in `MMCA.ADC.CI.slnf`) that lifted scorecard §5 to maturity 4 (#5 closed, scorecard §5 M4/I8). **Scoping clause (2026-08-14):** this accepted hybrid is the impl-8 cap, and it does **not** absorb **TD-19**. The absence of `MMCA.ADC.Notification.*` from `AdcArchitectureMap.cs:12-43` is an enforcement-coverage gap and schedulable work, not an accepted trade-off. - **#20 Design System Common-side residuals:** accepted as out-of-ADC-scope: `BrandColorTokenTests` guards the Primary token only (Secondary has no drift test), and a few `!important` overrides + Store-specific cart CSS live in MMCA.Common's shared `app.css`. These are MMCA.Common changes, not ADC-local; ADC's §20 is maturity 4 / impl 9. -- **Chromium-only deploy E2E gate (recorded 2026-07-18, CI-minute reduction; amended 2026-08-01):** `deploy.yml`'s `e2e-gate` invokes one browser leg instead of three (the job is at `deploy.yml:531` with `browsers: '["chromium"]'` at `:541`; anchors refreshed 2026-08-01 from the drifted `:500` / `:505`, substance re-confirmed and the job is still in `deploy.needs` at `:866`); firefox/webkit cross-engine coverage moved to the nightly `e2e.yml`, where `continue-on-error` (`e2e.yml:144`, refreshed from `:131`) keeps them advisory. **Amendment (2026-07-29, recorded here 2026-08-01): the nightly was thinned again to ALTERNATING single-engine legs.** Two separate crons now run Monday firefox and Thursday webkit (`e2e.yml:49,:50`, rationale `:44-48`, the leg chosen from the cron string that fired), so each non-chromium engine is verified **once a week** instead of both engines twice a week. This is a further deliberate CI-minute choice, recorded with the same shape as the parent entry; the earlier "Mon/Thu nightly matrix" phrasing used here and under #22 implied both engines on both nights and has been corrected. Scoring consequence: none beyond the existing one, since §22 already sits at M3/I8 on the chromium-only gate. The alternating schedule makes the nightly **signal** thinner, not the gate weaker. Recorded as a deliberate cost choice, with its scoring consequence stated plainly: it costs §22 its maturity 4, so the category reopens at M3/I8 (see #22). This is a trade-off, not a closure; option (b) under #22 (a `cross-browser-freshness` gate) would recover the maturity without restoring the runner minutes. +- **Chromium-only deploy E2E gate (recorded 2026-07-18, CI-minute reduction; amended 2026-08-01):** `deploy.yml`'s `e2e-gate` invokes one browser leg instead of three (the job is at `deploy.yml:531` with `browsers: '["chromium"]'` at `:541`; anchors refreshed 2026-08-01 from the drifted `:500` / `:505`, substance re-confirmed and the job is still in `deploy.needs` at `:866`); firefox/webkit cross-engine coverage moved to the nightly `e2e.yml`, where `continue-on-error` (`e2e.yml:144`, refreshed from `:131`) keeps them advisory. **Amendment (2026-07-29, recorded here 2026-08-01): the nightly was thinned again to ALTERNATING single-engine legs.** Two separate crons now run Monday firefox and Thursday webkit (`e2e.yml:49,:50`, rationale `:44-48`, the leg chosen from the cron string that fired), so each non-chromium engine is verified **once a week** instead of both engines twice a week. This is a further deliberate CI-minute choice, recorded with the same shape as the parent entry; the earlier "Mon/Thu nightly matrix" phrasing used here and under #22 implied both engines on both nights and has been corrected. Scoring consequence: none beyond the existing one, since §22 already sits at M3/I8 on the chromium-only gate. The alternating schedule makes the nightly **signal** thinner, not the gate weaker. Recorded as a deliberate cost choice, with its scoring consequence stated plainly: it costs §22 its maturity 4, so the category reopens at maturity 3 (see #22; implementation dropped separately to 7 on 2026-08-23 on the density/reflow gaps). This is a trade-off, not a closure; option (b) under #22 (a `cross-browser-freshness` gate) would recover the maturity without restoring the runner minutes. **Second amendment (2026-07-29 change, recorded here 2026-08-23): the gate is now also CONDITIONAL on the change set.** `e2e-gate` runs only when the `changes` job's `ui` output is true (`deploy.yml:538`, rationale `:533-537`, output described `:53-55`) and `deploy` treats a skipped gate as pass (`:896`, comment `:880-883`), so backend-only, infra-only and script-only merges deploy with **no browser, axe or CWV run at all**; the workflow names the post-deploy smoke gate as the accepted backstop. Same shape as the parent entry: a deliberate Actions-minute trade-off with its consequence stated plainly, paired with **TD-20** as the work that would restore an unconditional signal. - **Freshness-gate break-glass:** the three recency gates (`dr-freshness`, `load-freshness`, `cross-service-freshness`) each accept a `skip_freshness_gates` workflow_dispatch input with a required justification (declared at `deploy.yml:13-18`, with the per-gate checks at `:562`, `:619`, `:678`; anchors refreshed 2026-08-01 from the drifted `:526,583,642`, substance re-confirmed, and the gate jobs themselves are `dr-freshness :549`, `load-freshness :606`, `cross-service-freshness :663` alongside `cost-guard :519`), so every one of those proofs is bypassable by an operator. Recorded as an accepted escape hatch; it slightly qualifies the "enforced deploy precondition" language used under #6, #12, #29, and #33. - **Service Bus emulator smoke is advisory by design (recorded 2026-07-24 as "unscheduled", reconciled 2026-07-28, REWRITTEN 2026-08-01 because the code now says the opposite):** the §33 broker-parity tier was cut to dispatch-only on 2026-07-24, and it was **restored to the weekday nightly on 2026-07-29** (`cross-service-tests.yml:145` for the job, `needs: should-run` at `:146` + `if: needs.should-run.outputs.run == 'true'` at `:147`, under `workflow_dispatch` `:26` + `cron: '0 6 * * 1-5'` at `:31`, `timeout-minutes: 10` at `:149`; anchors corrected 2026-08-14). The RESCHEDULED comment at `:130-143` also records a **different root cause** than the 2026-07-24 entry claimed: per-test bus re-provisioning against an admin plane throttled at roughly 1 op/sec (`IAsyncLifetime` plus xUnit per-`[Fact]` class instantiation), fixed by hoisting the bus to the collection fixture and wall-clock bounding both startup phases. The "floating companion SQL image" blocker text is withdrawn, and the "no schedule / dispatch-only" framing is deleted. **What survives as the deliberate choice:** the tier is `continue-on-error: true` (`:150`) and advisory by design, and nothing gates on it, since `cross-service-freshness` keys off the `cross-service` job (`:126-129`, gate at `deploy.yml:663`). So §33 still holds M3/I8 on the no-gate half alone, and this tier must **not** be described as gating. Paired with **TD-17** (now half closed), which is the work that would make it authoritative. Same shape as the chromium-only entry above: a trade-off, not a closure. - **Pseudo-localization breadth DEFERRED (§27, recorded 2026-07-28 after a third rejection):** the §27 implementation 8→9 lever, broadening `PseudoLocalizationTests` beyond its three public pages (`PseudoLocalizationTests.cs:51`, public by design per `:31`) across the authenticated authoring surface, is adjudicated **deferred rather than open**. Rationale stated plainly: it is worth **1 weighted point of 800** (weight 1, one implementation rung) and costs authenticated-login plumbing plus text-expansion and overflow assertions across roughly 45 of the 49 routable `@page` files (48 excluding the MAUI-only `DeviceSettings.razor`; figures re-counted 2026-08-14 from the stale 34-of-37), the weakest cost-to-benefit ratio on either band. The identical proposal has now been adversarially rejected in **four** cycles (21st, 22nd, 24th, 25th) against byte-identical evidence: the tier is untouched since `c5e6f653` (2026-07-11) and no `.resx` has landed since 2026-07-20, so each cycle re-spent an adversarial verify pass to reach the same conclusion. The 2026-08-01 pass additionally found a citable culture-aware-formatting violation that was not previously recorded, so the fresh evidence points **away** from the lift, not toward it. §27 keeps its implementation-band row at implPriority 1 (band membership is numeric, `implementation <= 8`), but the lever is not to be re-proposed without new evidence. **Re-open triggers:** a second locale beyond `es`, any RTL locale, or a reported layout regression on an authenticated page. Maturity 4 is unaffected and remains doubly CI-gated (`TranslationCompletenessTests` + `LocalizedTextConventionTests`, both in `MMCA.ADC.CI.slnf:58`, run at `deploy.yml:219`; anchor refreshed 2026-08-01 from the drifted `:194`). - **BR-130 room double-booking overlap check accepted as a SOFT guard (recorded 2026-08-01, BugHunt M42):** `SessionRoomScheduling.ValidateRoomAssignmentAsync` is a read-then-write advisory check with no transaction, lock, or DB exclusion constraint tying check to write, so two concurrent organizer writes for the same room with overlapping windows can both pass. Accepted rather than hardened, with each alternative rejected on evidence at the 2026-08-01 BugHunt verification: a transactional re-check cannot close the race below SERIALIZABLE (and with no index on `(RoomId, StartsAt)` that isolation escalates to key-range/table locks across the Sessionize import path); `IDistributedLock`'s own contract forbids sole-guard use on a correctness invariant and it silently degrades to the in-process implementation when Redis is absent while Conference scales to `maxReplicas: 2`; `sp_getapplock` needs an EF/SqlClient reference the Application layer forbids (the same layering constraint that produced CreateSessionHandler's message-based collision detection). The spec's BR-130 text promises only the cross-event room check (422); the overlap guard is code-only, organizer-gated, milliseconds wide at the 2026 load, and a double-booking is repairable by editing either session. Documented in the class XML doc plus a SOFT note at the `ExistsAsync` call (ADC PR #94; same shape as the BR-231 soft-cap precedent). No scoring consequence claimed. **Re-open triggers:** organizer concurrency materially above today's handful, a real double-booking incident, or a cheap DB-level range-exclusion capability appearing. -- **FLAG re-checks:** This re-score's (v1.93.0 sweep) only FLAG is **§7** (M4/I8, in protect): a proposed impl 8→9 lift was adversarially rejected, the bidirectional Conference↔Engagement gRPC pair caps it in the Strong band, so it is a verified non-move. The prior 2026-06-29 re-score's other re-checks have since settled: **§5** was lifted to M4 on the v1.93.0 sweep (slice-cohesion CI gate, no longer flagged), while **§25** (M4/I8, closed; route-auth fitness tests CI-gated) and **§13** (M3/I8, open under Priority 2) are now plain CONFIRMED. A FLAG is a verified non-move, not a closure. **Update (2026-07-03 full re-score):** all 34 categories returned CONFIRMED with no new FLAGs; **§7** remains the standing verified non-move (M4/I8: the bidirectional Conference↔Engagement gRPC pair caps it in the Strong band), and the §24 impl 9→7 recalibration is a tracked substance gap (TD-14), not an accepted trade-off, so it does not enter this section. **Update (2026-07-10 nineteenth-cycle full re-score):** the FLAG set shifted. **§7 returns plain CONFIRMED** (M4/I8, no longer flagged; the bidirectional gRPC pair is a settled cap). The three verified non-moves this cycle are: **§12** (M3/I8: a proposed impl 8→9 was adversarially rejected because the Notification app stays pinned `maxReplicas: 1`, `infra/main.bicep:1113`, while the backplane key is injected at `:1056`; the stale no-backplane bicep comment was corrected this cycle), **§23** (M3/I8: a proposed maturity 3→4 was rejected; the WebVitals budgets are advisory by design, no §23 fitness gate exists, and the k6/vitals tiers run nightly/dispatch, not as a merge gate), and **§34** (M4/I9: a proposed impl 9→8 downgrade was rejected as unsupported; the untracked workspace-root `ArchitecturalAnalysis.md` remains the already-weighed 9-not-10 lever). Each is a verified non-move (score held), not a closure. **Update (2026-07-15 twentieth-cycle full re-score):** the FLAG set shifted again. **§12 returns plain CONFIRMED** (M3/I8, no longer flagged) and **§23 exits as a lift** (maturity 3→4 on the now-enforced CWV budgets, superseding its nineteenth-cycle rejection). This cycle's adversarial adjudications: **§19** (a first-pass impl 9→8 downgrade was rejected as unsupported while the maturity 3→4 lift was confirmed, so §19 closes at M4/I9), **§28** (M4/I8 verified non-move, but its row carried a materially false claim now corrected in place: E2E #5 is re-quarantined at `SpeakerSelfServiceTests.cs:57`, not "un-skipped/active", plus three drifted line anchors), **§33** (M3/I8: a proposed impl 8→9 was rejected on the open broker-parity red flag, `README.md:74`), and **§34** (M4/I9: the identical impl 9→8 downgrade re-proposed and re-rejected). Each non-move is a held score, not a closure. **Update (2026-07-17 twenty-first-cycle full re-score):** one FLAG this cycle: **§27** (M4/I8 verified non-move: the recorded impl 8→9 candidacy, extending the pseudo-loc text-expansion evidence to ADC pages, was adversarially rejected because `PseudoLocalizationTests.cs:51` covers only 3 public pages of 30+ routable pages, a partial extension; §27 stays in the protect set at its held score). §12 and §33 return plain CONFIRMED at M3/I8 (their twentieth-cycle adjudications re-derived from fresh evidence, including the `load-freshness` gate and the Service Bus emulator tier, neither sufficient for a move). A FLAG is a held score, not a closure. **Update (2026-07-21 twenty-second-cycle full re-score):** the single FLAG is again **§27** (M4/I8): the identical impl 8→9 pseudo-loc candidacy was re-proposed and re-rejected on unchanged evidence (`PseudoLocalizationTests.cs:51` covers exactly 3 public pages against 36 routable pages), so it stays a verified non-move in the protect set. The §33 sentence in earlier updates that quoted `README.md:74` is superseded: that admission no longer exists in the file (see the #33 header for the rewritten basis). **Update (2026-07-23 twenty-third-cycle full re-score):** the FLAG set shifted: **§27 returns plain CONFIRMED** (M4/I8, in the protect set; the impl 8→9 pseudo-loc candidacy was not re-proposed this cycle). The two verified non-moves are **§12** (M3/I8: a proposed maturity 3→4 was adversarially rejected because the k6 capacity proof executes monthly/dispatch out of band with `load-freshness` a recency-only check, `deploy.yml:553`, and Notification stays pinned `maxReplicas: 1`, `infra/main.bicep:1424`) and **§21** (M3/I8: a proposed maturity 3→4 was rejected because the manual screen-reader pass is still unrecorded in `ACCESSIBILITY-SCREENREADER-PASS.md`, the cheapest maturity 3→4 lever). §22 and §33 are plain CONFIRMED at M3/I8. A FLAG is a held score, not a closure. **Update (2026-07-28 twenty-fourth-cycle full re-score, pin v1.131.0, HEAD `2ec77796`):** one score moved, and it moved **down**. **§15 Best Practices & Code Quality implementation 8→7** (weight 2), which takes it to the top of the implementation band at implPriority 4; maturity holds 4, so #15 stays in the protect set and the maturity band is unchanged. **A down-move is not a FLAG**: it is a CONFIRMED move, adversarially verified, on three gaps read fresh this run (an audit suppression expired by its own written removal condition, three undated global `NoWarn` codes, and the MAUI project outside every CI build and outside the CI-audited graph). The single FLAG this cycle is **§27** (M4/I8 verified non-move): a first pass proposed impl 8→9 for the third time and the adversarial pass rejected it on byte-identical evidence, correcting the score back to the prior values. Because the corrected values equal the prior ones, all 34 categories are evidence-backed this run even though the indices are labeled "33 rescored + 1 prior". That lever is now adjudicated **DEFERRED** with its cost and re-open triggers recorded above, so it should not return as a candidacy. §12/§21/§22/§33 return plain CONFIRMED at M3/I8, and the #33 re-confirmation rests on a **weaker** basis than last cycle (its parity tier is now dispatch-only, TD-17). Also re-rejected: #24 impl 8→9 and #33 M3→4 / I8→9; #6 and #30 sit at I9, already at the scheduling target of 9, so their recorded "9→10" candidacies are out of scope for both bands. **Update (2026-08-01 twenty-fifth-cycle full re-score, pin v1.135.0, HEAD `995a7886`):** no score moved on either axis, and this cycle produced the largest FLAG set yet: **six**, every one of them a proposed implementation lift, every one **rejected** against current source. **§5 8→9 rejected** (the rubric's first §5 criterion wants the DTO in the slice; ADC's live in the Shared assembly with horizontal mapper/validation/specification folders, the layered-by-project hybrid is unchanged, and `AdcArchitectureMap.cs:12-44` omits `MMCA.ADC.Notification.Application`, so enforcement covers 3 of 4 modules). **§13 9→10 rejected** (three ENABLED production alerts have no runbook triage section and sit outside the pairing gate's scope, including the sev-1 gateway-availability alert at `infra/main.bicep:481`; that is an unmet criterion, not the trivial polish the recalibrated top rung allows). **§24 8→9 rejected** (the named bUnit lever shipped and is CI-gated, but client validation does not mirror the server's cross-field and format rules and the error summary reaches 7 of 15 MudForm forms; both are now named as §24's levers). **§27 8→9 rejected a fourth time** on byte-identical evidence plus one new culture-formatting violation. **§31 8→9 rejected** (the surge/revert automation is not pulled). **§33 8→9 rejected a second time** (the AppHost provisions RabbitMQ only, and the restored Service Bus nightly is `continue-on-error` and gates nothing). Six rejections and zero moves is not a stalled cycle: it is six categories each sitting **one criterion** short, with the criterion now named in the band for five of them (§7, §16, §21, §22, §25, §28 remain "lever not yet identified"). A FLAG is a held score, not a closure, and none of these six changed band membership. **Update (2026-08-14 twenty-sixth-cycle full re-score, pin v1.152.0, HEAD `19021d93`):** no score moved on either axis and the FLAG set grew to **eight**, every one a proposed lift, every one **rejected** against current source. **§5 8→9 rejected** (the DTO-in-the-slice criterion is still unmet and `AdcArchitectureMap.cs:12-43` still omits the Notification module: that half is now named as **TD-19**). **§7 8→9 rejected** (the bidirectional sync-gRPC red flag did not close, it broadened to a second pair, Identity-Notification, across 7 sync client registrations in 4 services). **§12 M3→4 rejected** (zero commits touched `load-test.yml`, `deploy.yml` or `Tests/Load/` since the prior cycle's HEAD, so the out-of-band capacity proof behind a recency-only gate is byte-for-byte intact). **§13 9→10 rejected a second time** (three ENABLED production alerts still carry no runbook triage section, including the sev-1 gateway-availability alert, anchor refreshed `infra/main.bicep:481`→`:496-502`; §13 sits at I9, outside both bands). **§15 7→8 rejected** (all three downgrade grounds intact, and the expired SQLite suppression is further past its own removal condition now that ADC pins v1.152.0). **§23 8→9 rejected** (WASM code-split and image optimization, the category's own named lever, are both still open). **§28 8→9 rejected** (the genuine new state-management bUnit coverage is a within-band improvement, not a band change). **§31 8→9 rejected a second time** (the conference-day surge is still manual with a manual reset instruction and no automated revert). A FLAG is a held score, not a closure, and none of these eight changed band membership. +- **FLAG re-checks:** This re-score's (v1.93.0 sweep) only FLAG is **§7** (M4/I8, in protect): a proposed impl 8→9 lift was adversarially rejected, the bidirectional Conference↔Engagement gRPC pair caps it in the Strong band, so it is a verified non-move. The prior 2026-06-29 re-score's other re-checks have since settled: **§5** was lifted to M4 on the v1.93.0 sweep (slice-cohesion CI gate, no longer flagged), while **§25** (M4/I8, closed; route-auth fitness tests CI-gated) and **§13** (M3/I8, open under Priority 2) are now plain CONFIRMED. A FLAG is a verified non-move, not a closure. **Update (2026-07-03 full re-score):** all 34 categories returned CONFIRMED with no new FLAGs; **§7** remains the standing verified non-move (M4/I8: the bidirectional Conference↔Engagement gRPC pair caps it in the Strong band), and the §24 impl 9→7 recalibration is a tracked substance gap (TD-14), not an accepted trade-off, so it does not enter this section. **Update (2026-07-10 nineteenth-cycle full re-score):** the FLAG set shifted. **§7 returns plain CONFIRMED** (M4/I8, no longer flagged; the bidirectional gRPC pair is a settled cap). The three verified non-moves this cycle are: **§12** (M3/I8: a proposed impl 8→9 was adversarially rejected because the Notification app stays pinned `maxReplicas: 1`, `infra/main.bicep:1113`, while the backplane key is injected at `:1056`; the stale no-backplane bicep comment was corrected this cycle), **§23** (M3/I8: a proposed maturity 3→4 was rejected; the WebVitals budgets are advisory by design, no §23 fitness gate exists, and the k6/vitals tiers run nightly/dispatch, not as a merge gate), and **§34** (M4/I9: a proposed impl 9→8 downgrade was rejected as unsupported; the untracked workspace-root `ArchitecturalAnalysis.md` remains the already-weighed 9-not-10 lever). Each is a verified non-move (score held), not a closure. **Update (2026-07-15 twentieth-cycle full re-score):** the FLAG set shifted again. **§12 returns plain CONFIRMED** (M3/I8, no longer flagged) and **§23 exits as a lift** (maturity 3→4 on the now-enforced CWV budgets, superseding its nineteenth-cycle rejection). This cycle's adversarial adjudications: **§19** (a first-pass impl 9→8 downgrade was rejected as unsupported while the maturity 3→4 lift was confirmed, so §19 closes at M4/I9), **§28** (M4/I8 verified non-move, but its row carried a materially false claim now corrected in place: E2E #5 is re-quarantined at `SpeakerSelfServiceTests.cs:57`, not "un-skipped/active", plus three drifted line anchors), **§33** (M3/I8: a proposed impl 8→9 was rejected on the open broker-parity red flag, `README.md:74`), and **§34** (M4/I9: the identical impl 9→8 downgrade re-proposed and re-rejected). Each non-move is a held score, not a closure. **Update (2026-07-17 twenty-first-cycle full re-score):** one FLAG this cycle: **§27** (M4/I8 verified non-move: the recorded impl 8→9 candidacy, extending the pseudo-loc text-expansion evidence to ADC pages, was adversarially rejected because `PseudoLocalizationTests.cs:51` covers only 3 public pages of 30+ routable pages, a partial extension; §27 stays in the protect set at its held score). §12 and §33 return plain CONFIRMED at M3/I8 (their twentieth-cycle adjudications re-derived from fresh evidence, including the `load-freshness` gate and the Service Bus emulator tier, neither sufficient for a move). A FLAG is a held score, not a closure. **Update (2026-07-21 twenty-second-cycle full re-score):** the single FLAG is again **§27** (M4/I8): the identical impl 8→9 pseudo-loc candidacy was re-proposed and re-rejected on unchanged evidence (`PseudoLocalizationTests.cs:51` covers exactly 3 public pages against 36 routable pages), so it stays a verified non-move in the protect set. The §33 sentence in earlier updates that quoted `README.md:74` is superseded: that admission no longer exists in the file (see the #33 header for the rewritten basis). **Update (2026-07-23 twenty-third-cycle full re-score):** the FLAG set shifted: **§27 returns plain CONFIRMED** (M4/I8, in the protect set; the impl 8→9 pseudo-loc candidacy was not re-proposed this cycle). The two verified non-moves are **§12** (M3/I8: a proposed maturity 3→4 was adversarially rejected because the k6 capacity proof executes monthly/dispatch out of band with `load-freshness` a recency-only check, `deploy.yml:553`, and Notification stays pinned `maxReplicas: 1`, `infra/main.bicep:1424`) and **§21** (M3/I8: a proposed maturity 3→4 was rejected because the manual screen-reader pass is still unrecorded in `ACCESSIBILITY-SCREENREADER-PASS.md`, the cheapest maturity 3→4 lever). §22 and §33 are plain CONFIRMED at M3/I8. A FLAG is a held score, not a closure. **Update (2026-07-28 twenty-fourth-cycle full re-score, pin v1.131.0, HEAD `2ec77796`):** one score moved, and it moved **down**. **§15 Best Practices & Code Quality implementation 8→7** (weight 2), which takes it to the top of the implementation band at implPriority 4; maturity holds 4, so #15 stays in the protect set and the maturity band is unchanged. **A down-move is not a FLAG**: it is a CONFIRMED move, adversarially verified, on three gaps read fresh this run (an audit suppression expired by its own written removal condition, three undated global `NoWarn` codes, and the MAUI project outside every CI build and outside the CI-audited graph). The single FLAG this cycle is **§27** (M4/I8 verified non-move): a first pass proposed impl 8→9 for the third time and the adversarial pass rejected it on byte-identical evidence, correcting the score back to the prior values. Because the corrected values equal the prior ones, all 34 categories are evidence-backed this run even though the indices are labeled "33 rescored + 1 prior". That lever is now adjudicated **DEFERRED** with its cost and re-open triggers recorded above, so it should not return as a candidacy. §12/§21/§22/§33 return plain CONFIRMED at M3/I8, and the #33 re-confirmation rests on a **weaker** basis than last cycle (its parity tier is now dispatch-only, TD-17). Also re-rejected: #24 impl 8→9 and #33 M3→4 / I8→9; #6 and #30 sit at I9, already at the scheduling target of 9, so their recorded "9→10" candidacies are out of scope for both bands. **Update (2026-08-01 twenty-fifth-cycle full re-score, pin v1.135.0, HEAD `995a7886`):** no score moved on either axis, and this cycle produced the largest FLAG set yet: **six**, every one of them a proposed implementation lift, every one **rejected** against current source. **§5 8→9 rejected** (the rubric's first §5 criterion wants the DTO in the slice; ADC's live in the Shared assembly with horizontal mapper/validation/specification folders, the layered-by-project hybrid is unchanged, and `AdcArchitectureMap.cs:12-44` omits `MMCA.ADC.Notification.Application`, so enforcement covers 3 of 4 modules). **§13 9→10 rejected** (three ENABLED production alerts have no runbook triage section and sit outside the pairing gate's scope, including the sev-1 gateway-availability alert at `infra/main.bicep:481`; that is an unmet criterion, not the trivial polish the recalibrated top rung allows). **§24 8→9 rejected** (the named bUnit lever shipped and is CI-gated, but client validation does not mirror the server's cross-field and format rules and the error summary reaches 7 of 15 MudForm forms; both are now named as §24's levers). **§27 8→9 rejected a fourth time** on byte-identical evidence plus one new culture-formatting violation. **§31 8→9 rejected** (the surge/revert automation is not pulled). **§33 8→9 rejected a second time** (the AppHost provisions RabbitMQ only, and the restored Service Bus nightly is `continue-on-error` and gates nothing). Six rejections and zero moves is not a stalled cycle: it is six categories each sitting **one criterion** short, with the criterion now named in the band for five of them (§7, §16, §21, §22, §25, §28 remain "lever not yet identified"). A FLAG is a held score, not a closure, and none of these six changed band membership. **Update (2026-08-14 twenty-sixth-cycle full re-score, pin v1.152.0, HEAD `19021d93`):** no score moved on either axis and the FLAG set grew to **eight**, every one a proposed lift, every one **rejected** against current source. **§5 8→9 rejected** (the DTO-in-the-slice criterion is still unmet and `AdcArchitectureMap.cs:12-43` still omits the Notification module: that half is now named as **TD-19**). **§7 8→9 rejected** (the bidirectional sync-gRPC red flag did not close, it broadened to a second pair, Identity-Notification, across 7 sync client registrations in 4 services). **§12 M3→4 rejected** (zero commits touched `load-test.yml`, `deploy.yml` or `Tests/Load/` since the prior cycle's HEAD, so the out-of-band capacity proof behind a recency-only gate is byte-for-byte intact). **§13 9→10 rejected a second time** (three ENABLED production alerts still carry no runbook triage section, including the sev-1 gateway-availability alert, anchor refreshed `infra/main.bicep:481`→`:496-502`; §13 sits at I9, outside both bands). **§15 7→8 rejected** (all three downgrade grounds intact, and the expired SQLite suppression is further past its own removal condition now that ADC pins v1.152.0). **§23 8→9 rejected** (WASM code-split and image optimization, the category's own named lever, are both still open). **§28 8→9 rejected** (the genuine new state-management bUnit coverage is a within-band improvement, not a band change). **§31 8→9 rejected a second time** (the conference-day surge is still manual with a manual reset instruction and no automated revert). A FLAG is a held score, not a closure, and none of these eight changed band membership. **Update (2026-08-23 twenty-seventh-cycle full re-score, pin v1.160.0, HEAD `96f0919a`):** two scores moved, both **down**, both CONFIRMED moves adversarially verified rather than FLAGs: **§4 implementation 9→8** (public-setter cross-aggregate navigations, aggregate-external validation of Event's optional fields, primitive obsession on `OrganizerContactEmail`; the prior row's citations had all drifted and the fresh read placed the substance in the Strong band) and **§22 implementation 8→7** (zero density-option adoption plus partial content reflow on the 17 non-DataGrid table pages, which names the lever this band had carried as "not yet identified"). The FLAG set held at **eight**, every one a proposed lift, every one **rejected**: **§5 8→9 rejected a third time** (DTOs and horizontal validators still outside the slice, the enforced validator rule exempting exactly the population that exists, `ArchitectureRules.Slices.cs:38-39`; the forgot-password vertical is fresh proof the hybrid still edits switchboards; TD-19 still open). **§7 8→9 rejected** (the synchronous-coupling red flag broadened rather than closed). **§15 7→8 rejected a second time** (all three downgrade grounds byte-intact; the expired suppression now twenty-five releases past its removal condition). **§17 9→10 rejected** (no CI/CD substance changed since the prior basis commit; the SQL public-network-access cap is verbatim open; the tightened 3d/keep-3 ACR purge narrows the rollback image window rather than widening it). **§18 8→9 rejected** (the cap-pressure gap WIDENED: eight code-behinds within 38 lines, two at 398; TD-16). **§21 M3→4 and I8→9 both rejected** (the SR-pass placeholder is still empty at `adc-ACCESSIBILITY-SCREENREADER-PASS.md:62`, and four routable pages shipped 2026-08-19 with no axe coverage: a new gap, not a lift). **§28 8→9 rejected** (zero visual-regression tests with the shared `MarkupSnapshot` helper unused, and the E2E layer is a conditional deploy gate, not a merge gate: named as **TD-20**). **§31 8→9 rejected a third time** (`cost-guard.yml` byte-unchanged since caf31e09; the surge is still a manual play with a manual reset). A FLAG is a held score, not a closure; the only band-membership changes this cycle came from the two confirmed down-moves. --- ## ✅ Already at level 4: protect, don't regress #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 Architecture · #9 API & Contract Design · #10 Cross-Cutting Concerns · #11 Security · #13 Observability & Operability · #14 Testability & Test Strategy · #15 Best Practices & Code Quality · #16 Maintainability & Evolvability · #17 DevOps & Deployment · #18 UI Architecture & Components · #19 State Management & Data Flow · #20 Design System · #23 Front-End Performance · #24 Forms & UX Safety · #25 Navigation & Information Arch · #26 Front-End Security · #27 Internationalization · #28 Front-End Testing & Quality · #29 Resilience & Business Continuity · #30 Compliance & Privacy · #31 Cost Efficiency / FinOps · #32 Dependency & Supply-Chain · #34 Architecture Governance & Docs *(30 categories at maturity 4)* -*(The pattern/layer/governance categories are auto-enforced by the architecture fitness functions in the deploy gate; the rest reached maturity 4 via the remediation tracked above. Keeping those gates green is the regression guard. UPDATE 2026-06-30: §16/§24/§27/§29/§31 joined the protect set via the enforcement-gate wave: #24/#16/#27 by new CI.slnf fitness tests, #31/#29 by the cost-guard/dr-freshness `deploy.needs` gates (all live in `deploy.needs`, `deploy.yml:791`). The 2026-06-29 §29 reopening is superseded. UPDATE (v1.93.0 sweep, 2026-06-30): #5 Vertical Slice Architecture also joined the protect set, its slice-cohesion fitness test confirmed a CI merge gate in CI.slnf. UPDATE (2026-07-02 re-score): **#18 UI Architecture left the protect set** because scorecard §18 maturity was corrected 4→3 (no automated §18 UI-architecture fitness gate; the container/presentational + code-behind conventions are review-enforced only), so it is reopened as an active priority-3 item and the count is now 26. UPDATE (2026-07-03 reconciliation): **#28 Front-End Testing joined** the protect set (scorecard §28 maturity 4 via the deploy-gating chromium `e2e-gate`) and **#19 State Management left it** (scorecard §19 maturity corrected 4→3 on the fifteenth cycle: no §19 fitness gate), so the membership swapped and the count stays 26. UPDATE (2026-07-15 twentieth-cycle re-score): **#18 UI Architecture, #19 State Management, and #23 Front-End Performance joined** the protect set (the §18/§19 fitness gates now run in the CI.slnf arch gate and the §23 CWV budgets are enforced inside the deploy-gating e2e-gate), taking the count to 29. UPDATE (2026-07-17 twenty-first-cycle re-score): **#13 Observability and #22 Responsive & Cross-Browser joined** the protect set (the `ObservabilityConventionTests` alert-runbook pairing gate runs in the CI.slnf arch gate, and all three e2e-gate browser legs now block the deploy per `e2e.yml:78`), taking the count to 31. UPDATE (2026-07-21 twenty-second-cycle re-score): **#22 Responsive & Cross-Browser LEFT the protect set** (scorecard §22 maturity corrected 4→3: the 2026-07-18 Actions-minute reduction cut the deploy `e2e-gate` to chromium only, `deploy.yml:488`, leaving firefox/webkit nightly-advisory under `e2e.yml:119`), taking the count to 30. #18 stays in the protect set: its maturity 4 gate is intact and only its implementation moved 9→8 (TD-16). The maturity-4 set is exactly the 30 categories other than §12/§21/§22/§33. UPDATE (2026-07-28 twenty-fourth-cycle re-score): **membership and count are unchanged at 30.** #15 stays in the protect set for the same reason #18 did: its maturity-4 gate is intact and only its implementation moved (8→7, TD-18 plus two effort-S hygiene items). UPDATE (2026-08-01 twenty-fifth-cycle re-score): **membership and count are again unchanged at 30**, and no category crossed either threshold; the six adversarial adjudications this cycle were all rejected implementation lifts, so nothing entered or left this list. **This list is the maturity-4 set, not the fully-closed set.** Of these 30, only **19** also score implementation >= 9, which is the pairing that means "done on both axes"; the other 11 (§5, §7, §15, §16, §18, §23, §24, §25, §27, §28, §31) keep a live row in the implementation band below. Protect what is here, but do not read presence here as "nothing left to do".)* +*(The pattern/layer/governance categories are auto-enforced by the architecture fitness functions in the deploy gate; the rest reached maturity 4 via the remediation tracked above. Keeping those gates green is the regression guard. UPDATE 2026-06-30: §16/§24/§27/§29/§31 joined the protect set via the enforcement-gate wave: #24/#16/#27 by new CI.slnf fitness tests, #31/#29 by the cost-guard/dr-freshness `deploy.needs` gates (all live in `deploy.needs`, `deploy.yml:791`). The 2026-06-29 §29 reopening is superseded. UPDATE (v1.93.0 sweep, 2026-06-30): #5 Vertical Slice Architecture also joined the protect set, its slice-cohesion fitness test confirmed a CI merge gate in CI.slnf. UPDATE (2026-07-02 re-score): **#18 UI Architecture left the protect set** because scorecard §18 maturity was corrected 4→3 (no automated §18 UI-architecture fitness gate; the container/presentational + code-behind conventions are review-enforced only), so it is reopened as an active priority-3 item and the count is now 26. UPDATE (2026-07-03 reconciliation): **#28 Front-End Testing joined** the protect set (scorecard §28 maturity 4 via the deploy-gating chromium `e2e-gate`) and **#19 State Management left it** (scorecard §19 maturity corrected 4→3 on the fifteenth cycle: no §19 fitness gate), so the membership swapped and the count stays 26. UPDATE (2026-07-15 twentieth-cycle re-score): **#18 UI Architecture, #19 State Management, and #23 Front-End Performance joined** the protect set (the §18/§19 fitness gates now run in the CI.slnf arch gate and the §23 CWV budgets are enforced inside the deploy-gating e2e-gate), taking the count to 29. UPDATE (2026-07-17 twenty-first-cycle re-score): **#13 Observability and #22 Responsive & Cross-Browser joined** the protect set (the `ObservabilityConventionTests` alert-runbook pairing gate runs in the CI.slnf arch gate, and all three e2e-gate browser legs now block the deploy per `e2e.yml:78`), taking the count to 31. UPDATE (2026-07-21 twenty-second-cycle re-score): **#22 Responsive & Cross-Browser LEFT the protect set** (scorecard §22 maturity corrected 4→3: the 2026-07-18 Actions-minute reduction cut the deploy `e2e-gate` to chromium only, `deploy.yml:488`, leaving firefox/webkit nightly-advisory under `e2e.yml:119`), taking the count to 30. #18 stays in the protect set: its maturity 4 gate is intact and only its implementation moved 9→8 (TD-16). The maturity-4 set is exactly the 30 categories other than §12/§21/§22/§33. UPDATE (2026-07-28 twenty-fourth-cycle re-score): **membership and count are unchanged at 30.** #15 stays in the protect set for the same reason #18 did: its maturity-4 gate is intact and only its implementation moved (8→7, TD-18 plus two effort-S hygiene items). UPDATE (2026-08-01 twenty-fifth-cycle re-score): **membership and count are again unchanged at 30**, and no category crossed either threshold; the six adversarial adjudications this cycle were all rejected implementation lifts, so nothing entered or left this list. UPDATE (2026-08-23 twenty-seventh-cycle re-score): **membership and count are unchanged at 30.** #4 stays in the protect set for the same reason #18 and #15 did: its maturity-4 gate is intact and only its implementation moved (9→8). **This list is the maturity-4 set, not the fully-closed set.** Of these 30, only **18** also score implementation >= 9, which is the pairing that means "done on both axes"; the other 12 (§4, §5, §7, §15, §16, §18, §23, §24, §25, §27, §28, §31) keep a live row in the implementation band below. Protect what is here, but do not read presence here as "nothing left to do".)* --- diff --git a/docs/governance/adc-ArchitectureScorecard.html b/docs/governance/adc-ArchitectureScorecard.html index 8e5fc90..01dc1db 100644 --- a/docs/governance/adc-ArchitectureScorecard.html +++ b/docs/governance/adc-ArchitectureScorecard.html @@ -6,21 +6,21 @@ MMCA.ADC: Architecture Scorecard · MMCA · Ivan Ball-llovera - + - + - + @@ -117,10 +117,10 @@

MMCA.ADC: Architecture Scorecard

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). Scored against the rubric at ArchitectureEvaluationCriteria.md; framework-wide facts in ../MMCA.Common/FACTS.md. Remediation lives in RemediationBacklog.md (the single ledger: the former TECHDEBT.md tactical TD-NN register was folded in 2026-06-26); 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 (HEAD 19021d93, clean tree); framework dependency pinned at MMCA.Common. v1.152.0* (all 15 packages, lockstep, Directory.Packages.props:92-110; the canonical ADR set is 001-078, indexed in ../adr/README.md; framework-wide facts in ../MMCA.Common/FACTS.md). What moved this cycle (twenty-sixth-cycle full 34-category re-score, 2026-08-14, pin v1.152.0): no score moves. All 34 categories were re-confirmed at their prior Maturity/Implementation from evidence read this run (26 rescored first-pass, 8 adversarially adjudicated). All eight adjudications were proposed lifts and all eight were rejected as verified non-moves, a second consecutive cycle in which every proposed lift landed one criterion short. §5 holds 4/8 (the rubric's first §5 criterion wants the DTO in the slice, and ADC's DTOs still sit in the Shared assembly with their mappers in horizontal Application/{Aggregate}/DTOs/ folders beside horizontal Validation/, Specifications/ and DomainEventHandlers/; AdcArchitectureMap.cs:12-43 still carries no Module("Notification", ...) entry, so enforcement covers 3 of the 4 modules: that half is now named as backlog TD-19). §7 holds 4/8 (the bidirectional sync-gRPC red flag that caps it did not close, it broadened to a second pair, Identity-Notification, across 7 sync client registrations in 4 services, and the same map omission leaves the transport-at-the-edge guard covering 3 of 4 services). §12 holds 3/8 (a maturity 3→4 was proposed; git log since the prior cycle's HEAD shows zero commits touching .github/workflows/load-test.yml, deploy.yml or Tests/Load/, so the blocker four prior cycles cited, a backend capacity proof executed out of band behind a recency-only deploy gate, is byte-for-byte intact). §13 holds 4/9 (a 9→10 was proposed a second time; runbooks are still missing for three of the six ENABLED production alerts, including the single severity-1 gateway-availability alert at infra/main.bicep:496-502, declared outside the sloAlertSpecs array the pairing gate parses). §15 holds 4/7 (all three grounds of the twenty-fourth-cycle downgrade are intact in current source, and the expired SQLite suppression is further past its own written removal condition than when it was recorded: the row cited v1.135.0, the pin is now v1.152.0, seventeen releases past the v1.121.0 sweep). §23 holds 4/8 (both halves of the category's own named lever, WASM code-split and image optimization, are verified still open, so Initial load and Asset hygiene stay short of reference quality). §28 holds 4/8 (the genuinely new state-management bUnit coverage is real but is a within-band improvement, not a band change, and two rubric criteria still carry concrete unmet elements). §31 holds 4/8 (the conference-day surge is still a manual scale-up with a manual reset instruction, cost-guard.yml:4, :83, and no automated or scheduled revert exists, so "reversible scale events" is unmet in its exact terms; the new daily ACR purge and the production metrics-instrument suppression strengthen other criteria without closing that one). Beyond the scores this is an anchor-and-provenance pass: the pin (v1.135.0→v1.152.0), HEAD (995a788619021d93) and ADR range (001-064→001-078) are updated, and the drifted citations corrected in place are the §5 architecture-map range, the §13 sev-1 alert (infra/main.bicep:481:496-502), the §21 axe coverage (17 pages→31 axe test methods over ~29 distinct pages, AccessibilityTests.cs:21-365, including the conference-day surfaces), the §27 routable-page denominator (37→49 @page files under Source, 48 excluding the MAUI-only DeviceSettings.razor, so the deferred lift's cost figure rises from roughly 34 pages to roughly 45), the §18 code-behind measurements and the §33 cross-service-tests.yml job anchors. TD-16 was re-measured: the high-water code-behind rose 395→398 of the 400 cap (SessionSelectionDashboard.razor.cs), so headroom narrowed from 5 lines to 2. Indices unchanged: Maturity 97.2% (311/320) / Implementation 85.6% (685/800), re-summed this run. The twenty-fifth-cycle record follows for provenance (2026-08-01, pin v1.135.0): no score moves. All 34 categories re-confirmed at their prior Maturity/Implementation from evidence read this run (28 rescored first-pass, 6 adversarially adjudicated). All six adjudications were proposed implementation lifts and all six were rejected as verified non-moves, which is itself the finding: the implementation axis is not stalling for want of effort, it is sitting one criterion short in six independent places. §5 holds 4/8 (the rubric's first §5 criterion wants command + handler + validator + DTO in the slice; ADC's DTOs live in the Shared assembly, Conference.Shared/Events/EventDTO.cs, with their mappers in a horizontal Application/Events/DTOs/, alongside horizontal Validation/, Specifications/ and DomainEventHandlers/ folders; the deliberate layered-by-project hybrid is unchanged, and AdcArchitectureMap.cs:12-44 still omits MMCA.ADC.Notification.Application from the enforced set, so a stranded handler added there would not be caught: benign today, not reference-quality enforcement). §13 holds 4/9 (a 9→10 was proposed; three ENABLED production alerts carry no runbook triage section and sit outside the CI pairing gate's scope, including the single severity-1 gateway-availability alert at infra/main.bicep:481, so the rubric's "runbooks for common failures" criterion is unmet: that is a real gap, not the trivial polish the top rung now allows). §24 holds 4/8 (the prior cycle's named 8→9 lever, a bUnit render-level assertion of the summary's error items, genuinely shipped and is CI-gated, but a fresh read finds two criteria only partially met: client validation does not mirror the server's cross-field and format rules, which is the category's first criterion and its first red flag, and the form-level error summary is present on 7 of the 15 MudForm forms). §27 holds 4/8 for a fourth consecutive rejection on byte-identical pseudo-localization evidence, plus one newly citable culture-aware-formatting violation not in the prior row's prose. §31 holds 4/8 (its own stated 8→9 lever, automating the conference-day surge and revert, is demonstrably not pulled, so "reversible scale events" stays unmet in its exact terms). §33 holds 3/8 for a second consecutive rejection: the lift rested on the broker-parity red flag being closed, and it is not: the AppHost still provisions RabbitMQ only, while the Azure Service Bus proof restored to the weekday nightly on 2026-07-29 is continue-on-error and explicitly rides no gate (cross-service-tests.yml:144-149; cross-service-freshness keys off the cross-service job, deploy.yml:663), so it is advisory, not closure, and the README sentence used to justify the lift is itself inaccurate about the gate. The below-maturity-4 set stays §12/§21/§22/§33 and §15 stays the only implementation score below 8. The v1.132.0 through v1.135.0 lockstep sweeps and the 2026-08-01 BugHunt remediation (ADC PR #94) moved no score. Beyond the scores, this cycle is an anchor-and-provenance pass: the pin (v1.131.0→v1.135.0), HEAD (2ec77796995a7886), ADR range (001-060→001-064) and roughly thirty drifted path:line citations were corrected in place against current source, and the TD-16/TD-17 ledger entries were re-measured (see RemediationBacklog.md). Indices unchanged: Maturity 97.2% (311/320) / Implementation 85.6% (685/800), re-summed this run. The twenty-fourth-cycle record follows for provenance (2026-07-28, pin v1.131.0): one score moves, and it moves down. §15 Best Practices & Code Quality Implementation 8→7 (Maturity 4 holds): three gaps verified in current source, none of which the prior row's basis ("only justified/tracked suppressions", "documented/dated NoWarn") still describes correctly. (1) The SQLite audit suppression GHSA-2m69-gcr7-jv3q is expired by its own written removal condition (Directory.Build.props:49-51, whose comment at :41-48 states it "stays only until the next MMCA.Common release and consumer pin sweep carry the fix through the published package graph, then it must be removed", updated 2026-07-20): ADC is now pinned at v1.131.0 (Directory.Packages.props:123), ten releases past the v1.121.0 SQLite sweep, MMCA.Common has removed its own suppression and pins the patched bundle directly (MMCA.Common/Directory.Packages.props:39,42, SQLitePCLRaw.bundle_e_sqlite3 3.0.5), and ADC's committed lock graph already resolves a patched transitive 3.0.4, so the entry now suppresses nothing in the audited graph while ADR-038 (../adr/038-supply-chain-provenance.md:49-52) already records the accepted-advisory list as empty. (2) Three global NoWarn codes (CS1591, RMG020, EXTEXP0001) carry no justification or date (Directory.Build.props:22), unlike every audit suppression in the same file (:7-12, :41-48) and unlike the test-wide ones commented at :31-33. (3) The MAUI MMCA.ADC.UI project sits outside every CI build and outside the audited graph (MMCA.ADC.CI.slnf:25 lists only UI.Web and UI.Web.Client; no workflow installs the maui-android workload), so analyzers and TWAE are review-only there and its own NoWarn CA5392 is never gated (Source/Hosts/UI/MMCA.ADC.UI/MMCA.ADC.UI.csproj:131); the gating vulnerable-package scan also runs against CI.slnf only (deploy.yml:288), so the MAUI graph that the :8-12 suppressions exist for is never audited in CI. The two hygiene items are a named effort-S lever in the backlog; the structural MAUI half is recorded as TD-18 rather than fixed, because adding a MAUI CI build cuts against the deliberate 2026-07-18 Actions-minute reduction. Maturity 4 was independently re-derived, not inherited: blanket dotnet_analyzer_diagnostic.severity = error (.editorconfig:312) plus TWAE/AnalysisMode=All/CodeAnalysisTreatWarningsAsErrors/EnforceCodeStyleInBuild (Directory.Build.props:16) and five analyzers repo-wide (:53-74), enforced by the required build-and-test Release build with a --locked-mode restore (deploy.yml:180, required per CONTRIBUTING.md:80). Positive signals unchanged: exactly one hand-written pragma disable in Source/, with an inline reason, and all five in-source SuppressMessage attributes carry a Justification. One adversarially adjudicated non-move: §27 holds M4/I8 for the third consecutive cycle on byte-identical evidence (PseudoLocalizationTests.cs:51 still declares exactly 3 public pages, public by design per :31, against 37 routable @page files counted this run; the file is untouched since c5e6f653 on 2026-07-11 and no .resx has landed since 2026-07-20). Rather than leave it a live candidacy for a fourth rejection, the lift is now adjudicated DEFERRED with its cost stated in the backlog's Deliberate/accepted section, with explicit re-open triggers (a second locale beyond es, any RTL locale, or a reported layout regression on an authenticated page). §12/§21/§22/§33 re-confirmed at M3/I8, so the below-maturity-4 set stays §12/§21/§22/§33. The v1.124.0 through v1.131.0 lockstep sweeps moved no score. Evidence refresh (no score move): the arch-test suite is now 29 test classes across 31 .cs files executing 91 methods, re-run green this cycle (91/91), up from the 26/28/82 snapshot, and 90 of the 91 are now inherited from the shared MMCA.Common.Testing.Architecture rule library, since the §13 alert-runbook pairing gate has been lifted upstream (ObservabilityConventionTests.cs:7 is now a bare thin subclass); the single remaining ADC-local method is the TD-14 Profile-form guard (FormsConventionTests.cs:31). Indices: Maturity 97.2% (311/320, unchanged) / Implementation 85.9%→85.6% (685/800). The twenty-third-cycle record follows for provenance: (twenty-third cycle, 2026-07-23, pin v1.123.0): no score moves; all 34 categories re-confirmed at their prior scores from evidence read this run (32 CONFIRMED first-pass, 2 adversarially adjudicated). The two adjudications were proposed maturity lifts, both rejected as verified non-moves: §12 holds M3/I8 (the k6 capacity tier still executes monthly cron + workflow_dispatch out of band, load-test.yml:8-18, and deploy's load-freshness is a recency check on the last successful run with no per-deploy k6 cost, deploy.yml:548-551; Notification stays pinned maxReplicas: 1, main.bicep:1424) and §21 holds M3/I8 (the recorded, dated manual screen-reader pass, the sole maturity-4 lever, is still the empty placeholder at adc-ACCESSIBILITY-SCREENREADER-PASS.md:62; the 18-page chromium axe/E2E deploy gate re-confirmed active via e2e-gate in deploy.needs, deploy.yml:791). §22 and §33 re-confirmed at M3/I8, so the below-maturity-4 set stays §12/§21/§22/§33; every implementation score is 8 or higher. The v1.122.0/v1.123.0 lockstep sweeps (filter DSL + cache observability; the IIntegrationEventPublisher removal with callers moved to IEventBus directly) moved no score. Indices unchanged: Maturity 97.2% (311/320) / Implementation 85.9% (687/800). The twenty-second-cycle record follows for provenance: (twenty-second cycle, 2026-07-21, pin v1.121.0): two scores move, both down, both on drift verified in current source and neither a code-quality regression. §22 Responsive & Cross-Browser Maturity 4→3. The twenty-first cycle's M4 rested on the deploy-gating e2e-gate passing all three engines; the 2026-07-18 Actions-minute reduction cut that gate to chromium only (deploy.yml:488 browsers: '["chromium"]', with the cost rationale recorded in the job comment at :478-480), so firefox and webkit now run only on the weeknight schedule (e2e.yml:39), where they are continue-on-error (e2e.yml:119). Cross-engine verification is therefore nightly-advisory, which is the rubric's Consistent (3), not Optimized (4); the responsive substance is untouched, so Implementation holds at 8. This is the scoring consequence of a deliberate cost choice, recorded as such in the backlog's Deliberate/accepted section. §18 UI Architecture & Components Implementation 9→8. Maturity 4 re-confirmed independently (UIArchitectureConventionTests.cs is a real sealed subclass of the shared base, the arch-test project is in MMCA.ADC.CI.slnf:58, and that filter is built and tested by the required build-and-test check at deploy.yml:181), but the exemplary-band 9 no longer holds: the largest code-behind in the repo, Engagement.UI/Pages/HappeningNow/HappeningNow.razor.cs, sits at exactly 400 lines against the enforced MaxCodeBehindLines => 400 cap (MMCA.Common.Testing.Architecture/Bases/UIArchitectureConventionTestsBase.cs:22), with six more files in the 360-379 band, so the next added method fails the gate rather than being caught in review. That is a real, isolated, citable gap (rubric Strong 7-8), and the prior 9 also rested partly on a broken citation (MobileInfiniteScrollList.razor:49-52, a file only 43 lines long). Tracked as TD-16 in the backlog. One adversarially adjudicated non-move: §27 holds M4/I8 (a proposed impl 8→9 was re-proposed and re-rejected on the identical basis as the twenty-first cycle: PseudoLocalizationTests.cs:51 still covers exactly 3 public pages of 36 routable pages, unchanged since the prior rejection). This cycle also corrected drifted line anchors across the file (CI.slnf:56:58, deploy.yml:303-309/343/417:483-489/:791, e2e.yml:78:119, main.bicep:1113:1424 and :341:488). The twenty-first-cycle record follows for provenance: (twenty-first cycle, 2026-07-17, pin v1.117.0): two maturity lifts, both on gates shipped 2026-07-16 that close the exact blockers the twentieth cycle recorded: §13 Observability 3→4 (ObservabilityConventionTests, an ADC-local fitness function in the CI.slnf arch gate, machine-enforces the alert-to-runbook pairing between infra/main.bicep sloAlertSpecs and infra/OPERATIONS.md, converting the "review-enforced IaC, not CI-gated" maturity cap) and §22 Responsive & Cross-Browser 3→4 (the deploy-gating e2e-gate passes the full chromium+firefox+webkit matrix, deploy.yml:309, and e2e.yml:78 scopes continue-on-error to scheduled nightly non-chromium legs only, so every engine the gate invokes can fail a deploy; promoted 2026-07-16 after 8 consecutive fully green nightly matrices). One adversarially adjudicated non-move: §27 holds M4/I8 (the wave-6 PseudoLocalizationTests tier covers 3 public pages, a partial extension of the text-expansion evidence, so the recorded impl 8→9 candidacy was rejected). This cycle also corrects stale prose introduced 2026-07-17 by PR #15, which accidentally bundled a superseded nineteenth-cycle draft (a "§12 mat 4" strength claim, a risk-1 rewrite asserting the firefox/webkit legs cannot fail the gate, and a mislabeled backlog update paragraph); §12 stays M3/I8 per the twentieth-cycle adjudication, re-confirmed this run (its k6 tier is freshness-gated via load-freshness, deploy.yml:348,417, but the tier itself runs monthly/dispatch and the Notification app stays pinned maxReplicas: 1). The arch-test suite re-ran green this cycle (82/82 methods across 26 test classes; the three new methods are the ADC-local §13 gate). Indices: Maturity 96.6%→97.8% (313/320) / Implementation 86.3% (690/800, unchanged). The twentieth-cycle record follows for provenance: (twentieth cycle, 2026-07-15, pin v1.116.0) five scores moved, all up, each on evidence that postdates the nineteenth cycle. Three maturity lifts close stale "no gate exists" rationales: §18 UI Architecture 3→4 (UIArchitectureConventionTests now machine-enforces the code-behind/container split in the CI.slnf gate), §19 State Management 3→4 (StateManagementConventionTests enforces no-mutable-static-UI-state + scoped stateful services in the same gate), and §23 Front-End Performance 3→4 (the Core Web Vitals budgets were recalibrated 2026-07-11 into real failing assertions riding the deploy-gating chromium e2e-gate, superseding the advisory-by-design budgets the nineteenth cycle correctly rejected). Two implementation lifts: §13 Observability 8→9 (the two named gaps closed: the Azure Monitor SLO workbook dashboard and the per-alert infra/OPERATIONS.md runbook) and §24 Forms 7→8 (TD-14 shipped: per-form MudAlert error summaries on all six create forms, machine-enforced markers, and a dedicated Profile-form fitness test). Adversarially adjudicated non-moves: §28 holds M4/I8 (its row's false "E2E #5 un-skipped" claim and three drifted line anchors are corrected below; the test is re-quarantined at SpeakerSelfServiceTests.cs:57), §33 holds M3/I8 (a proposed impl 9 was rejected: broker parity local-RabbitMQ vs prod-Service-Bus is mitigated, not closed, per README.md:74), and §34 holds M4/I9 (the same 9→8 downgrade the nineteenth cycle rejected was re-proposed and re-rejected). The arch-test suite re-ran green this cycle (79/79 inherited methods across 25 thin-subclass classes, up from 74/23 on the two new §18/§19 gates). Indices: Maturity 94.1%→96.6% (309/320) / Implementation 85.8%→86.3% (690/800).

+

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 (HEAD 96f0919a, clean tree); framework dependency pinned at MMCA.Common. v1.160.0* (all 15 packages, lockstep, Directory.Packages.props:92-110; the canonical ADR set is 001-096, indexed in ../adr/README.md; framework-wide facts in ../MMCA.Common/FACTS.md). What moved this cycle (twenty-seventh-cycle full 34-category re-score, 2026-08-23, pin v1.160.0): two scores move, both down, both on the implementation axis. §4 Domain-Driven Design Implementation 9→8 (Maturity 4 holds, independently re-derived): the prior row's citations had all drifted, and a fresh read against the rubric's own criteria finds three minor-but-real gaps that place the substance in the Strong 7-8 band rather than reference quality: (1) aggregate roots carry cross-aggregate object navigations with public setters alongside the by-ID FKs (Session.Event/Session.Room, Sponsor.Event, Activity.Event: the only public setters in the domain layer), so "references between aggregates by ID, not object graph" is only partially met; (2) Event.Create/Event.Update validate only name, timezone and date range while six newer optional fields (organizer contact email, sponsorship/ticketing URLs and friends) are accepted unvalidated by the aggregate with their email/URL rules living in the Application FluentValidation layer (EventValidationRules.cs:65), inconsistent with the repo's own convention (Sponsor.Create enforces its URL and booth-number invariants inside the aggregate); (3) Event.OrganizerContactEmail is a raw string? while the Email value object is used for the same concept on both User and Speaker, and ADC defines no value objects of its own. §22 Responsive & Cross-Browser Implementation 8→7 (Maturity 3 holds): the prior "holds 8 on unchanged substance" no longer stands, since an independent read finds the rubric's density-options criterion has zero adoption in ADC and the content-reflow criterion is only partially met on the 17 non-DataGrid table pages, including the data-dense conference-day surfaces; this names the implementation lever the backlog had recorded as "not yet identified". Eight adjudications were proposed lifts and all eight were rejected as verified non-moves, a third consecutive cycle in which every proposed lift landed short: §5 holds 4/8 (DTOs still in Shared with horizontal mapper/validator folders; the enforced validator rule exempts exactly the horizontal validators that exist, ArchitectureRules.Slices.cs:38-39; the forgot-password vertical landed command+handler slices with no in-slice validator plus a new controller, fresh proof the hybrid still edits switchboards); §7 holds 4/8 (the synchronous-coupling red flag broadened rather than closed); §15 holds 4/7 (all three twenty-fourth-cycle grounds byte-intact, the expired SQLite suppression now twenty-five releases past the v1.121.0 sweep); §17 holds 4/9 (no CI/CD substance changed; the SQL public-network-access cap is verbatim open); §18 holds 4/8 (the 400-line-cap pressure widened: eight code-behinds within 38 lines of the cap, two at 398, three files grew since 2026-08-14); §21 holds 3/8 (the screen-reader-pass placeholder is still empty, and four routable pages shipped 2026-08-19 with no axe coverage, a new gap not a lift); §28 holds 4/8 (zero visual-regression/snapshot tests while the shared MarkupSnapshot helper sits unused, and the E2E/axe layer is a conditional deploy gate, not a merge gate); §31 holds 4/8 (the surge automation lever unpulled for a third cycle, cost-guard.yml byte-unchanged). One structural finding is recorded rather than scored: the deploy-gating chromium E2E/axe/CWV suite is conditional on the diff being UI-affecting (deploy.yml:538) and the deploy job accepts a skipped gate (:896), so a backend-only merge reaches production with no browser run: recorded as backlog TD-20 plus a Deliberate/accepted amendment, and the ledger's "gates every deploy" phrasing is qualified accordingly. Indices: Maturity 97.2% (311/320, unchanged) / Implementation 85.6%→85.0% (680/800), both re-summed this run; the 5-point implementation drop reconciles exactly as §4 (-1 × w3) + §22 (-1 × w2). The twenty-sixth-cycle record follows for provenance (2026-08-14, pin v1.152.0): no score moves. All 34 categories were re-confirmed at their prior Maturity/Implementation from evidence read this run (26 rescored first-pass, 8 adversarially adjudicated). All eight adjudications were proposed lifts and all eight were rejected as verified non-moves, a second consecutive cycle in which every proposed lift landed one criterion short. §5 holds 4/8 (the rubric's first §5 criterion wants the DTO in the slice, and ADC's DTOs still sit in the Shared assembly with their mappers in horizontal Application/{Aggregate}/DTOs/ folders beside horizontal Validation/, Specifications/ and DomainEventHandlers/; AdcArchitectureMap.cs:12-43 still carries no Module("Notification", ...) entry, so enforcement covers 3 of the 4 modules: that half is now named as backlog TD-19). §7 holds 4/8 (the bidirectional sync-gRPC red flag that caps it did not close, it broadened to a second pair, Identity-Notification, across 7 sync client registrations in 4 services, and the same map omission leaves the transport-at-the-edge guard covering 3 of 4 services). §12 holds 3/8 (a maturity 3→4 was proposed; git log since the prior cycle's HEAD shows zero commits touching .github/workflows/load-test.yml, deploy.yml or Tests/Load/, so the blocker four prior cycles cited, a backend capacity proof executed out of band behind a recency-only deploy gate, is byte-for-byte intact). §13 holds 4/9 (a 9→10 was proposed a second time; runbooks are still missing for three of the six ENABLED production alerts, including the single severity-1 gateway-availability alert at infra/main.bicep:496-502, declared outside the sloAlertSpecs array the pairing gate parses). §15 holds 4/7 (all three grounds of the twenty-fourth-cycle downgrade are intact in current source, and the expired SQLite suppression is further past its own written removal condition than when it was recorded: the row cited v1.135.0, the pin is now v1.152.0, seventeen releases past the v1.121.0 sweep). §23 holds 4/8 (both halves of the category's own named lever, WASM code-split and image optimization, are verified still open, so Initial load and Asset hygiene stay short of reference quality). §28 holds 4/8 (the genuinely new state-management bUnit coverage is real but is a within-band improvement, not a band change, and two rubric criteria still carry concrete unmet elements). §31 holds 4/8 (the conference-day surge is still a manual scale-up with a manual reset instruction, cost-guard.yml:4, :83, and no automated or scheduled revert exists, so "reversible scale events" is unmet in its exact terms; the new daily ACR purge and the production metrics-instrument suppression strengthen other criteria without closing that one). Beyond the scores this is an anchor-and-provenance pass: the pin (v1.135.0→v1.152.0), HEAD (995a788619021d93) and ADR range (001-064→001-078) are updated, and the drifted citations corrected in place are the §5 architecture-map range, the §13 sev-1 alert (infra/main.bicep:481:496-502), the §21 axe coverage (17 pages→31 axe test methods over ~29 distinct pages, AccessibilityTests.cs:21-365, including the conference-day surfaces), the §27 routable-page denominator (37→49 @page files under Source, 48 excluding the MAUI-only DeviceSettings.razor, so the deferred lift's cost figure rises from roughly 34 pages to roughly 45), the §18 code-behind measurements and the §33 cross-service-tests.yml job anchors. TD-16 was re-measured: the high-water code-behind rose 395→398 of the 400 cap (SessionSelectionDashboard.razor.cs), so headroom narrowed from 5 lines to 2. Indices unchanged: Maturity 97.2% (311/320) / Implementation 85.6% (685/800), re-summed this run. The twenty-fifth-cycle record follows for provenance (2026-08-01, pin v1.135.0): no score moves. All 34 categories re-confirmed at their prior Maturity/Implementation from evidence read this run (28 rescored first-pass, 6 adversarially adjudicated). All six adjudications were proposed implementation lifts and all six were rejected as verified non-moves, which is itself the finding: the implementation axis is not stalling for want of effort, it is sitting one criterion short in six independent places. §5 holds 4/8 (the rubric's first §5 criterion wants command + handler + validator + DTO in the slice; ADC's DTOs live in the Shared assembly, Conference.Shared/Events/EventDTO.cs, with their mappers in a horizontal Application/Events/DTOs/, alongside horizontal Validation/, Specifications/ and DomainEventHandlers/ folders; the deliberate layered-by-project hybrid is unchanged, and AdcArchitectureMap.cs:12-44 still omits MMCA.ADC.Notification.Application from the enforced set, so a stranded handler added there would not be caught: benign today, not reference-quality enforcement). §13 holds 4/9 (a 9→10 was proposed; three ENABLED production alerts carry no runbook triage section and sit outside the CI pairing gate's scope, including the single severity-1 gateway-availability alert at infra/main.bicep:481, so the rubric's "runbooks for common failures" criterion is unmet: that is a real gap, not the trivial polish the top rung now allows). §24 holds 4/8 (the prior cycle's named 8→9 lever, a bUnit render-level assertion of the summary's error items, genuinely shipped and is CI-gated, but a fresh read finds two criteria only partially met: client validation does not mirror the server's cross-field and format rules, which is the category's first criterion and its first red flag, and the form-level error summary is present on 7 of the 15 MudForm forms). §27 holds 4/8 for a fourth consecutive rejection on byte-identical pseudo-localization evidence, plus one newly citable culture-aware-formatting violation not in the prior row's prose. §31 holds 4/8 (its own stated 8→9 lever, automating the conference-day surge and revert, is demonstrably not pulled, so "reversible scale events" stays unmet in its exact terms). §33 holds 3/8 for a second consecutive rejection: the lift rested on the broker-parity red flag being closed, and it is not: the AppHost still provisions RabbitMQ only, while the Azure Service Bus proof restored to the weekday nightly on 2026-07-29 is continue-on-error and explicitly rides no gate (cross-service-tests.yml:144-149; cross-service-freshness keys off the cross-service job, deploy.yml:663), so it is advisory, not closure, and the README sentence used to justify the lift is itself inaccurate about the gate. The below-maturity-4 set stays §12/§21/§22/§33 and §15 stays the only implementation score below 8. The v1.132.0 through v1.135.0 lockstep sweeps and the 2026-08-01 BugHunt remediation (ADC PR #94) moved no score. Beyond the scores, this cycle is an anchor-and-provenance pass: the pin (v1.131.0→v1.135.0), HEAD (2ec77796995a7886), ADR range (001-060→001-064) and roughly thirty drifted path:line citations were corrected in place against current source, and the TD-16/TD-17 ledger entries were re-measured (see RemediationBacklog.md). Indices unchanged: Maturity 97.2% (311/320) / Implementation 85.6% (685/800), re-summed this run. The twenty-fourth-cycle record follows for provenance (2026-07-28, pin v1.131.0): one score moves, and it moves down. §15 Best Practices & Code Quality Implementation 8→7 (Maturity 4 holds): three gaps verified in current source, none of which the prior row's basis ("only justified/tracked suppressions", "documented/dated NoWarn") still describes correctly. (1) The SQLite audit suppression GHSA-2m69-gcr7-jv3q is expired by its own written removal condition (Directory.Build.props:49-51, whose comment at :41-48 states it "stays only until the next MMCA.Common release and consumer pin sweep carry the fix through the published package graph, then it must be removed", updated 2026-07-20): ADC is now pinned at v1.131.0 (Directory.Packages.props:123), ten releases past the v1.121.0 SQLite sweep, MMCA.Common has removed its own suppression and pins the patched bundle directly (MMCA.Common/Directory.Packages.props:39,42, SQLitePCLRaw.bundle_e_sqlite3 3.0.5), and ADC's committed lock graph already resolves a patched transitive 3.0.4, so the entry now suppresses nothing in the audited graph while ADR-038 (../adr/038-supply-chain-provenance.md:49-52) already records the accepted-advisory list as empty. (2) Three global NoWarn codes (CS1591, RMG020, EXTEXP0001) carry no justification or date (Directory.Build.props:22), unlike every audit suppression in the same file (:7-12, :41-48) and unlike the test-wide ones commented at :31-33. (3) The MAUI MMCA.ADC.UI project sits outside every CI build and outside the audited graph (MMCA.ADC.CI.slnf:25 lists only UI.Web and UI.Web.Client; no workflow installs the maui-android workload), so analyzers and TWAE are review-only there and its own NoWarn CA5392 is never gated (Source/Hosts/UI/MMCA.ADC.UI/MMCA.ADC.UI.csproj:131); the gating vulnerable-package scan also runs against CI.slnf only (deploy.yml:288), so the MAUI graph that the :8-12 suppressions exist for is never audited in CI. The two hygiene items are a named effort-S lever in the backlog; the structural MAUI half is recorded as TD-18 rather than fixed, because adding a MAUI CI build cuts against the deliberate 2026-07-18 Actions-minute reduction. Maturity 4 was independently re-derived, not inherited: blanket dotnet_analyzer_diagnostic.severity = error (.editorconfig:312) plus TWAE/AnalysisMode=All/CodeAnalysisTreatWarningsAsErrors/EnforceCodeStyleInBuild (Directory.Build.props:16) and five analyzers repo-wide (:53-74), enforced by the required build-and-test Release build with a --locked-mode restore (deploy.yml:180, required per CONTRIBUTING.md:80). Positive signals unchanged: exactly one hand-written pragma disable in Source/, with an inline reason, and all five in-source SuppressMessage attributes carry a Justification. One adversarially adjudicated non-move: §27 holds M4/I8 for the third consecutive cycle on byte-identical evidence (PseudoLocalizationTests.cs:51 still declares exactly 3 public pages, public by design per :31, against 37 routable @page files counted this run; the file is untouched since c5e6f653 on 2026-07-11 and no .resx has landed since 2026-07-20). Rather than leave it a live candidacy for a fourth rejection, the lift is now adjudicated DEFERRED with its cost stated in the backlog's Deliberate/accepted section, with explicit re-open triggers (a second locale beyond es, any RTL locale, or a reported layout regression on an authenticated page). §12/§21/§22/§33 re-confirmed at M3/I8, so the below-maturity-4 set stays §12/§21/§22/§33. The v1.124.0 through v1.131.0 lockstep sweeps moved no score. Evidence refresh (no score move): the arch-test suite is now 29 test classes across 31 .cs files executing 91 methods, re-run green this cycle (91/91), up from the 26/28/82 snapshot, and 90 of the 91 are now inherited from the shared MMCA.Common.Testing.Architecture rule library, since the §13 alert-runbook pairing gate has been lifted upstream (ObservabilityConventionTests.cs:7 is now a bare thin subclass); the single remaining ADC-local method is the TD-14 Profile-form guard (FormsConventionTests.cs:31). Indices: Maturity 97.2% (311/320, unchanged) / Implementation 85.9%→85.6% (685/800). The twenty-third-cycle record follows for provenance: (twenty-third cycle, 2026-07-23, pin v1.123.0): no score moves; all 34 categories re-confirmed at their prior scores from evidence read this run (32 CONFIRMED first-pass, 2 adversarially adjudicated). The two adjudications were proposed maturity lifts, both rejected as verified non-moves: §12 holds M3/I8 (the k6 capacity tier still executes monthly cron + workflow_dispatch out of band, load-test.yml:8-18, and deploy's load-freshness is a recency check on the last successful run with no per-deploy k6 cost, deploy.yml:548-551; Notification stays pinned maxReplicas: 1, main.bicep:1424) and §21 holds M3/I8 (the recorded, dated manual screen-reader pass, the sole maturity-4 lever, is still the empty placeholder at adc-ACCESSIBILITY-SCREENREADER-PASS.md:62; the 18-page chromium axe/E2E deploy gate re-confirmed active via e2e-gate in deploy.needs, deploy.yml:791). §22 and §33 re-confirmed at M3/I8, so the below-maturity-4 set stays §12/§21/§22/§33; every implementation score is 8 or higher. The v1.122.0/v1.123.0 lockstep sweeps (filter DSL + cache observability; the IIntegrationEventPublisher removal with callers moved to IEventBus directly) moved no score. Indices unchanged: Maturity 97.2% (311/320) / Implementation 85.9% (687/800). The twenty-second-cycle record follows for provenance: (twenty-second cycle, 2026-07-21, pin v1.121.0): two scores move, both down, both on drift verified in current source and neither a code-quality regression. §22 Responsive & Cross-Browser Maturity 4→3. The twenty-first cycle's M4 rested on the deploy-gating e2e-gate passing all three engines; the 2026-07-18 Actions-minute reduction cut that gate to chromium only (deploy.yml:488 browsers: '["chromium"]', with the cost rationale recorded in the job comment at :478-480), so firefox and webkit now run only on the weeknight schedule (e2e.yml:39), where they are continue-on-error (e2e.yml:119). Cross-engine verification is therefore nightly-advisory, which is the rubric's Consistent (3), not Optimized (4); the responsive substance is untouched, so Implementation holds at 8. This is the scoring consequence of a deliberate cost choice, recorded as such in the backlog's Deliberate/accepted section. §18 UI Architecture & Components Implementation 9→8. Maturity 4 re-confirmed independently (UIArchitectureConventionTests.cs is a real sealed subclass of the shared base, the arch-test project is in MMCA.ADC.CI.slnf:58, and that filter is built and tested by the required build-and-test check at deploy.yml:181), but the exemplary-band 9 no longer holds: the largest code-behind in the repo, Engagement.UI/Pages/HappeningNow/HappeningNow.razor.cs, sits at exactly 400 lines against the enforced MaxCodeBehindLines => 400 cap (MMCA.Common.Testing.Architecture/Bases/UIArchitectureConventionTestsBase.cs:22), with six more files in the 360-379 band, so the next added method fails the gate rather than being caught in review. That is a real, isolated, citable gap (rubric Strong 7-8), and the prior 9 also rested partly on a broken citation (MobileInfiniteScrollList.razor:49-52, a file only 43 lines long). Tracked as TD-16 in the backlog. One adversarially adjudicated non-move: §27 holds M4/I8 (a proposed impl 8→9 was re-proposed and re-rejected on the identical basis as the twenty-first cycle: PseudoLocalizationTests.cs:51 still covers exactly 3 public pages of 36 routable pages, unchanged since the prior rejection). This cycle also corrected drifted line anchors across the file (CI.slnf:56:58, deploy.yml:303-309/343/417:483-489/:791, e2e.yml:78:119, main.bicep:1113:1424 and :341:488). The twenty-first-cycle record follows for provenance: (twenty-first cycle, 2026-07-17, pin v1.117.0): two maturity lifts, both on gates shipped 2026-07-16 that close the exact blockers the twentieth cycle recorded: §13 Observability 3→4 (ObservabilityConventionTests, an ADC-local fitness function in the CI.slnf arch gate, machine-enforces the alert-to-runbook pairing between infra/main.bicep sloAlertSpecs and infra/OPERATIONS.md, converting the "review-enforced IaC, not CI-gated" maturity cap) and §22 Responsive & Cross-Browser 3→4 (the deploy-gating e2e-gate passes the full chromium+firefox+webkit matrix, deploy.yml:309, and e2e.yml:78 scopes continue-on-error to scheduled nightly non-chromium legs only, so every engine the gate invokes can fail a deploy; promoted 2026-07-16 after 8 consecutive fully green nightly matrices). One adversarially adjudicated non-move: §27 holds M4/I8 (the wave-6 PseudoLocalizationTests tier covers 3 public pages, a partial extension of the text-expansion evidence, so the recorded impl 8→9 candidacy was rejected). This cycle also corrects stale prose introduced 2026-07-17 by PR #15, which accidentally bundled a superseded nineteenth-cycle draft (a "§12 mat 4" strength claim, a risk-1 rewrite asserting the firefox/webkit legs cannot fail the gate, and a mislabeled backlog update paragraph); §12 stays M3/I8 per the twentieth-cycle adjudication, re-confirmed this run (its k6 tier is freshness-gated via load-freshness, deploy.yml:348,417, but the tier itself runs monthly/dispatch and the Notification app stays pinned maxReplicas: 1). The arch-test suite re-ran green this cycle (82/82 methods across 26 test classes; the three new methods are the ADC-local §13 gate). Indices: Maturity 96.6%→97.8% (313/320) / Implementation 86.3% (690/800, unchanged). The twentieth-cycle record follows for provenance: (twentieth cycle, 2026-07-15, pin v1.116.0) five scores moved, all up, each on evidence that postdates the nineteenth cycle. Three maturity lifts close stale "no gate exists" rationales: §18 UI Architecture 3→4 (UIArchitectureConventionTests now machine-enforces the code-behind/container split in the CI.slnf gate), §19 State Management 3→4 (StateManagementConventionTests enforces no-mutable-static-UI-state + scoped stateful services in the same gate), and §23 Front-End Performance 3→4 (the Core Web Vitals budgets were recalibrated 2026-07-11 into real failing assertions riding the deploy-gating chromium e2e-gate, superseding the advisory-by-design budgets the nineteenth cycle correctly rejected). Two implementation lifts: §13 Observability 8→9 (the two named gaps closed: the Azure Monitor SLO workbook dashboard and the per-alert infra/OPERATIONS.md runbook) and §24 Forms 7→8 (TD-14 shipped: per-form MudAlert error summaries on all six create forms, machine-enforced markers, and a dedicated Profile-form fitness test). Adversarially adjudicated non-moves: §28 holds M4/I8 (its row's false "E2E #5 un-skipped" claim and three drifted line anchors are corrected below; the test is re-quarantined at SpeakerSelfServiceTests.cs:57), §33 holds M3/I8 (a proposed impl 9 was rejected: broker parity local-RabbitMQ vs prod-Service-Bus is mitigated, not closed, per README.md:74), and §34 holds M4/I9 (the same 9→8 downgrade the nineteenth cycle rejected was re-proposed and re-rejected). The arch-test suite re-ran green this cycle (79/79 inherited methods across 25 thin-subclass classes, up from 74/23 on the two new §18/§19 gates). Indices: Maturity 94.1%→96.6% (309/320) / Implementation 85.8%→86.3% (690/800).

Update 2026-06-30 (under-8 Implementation lift, §12). One Implementation score moves up; Maturity holds: Maturity 94.1% (301/320) unchanged, Implementation 85.9% → 86.1% (689/800). §12 Performance & Scalability Implementation 7→8: a WebVitalsTests Playwright tier now measures client-side Core Web Vitals (LCP/CLS/FCP/TTFB + a single-interaction INP sample) on the home, public-events, and login pages and emits a dated web-vitals-*.json artifact, closing the "no measured client-side CWV/INP" gap so both the backend k6 and client halves are measured. This is a test/CI-only change (no MMCA.Common release needed); it builds clean against the framework source. §21 Accessibility holds at Implementation 7 this cycle: its named 7→8 lever (promoting axe to a merge gate) is scoped as a follow-up, a backend-less in-process axe host mirroring MMCA.Common's proven gallery-host pattern, plus a recorded manual screen-reader pass: rather than rushed here (see RemediationBacklog.md #21). The shared /login//register a11y surface is already chromium-merge-gated upstream in MMCA.Common's CI.

Executive summary

-

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 monolith into four independently-hosted microservices (Identity, Conference, Engagement, Notification) behind a YARP gateway. Services collaborate synchronously via Result-over-the-wire gRPC contracts and asynchronously via MassTransit integration events on the outbox pattern, run database-per-service, and consume shared framework primitives as versioned MMCA.Common.* NuGet packages pinned uniformly at v1.152.0 (all 15, Directory.Packages.props:92-110). The standout characteristic is that architectural intent is not just documented but executable: 29 architecture-test classes (31 .cs files; 91 executed methods, re-run green 2026-07-28) enforce layer dependencies, domain purity, module isolation, transport-at-edge, concurrency, PII erasure, data residency, cross-source specification safety, UI architecture and state-management conventions, observability alert-runbook pairing, and resilience as CI gates; as of the framework's v1.73.0 these are thin subclasses of the shared MMCA.Common.Testing.Architecture rule library (ADR-015; 90 of the 91 methods are now inherited, the §13 alert-runbook pairing gate having been lifted upstream so ObservabilityConventionTests is a bare subclass, leaving the TD-14 Profile-form guard as the single ADC-local method), so ADC, Store, and Common run the same rules rather than parallel copies. Capacity/cost decisions are sized to measured conference load (~67 peak) rather than guesswork.

+

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 monolith into four independently-hosted microservices (Identity, Conference, Engagement, Notification) behind a YARP gateway. Services collaborate synchronously via Result-over-the-wire gRPC contracts and asynchronously via MassTransit integration events on the outbox pattern, run database-per-service, and consume shared framework primitives as versioned MMCA.Common.* NuGet packages pinned uniformly at v1.160.0 (all 15, Directory.Packages.props:92-110). The standout characteristic is that architectural intent is not just documented but executable: 29 architecture-test classes (31 .cs files; 91 executed methods, re-run green 2026-07-28) enforce layer dependencies, domain purity, module isolation, transport-at-edge, concurrency, PII erasure, data residency, cross-source specification safety, UI architecture and state-management conventions, observability alert-runbook pairing, and resilience as CI gates; as of the framework's v1.73.0 these are thin subclasses of the shared MMCA.Common.Testing.Architecture rule library (ADR-015; 90 of the 91 methods are now inherited, the §13 alert-runbook pairing gate having been lifted upstream so ObservabilityConventionTests is a bare subclass, leaving the TD-14 Profile-form guard as the single ADC-local method), so ADC, Store, and Common run the same rules rather than parallel copies. Capacity/cost decisions are sized to measured conference load (~67 peak) rather than guesswork.

This re-verification (2026-06-20) found the prior report materially stale. A remediation wave landed on 2026-06-19/06-20 that closes several of the previously-flagged top risks, each verified against source: (1) the disaster-recovery restore has now been drilled: DISASTER-RECOVERY.md:140-144 records a 2026-06-20 PITR restore of ADC_Conference in 2.6 min (well within the 2 h RTO), automated via dr-drill.yml/scripts/dr-restore-drill.ps1, and TD-10 is closed: eliminating the rubric's #1 §29 red flag (untested restore); (2) the SessionSpeakers GetAll gap is closed: SpeakerSelfServiceTests.cs:48-98 is now an active [Fact] (the Assert.Skip is gone) backed by a dedicated SessionIncludeChildrenRegressionTests; (3) a v2 API endpoint now exists: ServiceInfoController carries [ApiVersion("1.0", Deprecated)] + [ApiVersion("2.0")], demonstrating the versioning machinery beyond a single version; (4) EnableInbox=true on both consumers (Conference + Identity), activating Common's idempotent at-least-once delivery; (5) the PRIVACY.md residency contradiction is resolved: it now reads East US 2 apps / West US 2 SQL, matching the deployment, and is guarded by a new DataResidencyTests fitness function.

The headline finding remains a system whose backend and governance are near-exemplary, pulled down by a smaller-than-before cluster of unverified or unenforced operational claims. The weaker axis is still Implementation: an unusual inversion (process more complete than the proof those processes hold); through the v1.80.0 cycle the figures were Implementation 84.1% / Maturity 89.9%, the §32 lift took them to Implementation 84.3% / Maturity 90.5%, and this cycle's §29 lift to Implementation 84.7% / Maturity 90.5%: see Indices. The remaining points are won not by re-architecting anything but by promoting the high-quality but dispatch-only E2E/axe/a11y suites to merge gates (§21, §28, §22 (TD-06/07), closing structural supply-chain gaps like the missing lock files (§32) TD-01), and hardening the SQL data-plane (§11/§17: public network access + runtime SQL admin login).

This re-verification (2026-06-25) found no score-moving change since 06-22. ADC was swept with the framework from v1.76.0 to v1.79.0 in three lockstep bumps (1.77/1.78/1.79), so it inherits Common's third-wave additions (polyglot-persistence plumbing, ADR-017 request-idempotency documentation, the Dependabot held-package guards). The one ADC-specific event worth recording is the polyglot trial-and-revert: ADC built and locally tested Conference Session→Cosmos DB and Room→SQLite (ADR-018's first-use trial), then deliberately reverted to all-SQL-Server while keeping every framework extension point: the AppHost wires all four modules via WithSQLServerDataSource, every appsettings Cosmos/SQLite connection string is empty, and no entity uses a non-SQL engine shim. ADC did adopt the new cross-source SpecificationConventionTests opt-in, though its doc-comment still narrates the reverted Cosmos/SQLite layout: a cosmetic doc-drift, not a behavioral one. (Correction 2026-06-26: the prior cycle's claim that DataResidencyTests also narrates the reverted layout was wrong, that file's comment is about deployment-region residency only; SpecificationConventionTests.cs:3 is the sole arch-test still mentioning Cosmos/SQLite.) Net: the scorecard below is unchanged from 06-22 except for refreshed counts and evidence; the open items (E2E gate, lock files, SQL data-plane) are all re-confirmed open.

@@ -184,9 +184,9 @@

Scorecard

Domain-Driven Design 3 4 - 9 - 12/27 - Aggregates enforce invariants internally, reference each other by ID, use value objects (Email) + identifier aliases, raise domain events, expose rich behavior with Result-returning factories: no anemic model; ubiquitous language (BR-### business rules) pervasive. Money/Address VOs live in Common rather than ADC, a minor locality nit. Evidence: Identity.Domain/Users/User.cs:16,20,130 (rich aggregate, IAnonymizable, Email VO, Result factory, domain events :267/:305); Conference.Domain/Events/Event.cs:55-71,424,445 (encapsulated child collections, by-ID refs, EventSpeakerChanged events); Conference.Domain/Speakers/Speaker.cs:7,24 (Email VO from Common) + 8 + 12/24 + ↓ Implementation 9→8 (twenty-seventh cycle): three minor-but-real gaps against the rubric's own aggregate criteria, none individually a red flag, together the Strong 7-8 band rather than reference quality. (1) Aggregate roots carry cross-aggregate object navigations with public setters alongside the by-ID FKs, the only public setters in the domain layer (Conference.Domain/Sessions/Session.cs:71 Event/Room; Conference.Domain/Sponsors/Sponsor.cs:49; Conference.Domain/Activities/Activity.cs:58), so "references between aggregates by ID, not object graph" is partial. (2) Event.Create validates only name/timezone/date range (Event.cs:180) and Event.Update repeats the same three-invariant check then assigns organizerContactEmail/sponsorshipPacketUrl/ticketingUrl unchecked (Event.cs:244); their email/URL rules live in the Application layer (EventValidationRules.cs:65), inconsistent with the repo's own convention (Sponsor.Create enforces its URL + booth-number invariants in-aggregate, Sponsor.cs:120). (3) Event.OrganizerContactEmail is a raw string? (Event.cs:56) while the Email VO covers the same concept on User (Identity.Domain/Users/User.cs:38) and Speaker (Conference.Domain/Speakers/Speaker.cs:31); ADC defines no VOs of its own. Holding at 8, not lower: rich Result-returning factories with combined invariants (User.cs:163), domain events raised by the aggregates (User.cs:332, Event.cs:561), encapsulated child collections behind IReadOnlyCollection with a documented include policy (Event.cs:85), and the newest aggregate is reference quality (alias-typed ID refs, invariants, Result factory, event in the same transaction: Engagement.Domain/CheckIns/CheckIn.cs:89; AttendeeBadge.cs:13 documents why it deliberately raises no event). Maturity 4 independently re-derived: EntityConventionTests.cs:3 + ImmutabilityTests.cs:3 (sealed subclasses of the shared bases over AdcArchitectureMap.cs:22, covering every domain-bearing module) gate CI via MMCA.ADC.CI.slnf:58 + deploy.yml:219 5 @@ -346,9 +346,9 @@

Scorecard

Responsive & Cross-Browser 2 3 - 8 - 6/16 - ↓ Maturity 4→3 (twenty-second cycle): the deploy gate was cut to a single engine, so cross-browser verification is no longer enforced. The twenty-first cycle scored M4 on a deploy-gating e2e-gate that passed chromium + firefox + webkit; the 2026-07-18 Actions-minute reduction narrowed it to browsers: '["chromium"]' (deploy.yml:541, in the e2e-gate job at :531, still in deploy.needs at :866). Firefox and webkit now run only on the nightly, and since 2026-07-29 that nightly was thinned further to alternating single-engine legs: two separate crons, Monday firefox and Thursday webkit (e2e.yml:49,:50, rationale :44-48), so each non-chromium engine is verified once a week rather than both twice; on the scheduled event they are continue-on-error (e2e.yml:144). Cross-engine coverage is therefore nightly-advisory and thinner than when the maturity dropped, enforced by convention rather than automatically, which is the rubric's Consistent (3), not Optimized (4). This is a deliberate cost trade-off, recorded in the backlog's Deliberate/accepted section, not a regression in the responsive work. Implementation holds 8 on unchanged substance: fluid layouts via MudBlazor grid/breakpoints, data grids degrading to card lists on mobile (no horizontal-scroll/unusable-grid red flag), the shared .mmca-touch-target 48px affordance (Common, v1.94.0), and a documented Chromium/Firefox/WebKit matrix that still runs, just off the gate. Maturity 4 lever: re-add the two legs to the deploy gate, or add a cross-browser-freshness job to deploy.needs mirroring the dr/load/cross-service freshness pattern (deploy.yml:549,606,663) so a stale or red nightly leg blocks the deploy at near-zero runner cost. Evidence: deploy.yml:531,541,866; e2e.yml:44-50,144; Common BreakpointConstants.cs:16-17 (<960px drives grid→card); EventList.razor:28-60 (MobileInfiniteScrollList) + EventCreate.razor:39-56 (MudGrid xs/sm); Common app.css:195-200 (hide-below-desktop columns) + 7 + 6/14 + ↓ Implementation 8→7 (twenty-seventh cycle): the rubric's density-options criterion has zero adoption in ADC, and content reflow is only partially met on the 17 non-DataGrid table pages, including the data-dense conference-day surfaces; this names the implementation lever the backlog had carried as "not yet identified" and places the substance at the bottom of the Strong band. ↓ Maturity 4→3 (twenty-second cycle): the deploy gate was cut to a single engine, so cross-browser verification is no longer enforced. The twenty-first cycle scored M4 on a deploy-gating e2e-gate that passed chromium + firefox + webkit; the 2026-07-18 Actions-minute reduction narrowed it to browsers: '["chromium"]' (deploy.yml:541, in the e2e-gate job at :531, still in deploy.needs at :866). Firefox and webkit now run only on the nightly, and since 2026-07-29 that nightly was thinned further to alternating single-engine legs: two separate crons, Monday firefox and Thursday webkit (e2e.yml:49,:50, rationale :44-48), so each non-chromium engine is verified once a week rather than both twice; on the scheduled event they are continue-on-error (e2e.yml:144). Cross-engine coverage is therefore nightly-advisory and thinner than when the maturity dropped, enforced by convention rather than automatically, which is the rubric's Consistent (3), not Optimized (4). This is a deliberate cost trade-off, recorded in the backlog's Deliberate/accepted section, not a regression in the responsive work. Implementation 7: the strong substance stands (fluid layouts via MudBlazor grid/breakpoints, data grids degrading to card lists on mobile with no horizontal-scroll/unusable-grid red flag, the shared .mmca-touch-target 48px affordance (Common, v1.94.0), and a documented Chromium/Firefox/WebKit matrix that still runs, just off the gate), but two rubric criteria are now verified short: density options have zero adoption anywhere in ADC, and content reflow is only partial on the 17 table pages that do not use the DataGrid's card-list degradation, including the conference-day surfaces. Maturity 4 lever: re-add the two legs to the deploy gate, or add a cross-browser-freshness job to deploy.needs mirroring the dr/load/cross-service freshness pattern (deploy.yml:549,606,663) so a stale or red nightly leg blocks the deploy at near-zero runner cost. Evidence: deploy.yml:531,541,866; e2e.yml:44-50,144; Common BreakpointConstants.cs:16-17 (<960px drives grid→card); EventList.razor:28-60 (MobileInfiniteScrollList) + EventCreate.razor:39-56 (MudGrid xs/sm); Common app.css:195-200 (hide-below-desktop columns) 23 @@ -460,20 +460,20 @@

Scorecard

-

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 when the chromium E2E/axe suite became a deploy gate (§21 now M3/I8; the recorded screen-reader pass is the remaining maturity-4 lever). The 2026-06-30 enforcement-gate wave lifted §16/§24/§27/§29/§31 maturity 3→4, making each category's already-strong implementation enforced by a CI gate (three new fitness tests in the CI.slnf arch gate, plus the cost-guard and dr-freshness deploy gates), which closed most of the Maturity-vs-Implementation inversion. The prior 2026-06-29 re-score had corrected §29 maturity 4→3 (the DR restore drill was then a scheduled-but-non-gating cron; this wave's dr-freshness deploy gate now makes it an actual gate, so §29 is back to maturity 4 on substantively different evidence) and lifted §32 implementation 8→9 (CI restore runs --locked-mode in both gating jobs); §5/§7/§13/§25 were adversarially FLAG-re-checked and confirmed unchanged. The subsequent v1.93.0 sweep (2026-06-30) then lifted §5 Vertical Slice Architecture maturity 3→4 (the slice-cohesion fitness function SliceCohesionTests is confirmed a CI merge gate in MMCA.ADC.CI.slnf), and re-confirmed §7 at M4/I8 (a proposed impl 8→9 lift adversarially rejected over the bidirectional Conference↔Engagement gRPC pair). Earlier waves had flipped §27 i18n from N/A to scored (M3/I8), moved §20/§24 implementation 8→9, lifted §29 impl 8→9 (graceful-shutdown failure test CI-gated), closed §32 mature-but-not-locked (lock files committed, M3→4/I7→8), §29 recovery (drilled), and §30 (residency matched). The sixteenth-cycle full re-score (2026-07-03) recalibrated §24 Implementation 9→7 (the per-form error summary exists only on the Profile form, the six create forms surface a generic validation snackbar, and the Profile handlers show raw exception text), the only score move of that cycle; §24 Maturity holds 4 on the FormsConventionTests gate and the residual is tracked as TD-14. The eighteenth (2026-07-06, pin v1.106.0) and nineteenth (2026-07-10, pin v1.110.0) full re-scores moved no score; the nineteenth adversarially rejected three proposed moves (§12 impl 8→9 on the Notification single-replica pin, §23 maturity 3→4 on the advisory-by-design vitals budgets, §34 impl 9→8 as unsupported), each a verified non-move. The twentieth-cycle full re-score (2026-07-15, pin v1.116.0) lifted §18/§19/§23 maturity 3→4 (the §18/§19 fitness gates now exist in the CI.slnf arch gate; the §23 vitals budgets became enforced assertions inside the deploy-gating e2e-gate on 2026-07-11) and §13/§24 implementation (8→9 on the shipped SLO workbook + OPERATIONS.md runbook; 7→8 on TD-14's per-form error summaries), while re-rejecting the §34 9→8 downgrade, rejecting a §33 impl 8→9 on the open broker-parity red flag, and correcting §28's false "E2E #5 un-skipped" claim in place (score held). This closes the §18/§19 half of the Maturity-vs-Implementation inversion; the below-maturity-4 set narrows to §12/§13/§21/§22/§33. The twenty-first-cycle full re-score (2026-07-17, pin v1.117.0) lifted §13 and §22 maturity 3→4 (the ADC-local alert-runbook pairing fitness gate and the fully gating three-browser e2e-gate, both shipped 2026-07-16), adversarially rejected the §27 impl 8→9 pseudo-loc candidacy (3 pages of 30+, partial), re-confirmed §12 at M3/I8, and corrected the stale nineteenth-cycle draft prose that PR #15 accidentally committed; the below-maturity-4 set narrows to §12/§21/§33. The twenty-second-cycle full re-score (2026-07-21, pin v1.121.0) moved two scores down, neither on a quality regression: §22 maturity 4→3 (the 2026-07-18 CI-minute reduction cut the deploy e2e-gate to chromium only, deploy.yml:488, leaving firefox/webkit nightly-advisory, e2e.yml:119, a deliberate cost trade-off now recorded as such) and §18 implementation 9→8 (the largest code-behind is flush at the enforced 400-line cap with zero headroom, TD-16), while re-rejecting the §27 impl 8→9 pseudo-loc candidacy a second time on unchanged evidence. The below-maturity-4 set widens to §12/§21/§22/§33, and this reopens the Maturity-vs-Implementation picture on the front-end operational side: the §22 gap is not "the tests do not exist" but "the tests no longer gate." The twenty-third-cycle full re-score (2026-07-23, pin v1.123.0) moved no score (both proposed maturity lifts, §12 and §21, adversarially rejected as verified non-moves). The twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0) moved one score, down: §15 Best Practices & Code Quality implementation 8→7, on suppression/NoWarn hygiene drift rather than any code-quality regression (an audit suppression expired by its own written removal condition, three unjustified global NoWarn codes, and the MAUI project sitting outside every CI build and outside the audited dependency graph). This is a third distinct shape of axis gap: not "no gate exists" (§18/§19 pre-2026-07-15) and not "the gate stopped gating" (§22), but the enforced perimeter has a documented hole in it, since maturity 4 is measured on the CI.slnf graph while one shipped project sits outside that graph entirely. §15 keeps maturity 4 and so stays in the protect set while sitting at the top of the implementation band, which is exactly the two-axis behaviour the bands exist to make visible. The same cycle adjudicated the §27 pseudo-localization lift DEFERRED after a third rejection on unchanged evidence, so it is no longer carried as an open candidacy. The twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0) moved no score, and all six adjudications were rejected implementation lifts (§5, §13, §24, §27, §31, §33). That is the cycle's actual finding and a fourth shape of axis gap: not "no gate exists", not "the gate stopped gating", not "the enforced perimeter has a hole", but one unmet criterion each, in six independent categories, where the work that would close them is named and small but has not shipped. Two of the six (§27 and §33) are now on their fourth and second consecutive rejection respectively, which is the signal that they need a decision (schedule it or record it as deliberate) rather than another re-proposal.

+

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 when the chromium E2E/axe suite became a deploy gate (§21 now M3/I8; the recorded screen-reader pass is the remaining maturity-4 lever). The 2026-06-30 enforcement-gate wave lifted §16/§24/§27/§29/§31 maturity 3→4, making each category's already-strong implementation enforced by a CI gate (three new fitness tests in the CI.slnf arch gate, plus the cost-guard and dr-freshness deploy gates), which closed most of the Maturity-vs-Implementation inversion. The prior 2026-06-29 re-score had corrected §29 maturity 4→3 (the DR restore drill was then a scheduled-but-non-gating cron; this wave's dr-freshness deploy gate now makes it an actual gate, so §29 is back to maturity 4 on substantively different evidence) and lifted §32 implementation 8→9 (CI restore runs --locked-mode in both gating jobs); §5/§7/§13/§25 were adversarially FLAG-re-checked and confirmed unchanged. The subsequent v1.93.0 sweep (2026-06-30) then lifted §5 Vertical Slice Architecture maturity 3→4 (the slice-cohesion fitness function SliceCohesionTests is confirmed a CI merge gate in MMCA.ADC.CI.slnf), and re-confirmed §7 at M4/I8 (a proposed impl 8→9 lift adversarially rejected over the bidirectional Conference↔Engagement gRPC pair). Earlier waves had flipped §27 i18n from N/A to scored (M3/I8), moved §20/§24 implementation 8→9, lifted §29 impl 8→9 (graceful-shutdown failure test CI-gated), closed §32 mature-but-not-locked (lock files committed, M3→4/I7→8), §29 recovery (drilled), and §30 (residency matched). The sixteenth-cycle full re-score (2026-07-03) recalibrated §24 Implementation 9→7 (the per-form error summary exists only on the Profile form, the six create forms surface a generic validation snackbar, and the Profile handlers show raw exception text), the only score move of that cycle; §24 Maturity holds 4 on the FormsConventionTests gate and the residual is tracked as TD-14. The eighteenth (2026-07-06, pin v1.106.0) and nineteenth (2026-07-10, pin v1.110.0) full re-scores moved no score; the nineteenth adversarially rejected three proposed moves (§12 impl 8→9 on the Notification single-replica pin, §23 maturity 3→4 on the advisory-by-design vitals budgets, §34 impl 9→8 as unsupported), each a verified non-move. The twentieth-cycle full re-score (2026-07-15, pin v1.116.0) lifted §18/§19/§23 maturity 3→4 (the §18/§19 fitness gates now exist in the CI.slnf arch gate; the §23 vitals budgets became enforced assertions inside the deploy-gating e2e-gate on 2026-07-11) and §13/§24 implementation (8→9 on the shipped SLO workbook + OPERATIONS.md runbook; 7→8 on TD-14's per-form error summaries), while re-rejecting the §34 9→8 downgrade, rejecting a §33 impl 8→9 on the open broker-parity red flag, and correcting §28's false "E2E #5 un-skipped" claim in place (score held). This closes the §18/§19 half of the Maturity-vs-Implementation inversion; the below-maturity-4 set narrows to §12/§13/§21/§22/§33. The twenty-first-cycle full re-score (2026-07-17, pin v1.117.0) lifted §13 and §22 maturity 3→4 (the ADC-local alert-runbook pairing fitness gate and the fully gating three-browser e2e-gate, both shipped 2026-07-16), adversarially rejected the §27 impl 8→9 pseudo-loc candidacy (3 pages of 30+, partial), re-confirmed §12 at M3/I8, and corrected the stale nineteenth-cycle draft prose that PR #15 accidentally committed; the below-maturity-4 set narrows to §12/§21/§33. The twenty-second-cycle full re-score (2026-07-21, pin v1.121.0) moved two scores down, neither on a quality regression: §22 maturity 4→3 (the 2026-07-18 CI-minute reduction cut the deploy e2e-gate to chromium only, deploy.yml:488, leaving firefox/webkit nightly-advisory, e2e.yml:119, a deliberate cost trade-off now recorded as such) and §18 implementation 9→8 (the largest code-behind is flush at the enforced 400-line cap with zero headroom, TD-16), while re-rejecting the §27 impl 8→9 pseudo-loc candidacy a second time on unchanged evidence. The below-maturity-4 set widens to §12/§21/§22/§33, and this reopens the Maturity-vs-Implementation picture on the front-end operational side: the §22 gap is not "the tests do not exist" but "the tests no longer gate." The twenty-third-cycle full re-score (2026-07-23, pin v1.123.0) moved no score (both proposed maturity lifts, §12 and §21, adversarially rejected as verified non-moves). The twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0) moved one score, down: §15 Best Practices & Code Quality implementation 8→7, on suppression/NoWarn hygiene drift rather than any code-quality regression (an audit suppression expired by its own written removal condition, three unjustified global NoWarn codes, and the MAUI project sitting outside every CI build and outside the audited dependency graph). This is a third distinct shape of axis gap: not "no gate exists" (§18/§19 pre-2026-07-15) and not "the gate stopped gating" (§22), but the enforced perimeter has a documented hole in it, since maturity 4 is measured on the CI.slnf graph while one shipped project sits outside that graph entirely. §15 keeps maturity 4 and so stays in the protect set while sitting at the top of the implementation band, which is exactly the two-axis behaviour the bands exist to make visible. The same cycle adjudicated the §27 pseudo-localization lift DEFERRED after a third rejection on unchanged evidence, so it is no longer carried as an open candidacy. The twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0) moved no score, and all six adjudications were rejected implementation lifts (§5, §13, §24, §27, §31, §33). That is the cycle's actual finding and a fourth shape of axis gap: not "no gate exists", not "the gate stopped gating", not "the enforced perimeter has a hole", but one unmet criterion each, in six independent categories, where the work that would close them is named and small but has not shipped. Two of the six (§27 and §33) are now on their fourth and second consecutive rejection respectively, which is the signal that they need a decision (schedule it or record it as deliberate) rather than another re-proposal. The twenty-sixth-cycle full re-score (2026-08-14, pin v1.152.0) moved no score (all eight adjudications rejected lifts). The twenty-seventh-cycle full re-score (2026-08-23, pin v1.160.0) moved two scores, both down on the implementation axis: §4 9→8 (public-setter cross-aggregate navigations, aggregate-external validation of Event's newer optional fields, and primitive obsession on OrganizerContactEmail where the Email VO covers the same concept elsewhere) and §22 8→7 (zero density-option adoption plus partial content reflow on the non-DataGrid table pages, naming the lever the backlog had carried as "not yet identified"). Both are fresh-read recalibrations against drifted prior citations, not code regressions; §4 enters the implementation band for the first time, and §22 rises to its joint top alongside §15. The same cycle rejected all eight proposed lifts and recorded the conditionality of the e2e-gate (UI-affecting diffs only, skipped gate accepted by deploy) as TD-20 rather than a score move.

Indices

    -
  • 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 4→3 (-2 weighted) after the 2026-07-18 Actions-minute reduction cut the deploy e2e-gate to chromium only (deploy.yml:488), leaving firefox/webkit on the nightly matrix where they are continue-on-error (e2e.yml:119), so cross-engine verification is no longer automatically enforced. The open maturity-3 set widens to §12/§21/§22/§33. The twenty-first-cycle §13 3→4 lift and the twentieth-cycle §18/§19/§23 3→4 lifts stand.) Re-confirmed unchanged by the twenty-third-cycle full re-score (2026-07-23, pin v1.123.0, no moves; the §12 and §21 maturity 3→4 proposals were adversarially rejected) and again by the twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0), where the single score move was on the implementation axis (§15 8→7) and no category crossed a maturity band, so the maturity numerator, denominator and open maturity-3 set (§12/§21/§22/§33) are all unchanged. Re-confirmed again by the twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0, HEAD 995a7886): no score moved on either axis, the six adversarial adjudications were all rejected implementation lifts, and the sum was re-derived independently this run; re-confirmed once more by the twenty-sixth-cycle full re-score (2026-08-14, pin v1.152.0, HEAD 19021d93), where all eight adjudications were again rejected lifts and the numerator was re-summed unchanged (Σ(M×w) = 311, Σ(w) = 80).
  • -
  • Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 685 ÷ 800 = 85.6% (down from 85.9%, 687/800: the twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0) corrected §15 8→7 (-2 weighted) on suppression/NoWarn hygiene drift plus the MAUI project sitting outside the CI-audited graph, the only score move of that cycle and the only rank change on either band. The §27 impl 8→9 pseudo-loc candidacy was adversarially rejected for a third consecutive cycle on byte-identical evidence and is now adjudicated DEFERRED rather than carried open. Prior basis follows: down from 86.3%, 690/800: §18 impl 9→8 (-3 weighted) on the code-behind flush at the enforced 400-line cap with six more files in the 360-379 band, TD-16. The §27 impl 8→9 pseudo-loc candidacy was adversarially rejected for a second consecutive cycle on unchanged evidence, 3 public pages of 36 routable. The twentieth-cycle §13 8→9 and §24 7→8 lifts, the seventeenth-cycle §27 7→8 lift, and the fourteenth-cycle §6 10→9 correction stand.) Re-confirmed unchanged by the twenty-third-cycle full re-score (2026-07-23, pin v1.123.0, no moves; every implementation score was then 8 or higher). As of the twenty-fourth cycle §15 at 7 is the only implementation score below 8; the other 33 remain 8 or higher. Re-confirmed again by the twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0): no score moved, and the six proposed lifts (§5, §13, §24, §27, §31, §33) were each adversarially rejected against current source, so the numerator was re-derived rather than inherited (Σ(I×w) = 685 re-summed this run); the twenty-sixth-cycle full re-score (2026-08-14, pin v1.152.0, HEAD 19021d93) rejected all eight proposed lifts (§5, §7, §12 as an M3→4, §13 as a 9→10, §15, §23, §28, §31) and re-summed the same 685. The gap to a full 800 is 35 weighted points spread across 15 categories, which is what the backlog's implementation band ranks.
  • +
  • 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 4→3 (-2 weighted) after the 2026-07-18 Actions-minute reduction cut the deploy e2e-gate to chromium only (deploy.yml:488), leaving firefox/webkit on the nightly matrix where they are continue-on-error (e2e.yml:119), so cross-engine verification is no longer automatically enforced. The open maturity-3 set widens to §12/§21/§22/§33. The twenty-first-cycle §13 3→4 lift and the twentieth-cycle §18/§19/§23 3→4 lifts stand.) Re-confirmed unchanged by the twenty-third-cycle full re-score (2026-07-23, pin v1.123.0, no moves; the §12 and §21 maturity 3→4 proposals were adversarially rejected) and again by the twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0), where the single score move was on the implementation axis (§15 8→7) and no category crossed a maturity band, so the maturity numerator, denominator and open maturity-3 set (§12/§21/§22/§33) are all unchanged. Re-confirmed again by the twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0, HEAD 995a7886): no score moved on either axis, the six adversarial adjudications were all rejected implementation lifts, and the sum was re-derived independently this run; re-confirmed once more by the twenty-sixth-cycle full re-score (2026-08-14, pin v1.152.0, HEAD 19021d93), where all eight adjudications were again rejected lifts and the numerator was re-summed unchanged (Σ(M×w) = 311, Σ(w) = 80); and again by the twenty-seventh-cycle full re-score (2026-08-23, pin v1.160.0, HEAD 96f0919a), whose two score moves were both on the implementation axis, so the maturity numerator and the open maturity-3 set (§12/§21/§22/§33) are unchanged (Σ(M×w) = 311 re-summed this run).
  • +
  • Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 680 ÷ 800 = 85.0% (down from 85.6%, 685/800: the twenty-seventh-cycle full re-score (2026-08-23, pin v1.160.0, HEAD 96f0919a) corrected §4 9→8 (-3 weighted: public-setter cross-aggregate navigations, aggregate-external validation of Event's newer optional fields, primitive obsession on OrganizerContactEmail) and §22 8→7 (-2 weighted: zero density-option adoption, partial content reflow on the 17 non-DataGrid table pages), both fresh-read recalibrations against drifted prior citations; the same cycle rejected all eight proposed lifts (§5, §7, §15, §17, §18, §21, §28, §31) and the sum was re-derived independently this run (Σ(I×w) = 680). The gap to a full 800 is 40 weighted points spread across 16 categories, which is what the backlog's implementation band ranks; §15 at 7 and now §22 at 7 are the two implementation scores below 8. Prior basis follows: down from 85.9%, 687/800: the twenty-fourth-cycle full re-score (2026-07-28, pin v1.131.0) corrected §15 8→7 (-2 weighted) on suppression/NoWarn hygiene drift plus the MAUI project sitting outside the CI-audited graph, the only score move of that cycle and the only rank change on either band. The §27 impl 8→9 pseudo-loc candidacy was adversarially rejected for a third consecutive cycle on byte-identical evidence and is now adjudicated DEFERRED rather than carried open. Prior basis follows: down from 86.3%, 690/800: §18 impl 9→8 (-3 weighted) on the code-behind flush at the enforced 400-line cap with six more files in the 360-379 band, TD-16. The §27 impl 8→9 pseudo-loc candidacy was adversarially rejected for a second consecutive cycle on unchanged evidence, 3 public pages of 36 routable. The twentieth-cycle §13 8→9 and §24 7→8 lifts, the seventeenth-cycle §27 7→8 lift, and the fourteenth-cycle §6 10→9 correction stand.) Re-confirmed unchanged by the twenty-third-cycle full re-score (2026-07-23, pin v1.123.0, no moves; every implementation score was then 8 or higher). From the twenty-fourth through the twenty-sixth cycle §15 at 7 was the only implementation score below 8. Re-confirmed again by the twenty-fifth-cycle full re-score (2026-08-01, pin v1.135.0): no score moved, and the six proposed lifts (§5, §13, §24, §27, §31, §33) were each adversarially rejected against current source, so the numerator was re-derived rather than inherited (Σ(I×w) = 685 re-summed that run); the twenty-sixth-cycle full re-score (2026-08-14, pin v1.152.0, HEAD 19021d93) rejected all eight proposed lifts (§5, §7, §12 as an M3→4, §13 as a 9→10, §15, §23, §28, §31) and re-summed the same 685.
  • The implementation index reads directly against 100% (recalibrated 2026-08-01): a 10 is awardable when an implementation is almost perfect (every criterion met at reference quality, no red flags, at most trivial polish left), superseding the rubric's literal "nothing left to improve" wording. The prior "9 is the top attainable rung / 90% ceiling" line is therefore retired and removed from this block. The denominators were never scaled (they stay ×10), so these percentages remain directly comparable to every pre-recalibration cycle. Backlog scheduling still targets 9, because ranking against 10 would put nearly every strong category in the band and drown the real gaps: the 9→10 rung is recognition earned at re-score time, not scheduled work.
  • -
  • Weaker axis: Implementation (execution quality), by ~11.6 points
  • +
  • Weaker axis: Implementation (execution quality), by ~12.2 points
  • N/A (excluded from denominators): none: §27 Internationalization is scored (M4/I8) as of ADR-027 (which supersedes the single-locale ADR-011), so no category is excluded from the denominators.
  • §32 weight = 2 (the default; raised to 3 only for the published framework MMCA.Common)

Top 5 strengths

    -
  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): Tests/Architecture/MMCA.ADC.Architecture.Tests/ with LayerDependencyTests, DomainPurityTests, MicroserviceExtractionTests (thin subclasses of the shared rule library), plus PiiConvention/Concurrency/IntegrationEventContract/Specification/SliceCohesion/DataResidency/ConstructorDependencyCount/BrandColorToken/UIArchitectureConvention/StateManagementConvention/ObservabilityConvention: 90 of the 91 methods inherited from the shared MMCA.Common.Testing.Architecture bases (ADR-015), leaving one ADC-local method (the TD-14 Profile-form guard); ADRs 001-078 (canonical in ../adr/) capture the 'why'. The counts are the 2026-07-28 measured snapshot; the twenty-fifth cycle did not re-run the suite.

    +
  2. 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): Tests/Architecture/MMCA.ADC.Architecture.Tests/ with LayerDependencyTests, DomainPurityTests, MicroserviceExtractionTests (thin subclasses of the shared rule library), plus PiiConvention/Concurrency/IntegrationEventContract/Specification/SliceCohesion/DataResidency/ConstructorDependencyCount/BrandColorToken/UIArchitectureConvention/StateManagementConvention/ObservabilityConvention: 90 of the 91 methods inherited from the shared MMCA.Common.Testing.Architecture bases (ADR-015), leaving one ADC-local method (the TD-14 Profile-form guard); ADRs 001-096 (canonical in ../adr/) capture the 'why'. The counts are the 2026-07-28 measured snapshot; the later cycles did not re-run the suite.

    • Remediation: Resolve the provenance nit, ArchitecturalAnalysis.md still lives at the untracked workspace root outside all three git repos (the arch-test doc-comment drift was already fixed in SpecificationConventionTests.cs).
    • Expected delta: Tracking the governance docs in-repo lifts §34 impl 9→10.
    • @@ -497,7 +497,7 @@

      Top 5 strengths

    • Expected delta: Automating the surge/revert lifts §31 impl 8→9.
  3. -
  4. Non-inverted test pyramid with a deploy-gated integration tier AND a deploy-gated chromium E2E/axe suite: §14 (mat 4 / impl 9) and §28 (mat 4 / impl 8): 1507 unit/UI Fact/Theory across 223 files; 91 architecture-test methods across 29 test classes; 303 gating integration methods across four per-service WAF tiers; the integration tier, a 55.5% unit-tier coverage floor, cost-guard, dr-freshness, and the chromium E2E gate all block deploy (deploy.yml:866,:254). E2E #5 is active again (un-quarantined 2026-07-19, plain [Fact] at SpeakerSelfServiceTests.cs:58) while the underlying split-query fix stays guarded by the active SessionIncludeChildrenRegressionTests.

    +
  5. Non-inverted test pyramid with a deploy-gated integration tier AND a deploy-gated chromium E2E/axe suite: §14 (mat 4 / impl 9) and §28 (mat 4 / impl 8): 1507 unit/UI Fact/Theory across 223 files; 91 architecture-test methods across 29 test classes; 303 gating integration methods across four per-service WAF tiers; the integration tier gates every PR as a required check, and the coverage floor, cost-guard, dr-freshness, and the chromium E2E gate sit in deploy.needs (deploy.yml:866,:254), though the E2E/axe/CWV gate is conditional: it runs only when the diff is UI-affecting and deploy accepts a skipped gate (deploy.yml:538,:896, recorded as TD-20). E2E #5 is active again (un-quarantined 2026-07-19, plain [Fact] at SpeakerSelfServiceTests.cs:58) while the underlying split-query fix stays guarded by the active SessionIncludeChildrenRegressionTests.

    • Remediation: Record the manual screen-reader pass (§21 maturity 4 lever). The firefox/webkit gating half regressed on 2026-07-18: the deploy gate now runs chromium only (deploy.yml:541), and the nightly was thinned again on 2026-07-29 to alternating single-engine legs (e2e.yml:49,:50), so §22 stays at maturity 3 and needs either the two legs restored to the gate or a cross-browser-freshness job.
    • Expected delta: SR pass lifts §21 maturity 3→4 (the last weight-3 maturity gap); a cross-browser freshness gate lifts §22 back to maturity 4.
    • @@ -521,7 +521,7 @@

      Top 5 risks

    • Expected delta: The managed-identity switch is DONE (§17 impl 8→9 realized). Closing the remaining public-network-access flag would lift §11 impl 9→10.
  6. -
  7. The enforced-analyzer perimeter has a documented hole, and one suppression has outlived its own removal condition: §15 (mat 4 / impl 7, weight 2), the top implementation-band item at implPriority 4 and unchanged at this cycle's HEAD. Enforcement inside CI.slnf is strong (five analyzers at error, TWAE, AnalysisMode=All, --locked-mode), but the MAUI MMCA.ADC.UI project sits outside every CI build (MMCA.ADC.CI.slnf lists only the two web UI hosts at :25,:26; no runner installs the maui-android workload), so analyzers and TWAE are review-only there and the gating vulnerable-package scan (deploy.yml:319, exit at :328) never audits that graph, which is precisely the graph the Directory.Build.props:8-12 suppressions exist for. Separately, the GHSA-2m69-gcr7-jv3q SQLite suppression (Directory.Build.props:54) is expired by its own written condition (:45-52): ADC now pins v1.152.0, Common removed its own entry and pins the patched bundle directly (3.0.5), and ADR-038 already records the accepted-advisory list as empty. Three of the four global NoWarn codes (:26) carry no justification or date (S8970, the fourth, does). This is hygiene drift and a scope gap, not a code-quality regression.

    +
  8. The enforced-analyzer perimeter has a documented hole, and one suppression has outlived its own removal condition: §15 (mat 4 / impl 7, weight 2), the joint-top implementation-band item at implPriority 4 (alongside §22, whose impl dropped to 7 this cycle) and unchanged at this cycle's HEAD. Enforcement inside CI.slnf is strong (five analyzers at error, TWAE, AnalysisMode=All, --locked-mode), but the MAUI MMCA.ADC.UI project sits outside every CI build (MMCA.ADC.CI.slnf lists only the two web UI hosts at :25,:26; no runner installs the maui-android workload), so analyzers and TWAE are review-only there and the gating vulnerable-package scan (deploy.yml:319, exit at :328) never audits that graph, which is precisely the graph the Directory.Build.props:8-12 suppressions exist for. Separately, the GHSA-2m69-gcr7-jv3q SQLite suppression (Directory.Build.props:54) is expired by its own written condition (:45-52): ADC now pins v1.160.0, twenty-five releases past the v1.121.0 SQLite sweep, Common removed its own entry and pins the patched bundle directly (3.0.5), and ADR-038 already records the accepted-advisory list as empty. Three of the four global NoWarn codes (:26) carry no justification or date (S8970, the fourth, does). This is hygiene drift and a scope gap, not a code-quality regression.

    • Remediation: Delete the expired suppression and justify-or-drop the three NoWarn codes (effort S), verified by a full-solution package-mode restore, not CI.slnf, since the MAUI graph is exactly what CI.slnf omits. If MAUI genuinely still needs the entry, narrow it to that project with a fresh dated justification rather than keeping a stale global one. Store carries the identical entry and should be swept in the same pass.
    • Expected delta: The two hygiene items lift §15 impl 7→8 (+2 weighted, restoring the index to 85.9%). Bringing MAUI inside a CI build is the 8→9 lever, recorded as TD-18 and deliberately deferred against the 2026-07-18 Actions-minute reduction.
    • diff --git a/docs/governance/adc-RemediationBacklog.html b/docs/governance/adc-RemediationBacklog.html index 147fd99..5c69c0c 100644 --- a/docs/governance/adc-RemediationBacklog.html +++ b/docs/governance/adc-RemediationBacklog.html @@ -114,7 +114,7 @@

      Architecture governance

      MMCA.ADC: Architecture Remediation Backlog

      -

      Derived from ArchitectureScorecard.md (single-axis 0-4, baseline 75%, 241/320, dated 2026-06-08). Current authoritative two-axis scores (twenty-sixth-cycle full re-score, 2026-08-14, pin v1.152.0, HEAD 19021d93): Maturity 97.2% (311/320) / Implementation 85.6% (685/800), no score move on either axis. All 34 categories were re-confirmed from evidence read this run, and all eight adversarial adjudications (§5, §7, §12, §13, §15, §23, §28, §31) were proposed lifts that were rejected, each one criterion short. See the Index note for the cycle record. +

      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, 2026-08-23, pin v1.160.0, HEAD 96f0919a): Maturity 97.2% (311/320) / Implementation 85.0% (680/800), two scores moved, both down on the implementation axis (§4 9→8, §22 8→7). All eight adversarial adjudications of proposed lifts (§5, §7, §15, §17, §18, §21, §28, §31) were rejected, each one criterion short. See the Index note for the cycle record. Tasks are ranked on both scorecard axes, one band per axis (two-axis policy adopted 2026-07-28):

      • Maturity band: every category scoring maturity < 4, ranked by priority = (4 − maturity) × weight.
      • @@ -125,9 +125,9 @@

        MMCA.ADC: Architecture Remedia

        This is the single remediation ledger. The former TECHDEBT.md tactical register is folded in here (2026-06-26): each deferred sub-item keeps its TD-NN ID and lives under its #NN category with its blocker, resolution path, and effort estimate; the recorded-but-not-scheduled choices live in the Deliberate / accepted section below. There is no separate tech-debt file (matching MMCA.Common and MMCA.Store). Effort key: S ≈ hours · M ≈ ~1 day · L ≈ multi-day.

        -

        ⚠️ Index note (2026-06-27). The 75% / 241-320 figure is the 2026-06-08 single-axis baseline and is not recomputed as items below are ticked: many already-RESOLVED rows (#11, #14, #26, #29, #30, #32, …) have moved the real total well past it. For the current, authoritative scores use the canonical, in-repo ArchitectureScorecard.md (two-axis, at framework v1.152.0: Maturity 97.2% (311/320) / Implementation 85.6% (685/800)). This backlog remains the living what-to-do-next checklist; trust the scorecard for scores. Closed 2026-06-26/27: #32 (TD-01 lock files + blocking supply-chain gates), the #14 coverage floor (TD-05), #26 (Gateway header-regression test), the #29 graceful-shutdown test (scorecard §29 impl 8→9), and #5 (slice-cohesion fitness function, scorecard §5 impl 7→8, on the v1.85.0 sweep). Closed on the v1.86.0 i18n + dark-mode sweep (2026-06-27): the #29 scheduled DR-drill gate (scorecard §29 maturity 3→4), #24 change-password client validation (scorecard §24 impl 8→9), the #20 landing-page brand-token dedupe (scorecard §20 impl 8→9), and #27 i18n flips from N/A to scored (M3/I8, ADR-027 supersedes 011). Activated 2026-06-28: managed-identity SQL DB auth in production (useManagedIdentitySql=true, scorecard §17 impl 8→9; #11 holds at 9, now capped only by the deferred public-network-access epic). The last big open lever is the E2E/axe merge gate (TD-06/07). Reconciled 2026-06-29 (re-score, pin v1.92.0): §29 was REOPENED (scorecard §29 corrected maturity 4→3: the dr-drill.yml cron is scheduled but gates nothing, so it is Consistent/M3 not an automatic CI gate, the same standard §28 is held to), and the prior "#29 DR-drill gate closed maturity 3→4" claim above is withdrawn; #16 was also reopened (scorecard §16 is maturity 3; deleting the orphan-test folder did not by itself reach 4); §32 moved impl 8→9 (CI restore already runs --locked-mode in both gating jobs, deploy.yml:40/:119); and the backlog was caught up to the scorecard by closing #6/#8/#17/#18/#20/#26/#30 (all already at maturity 4). Reconciled 2026-06-30 (enforcement-gate wave): #16/#24/#27/#29/#31 lifted maturity 3→4 by adding CI-enforced governance over already-strong implementation: #24 FormsConventionTests, #27 TranslationCompletenessTests, and #16 FrameworkVersionConsistencyTests run in the CI.slnf arch gate (locally verified green, 74/74 arch tests pass); #31 cost-guard and #29 dr-freshness are wired into deploy.needs (committed, activate on the next push). Reconciled 2026-06-30 (v1.92.0→v1.93.0 sweep, the Common tenth-wave): §5 Vertical Slice Architecture lifted maturity 3→4 (the slice-cohesion fitness function SliceCohesionTests is confirmed a CI merge gate in MMCA.ADC.CI.slnf), and §7 was adversarially FLAG-re-checked (a proposed impl 8→9 lift rejected) and confirmed unchanged at M4/I8. Scorecard now Maturity 94.1% / Implementation 85.9% (HEAD 89d8439, pin v1.93.0); the §21 a11y axe scans were broadened 10→17 pages (impl 7→8 pending a green nightly), and the recorded screen-reader pass remains the §21 maturity lever. Reconciled 2026-07-02 (re-score, pin v1.99.0): three honest recalibrations, no code regressions. §18 UI Architecture was REOPENED (scorecard §18 maturity 4→3: no automated §18 UI-architecture fitness gate exists, so the container/presentational + code-behind conventions are review-enforced only, making §18 Consistent/M3 not Optimized/M4; its prior maturity-4 "UI convention test" basis was actually the route-authorization tests, a §25 gate). §6 impl was corrected 10→9 (the idempotent inbox covers only 2 of 4 consumer services: Conference appsettings.json:32, Identity :29; Engagement/Notification carry none, so real levers remain and 10 was overstated). §27 impl was corrected 8→7 (residual hard-coded English is broader than exception-path only, plus no text-expansion test). Scorecard now Maturity 93.1% (298/320) / Implementation 85.8% (686/800) (pin v1.99.0); the "Scorecard now Maturity 94.1% / Implementation 85.9%" figure above is the frozen v1.93.0 provenance. Reconciled 2026-07-03 (sixteenth-cycle full re-score, pin v1.101.0, HEAD ac43c8d8, all 34 categories CONFIRMED): the 2026-07-02 e2e-gate promotion is now reflected in this ledger: #28 is CLOSED (scorecard §28 maturity 4: the chromium E2E/axe suite is an enforced deploy gate, deploy.yml:303-308 e2e-gate job + :343 in deploy.needs; TD-06 and TD-07 ticked), #21 re-ranked priority 6→3 (scorecard §21 M3/I8 via the same gate; the recorded SR pass remains the cheapest maturity lever), and #19 is REOPENED (scorecard §19 M3/I9: review-enforced conventions, no §19 fitness gate in Tests/Architecture/). One implementation recalibration: §24 impl 9→7 (per-form error summary only on the Profile form; the six create forms surface a generic validation snackbar; raw {ex.Message} in Profile snackbars), tracked as new TD-14 under #24 (the category header stays closed: maturity holds 4 on FormsConventionTests). Scorecard now Maturity 94.1% (301/320) / Implementation 85.6% (685/800) (pin v1.101.0); the 93.1%/85.8% figures in this note are the frozen v1.99.0 provenance. Reconciled 2026-07-03 (same-day i18n completion sweep, ADR-027 Decision 9): #27's impl lever CLOSED (scorecard §27 impl 7→8: zero residual literals, dual CI gates incl. the new LocalizedTextConventionTests, MudBlazor chrome + nav localized; a new impl 8→9 sub-item tracks extending the pseudo-loc text-expansion evidence to ADC pages), and TD-14 NARROWED (raw {ex.Message} snackbars eliminated; the Profile-form gate exclusion + per-form error summaries remain). Scorecard now Maturity 94.1% (301/320) / Implementation 85.8% (686/800). Reconciled 2026-07-06 (eighteenth-cycle full re-score, pin v1.106.0, HEAD 8fc9e0d2, all 34 categories CONFIRMED): every category re-confirmed at its prior score from evidence read this run (no moves). #8's TD-03 CLOSED: the optimistic-concurrency API round-trip is implemented and deploy-gated (EventDTO.cs:16 carries the RowVersion token via IConcurrencyAware, UpdateEventHandler.cs:34 stamps it with SetOriginalRowVersion, OrganizerConcurrencyTests.cs:26 asserts a stale token returns 409 inside the deploy-gating MMCA.ADC.Integration.slnf); scorecard §8 holds impl 9 because the round-trip is Conference-only. #6/TD-02 partially addressed: the genuine broker round-trip test landed as the non-gating nightly MMCA.ADC.CrossService.IntegrationTests (9 tests, Testcontainers RabbitMQ+SQL), so scorecard §6 holds impl 9; the 9→10 lever is now gating it plus enabling the inbox on all 4 consumer services. Evidence counts refreshed: arch-tests 23 classes / 25 files / 74 methods (all thin subclasses, 0 ADC-local), §14 unit 1507/223 plus integration 303 gating methods / four tiers + 9 non-gating CrossService, coverage floor 38→55.5% (actual ~57%), ADR set 001-038, §27 resx 40 base + 40 es. Scorecard indices hold Maturity 94.1% (301/320) / Implementation 85.8% (686/800). Reconciled 2026-07-10 (nineteenth-cycle full re-score, pin v1.110.0, HEAD 246a24dc, all 34 categories held): every category re-confirmed at its prior score from evidence read this run (no moves, no closures, no re-ranks; the below-4 set stays §12/§13/§18/§19/§21/§22/§23/§33 with priorities recomputed byte-identical, and every TD status is unchanged: done TD-01/03/04/05/09/10, open TD-02/06/07/08/13/14). Three first-pass move proposals were adversarially rejected as verified non-moves: §12 impl 8→9 (the Notification app stays pinned maxReplicas: 1, infra/main.bicep:1113, even though the v1.110.0 wave provisioned Azure Managed Redis Balanced B0 and scaled the REST services to maxReplicas: 2), §23 maturity 3→4 (the WebVitals budgets are advisory by design and no §23 fitness gate exists), and §34 impl 9→8 (no governance regression; the untracked workspace-root ArchitecturalAnalysis.md remains the already-weighed 9-not-10 lever). Evidence refresh: ADR set 001-041, pin v1.110.0, arch tests re-run green this cycle (74/74); a contradictory main.bicep Notification scale-pin comment (claiming no Redis backplane while the backplane key is injected at :1056) was corrected in place. Scorecard indices hold Maturity 94.1% (301/320) / Implementation 85.8% (686/800). Reconciled 2026-07-15 (twentieth-cycle full re-score, pin v1.116.0, HEAD 913d088a, five scores up): the remediation-wave candidacies recorded below were adjudicated. Accepted: #18 CLOSED (scorecard §18 maturity 3→4: UIArchitectureConventionTests in the CI.slnf arch gate), #19 CLOSED (scorecard §19 maturity 3→4: StateManagementConventionTests in the same gate, impl held at 9 after a first-pass 9→8 proposal was adversarially rejected as unsupported), #23 CLOSED for maturity (scorecard §23 maturity 3→4: the CWV budgets became enforced assertions inside the deploy-gating chromium e2e-gate on 2026-07-11, superseding the nineteenth-cycle advisory-by-design rejection; the WASM code-split/image sub-item stays open as impl polish), #13's impl half (scorecard §13 impl 8→9 on the SLO workbook + infra/OPERATIONS.md day-2 runbooks), and TD-14 confirmed (scorecard §24 impl 7→8). Rejected, headers corrected below: the #13 maturity 3→4 candidacy (runbooks/dashboards are review-enforced conventions and IaC, not CI-gated fitness functions, so §13 holds M3/I9 and REOPENS), the #22 maturity 3→4 candidacy (the firefox/webkit legs added to the e2e-gate run continue-on-error: true per e2e.yml:74, i.e. advisory inside the gate, so §22 holds M3/I8 and REOPENS), and the #33 impl 8→9 candidacy (broker parity local-RabbitMQ vs prod-Service-Bus is mitigated, not closed, per README.md:74, a live rubric red flag, so §33 holds M3/I8 and REOPENS). Also corrected in the scorecard: §28's false "E2E #5 un-skipped" claim (the test is re-quarantined at SpeakerSelfServiceTests.cs:57; score held M4/I8) and the §34 impl 9→8 downgrade re-rejected. Scorecard indices move to Maturity 96.6% (309/320) / Implementation 86.3% (690/800); the below-4 set narrows to §12/§13/§21/§22/§33. Reconciled 2026-07-17 (twenty-first-cycle full re-score, pin v1.117.0, HEAD c4c01aa5, two scores up): the two 2026-07-16 gate candidacies were adjudicated ACCEPTED. #13 CLOSED (scorecard §13 maturity 3→4: ObservabilityConventionTests machine-enforces the alert-to-runbook pairing in the CI.slnf arch gate, MMCA.ADC.CI.slnf:56 + deploy.yml:57,417; impl holds 9) and #22 CLOSED (scorecard §22 maturity 3→4: the deploy-gating e2e-gate passes all three engines, deploy.yml:309, and e2e.yml:78 scopes continue-on-error to scheduled nightly non-chromium legs, so every invoked engine can fail a deploy; impl holds 8). Rejected: the #27 impl 8→9 pseudo-loc candidacy (PseudoLocalizationTests.cs:51 covers 3 public pages of 30+, a partial extension; §27 holds M4/I8 as a verified non-move). Corrected: a stale nineteenth-cycle draft accidentally committed via PR #15 (2026-07-17) had relabeled the #12 header "RESOLVED M4/I8" and added a mislabeled "2026-07-12 twentieth-cycle" update paragraph; both are reverted below, and §12 stays M3/I8 open per the twentieth-cycle adjudication (re-confirmed this run: the k6 tier is freshness-gated via load-freshness, deploy.yml:348,417, but executes monthly/dispatch out of band, and Notification stays pinned maxReplicas: 1). #33 re-confirmed M3/I8 (the 2026-07-16 Service Bus emulator tier candidacy stands recorded for a future cycle; the tier is nightly, riding the freshness gate rather than in-band). Scorecard indices move to Maturity 97.8% (313/320) / Implementation 86.3% (690/800); the below-4 set narrows to §12/§21/§33. Reconciled 2026-07-21 (twenty-second-cycle full re-score, pin v1.121.0, HEAD 8509a05d, two scores down, neither a quality regression): #22 REOPENED (scorecard §22 maturity 4→3: the 2026-07-18 Actions-minute reduction cut the deploy e2e-gate to browsers: '["chromium"]', deploy.yml:488 with its rationale comment at :478-480, so firefox/webkit run only on the weeknight nightly schedule where e2e.yml:119 keeps them continue-on-error; cross-engine verification is nightly-advisory again, which is M3, and the trade-off is recorded in Deliberate / accepted with its scoring cost stated plainly). §18 implementation 9→8 (the category header stays closed, maturity holds 4 on the UIArchitectureConventionTests gate, but the largest code-behind sits flush at the enforced 400-line cap with zero headroom, HappeningNow.razor.cs:400 vs UIArchitectureConventionTestsBase.cs:22, plus six files in the 360-379 band; tracked as new TD-16 under #18, effort S). Rejected for a second consecutive cycle: the #27 impl 8→9 pseudo-loc candidacy (PseudoLocalizationTests.cs:51 still covers exactly 3 public pages of 36 routable pages, unchanged since the twenty-first-cycle rejection; §27 holds M4/I8). Sub-item closed: #12's deferred prod-Redis provisioning (Redis Enterprise is provisioned, infra/main.bicep:740,753,771), though the SignalR fan-out stays unexercised behind the maxReplicas: 1 pin (:1424), so #12 itself stays open. Corrected: #33's load-bearing README.md:74 quote no longer exists (the file now states the opposite at README.md:80-84, the Service Bus emulator tier having landed), and drifted anchors were refreshed repo-wide (CI.slnf:56:58, deploy.yml:303-309/343/348/417:483-489/:553/:783, e2e.yml:78:119, main.bicep:1113:1424, :341:488). Scorecard indices move to Maturity 97.2% (311/320) / Implementation 85.9% (687/800); the below-4 set widens to §12/§21/§22/§33. Reconciled 2026-07-23 (twenty-third-cycle full re-score, pin v1.123.0, HEAD 160f59f5, no moves): every category re-confirmed at its prior score from evidence read this run (no closures, no new items, no re-ranks, no TD changes; the below-4 set stays §12/§21/§22/§33 with priorities unchanged: #21 at 3, #12/#22/#33 at 2). Two first-pass maturity-lift proposals were adversarially rejected as verified non-moves: §12 M3→4 rejected (the k6 tier still runs monthly/dispatch out of band, load-test.yml:8, with load-freshness a recency-only deploy check, deploy.yml:553, and Notification pinned maxReplicas: 1, infra/main.bicep:1424) and §21 M3→4 rejected (the recorded manual screen-reader pass is still the empty placeholder in ACCESSIBILITY-SCREENREADER-PASS.md, remaining the cheapest maturity lever). The v1.122.0/v1.123.0 lockstep sweeps moved no score. Scorecard indices hold Maturity 97.2% (311/320) / Implementation 85.9% (687/800), ADR set 001-051. Verification pass 2026-07-23 (post-cycle, no score claims): stale claims corrected in place across this ledger and the scorecard: the #6 header's "2 of 4 services" inbox basis (all four services carry EnableInbox=true since the TD-02 close), #26's "pending manual Aspire verification + release" phrasing (shipped and deployed), the #29/#31 "activates on the next push" phrasing (gates live in deploy.needs since 2026-06-30), the scorecard's §8/§28 "re-quarantined" claim (E2E #5 was un-quarantined 2026-07-19, plain [Fact] at SpeakerSelfServiceTests.cs:58), lock-file count 58→65, resx pairs 40→53, MMCA.Common.* pin 1.117.0→1.123.0 in the §16/§32 rows, and drifted anchors (deploy.needs :783:791, load-freshness :548:553, coverage floor :83:210-212, sloWorkbook :278:425, Notification pin :1113:1424, CI.slnf:56:58, OrganizerConcurrencyTests.cs:26:27). The genuinely-open TD set today is TD-08, TD-15, TD-16 (older per-cycle "open TD-..." snapshots above are frozen provenance). The screen-reader-pass runbook lives centralized as adc-ACCESSIBILITY-SCREENREADER-PASS.md in Website docs-src/guides/ (2026-07-20 centralization); bare-name references below predate that move. Reconciled 2026-07-28 (twenty-fourth-cycle full re-score, pin v1.131.0, HEAD 2ec77796, one score down): §15 Best Practices & Code Quality implementation 8→7 (weight 2), the only score move and the only rank change on either axis; maturity holds 4, independently re-derived, so #15 stays in the protect set while taking the top row of the implementation band at implPriority 4. The basis is hygiene drift, not a code-quality regression: an audit suppression expired by its own written removal condition (Directory.Build.props:49-51 vs its comment at :41-48), three undated global NoWarn codes (:22), and the MAUI MMCA.ADC.UI project sitting outside every CI build and outside the CI-audited dependency graph (MMCA.ADC.CI.slnf:25, deploy.yml:288), which is precisely the graph the :8-12 suppressions exist for. Band totals move to 15 categories / 35 gap points (count unchanged, §15 was already in the band) and 95.1% of the 90% attainable ceiling. No closures: closure needs maturity 4 AND implementation >= 9 independently, and all four maturity-band items are still M3 and all four still I8, with none of the 15 implementation-band categories reaching 9, so nothing moves to the protect list and the maturity band is byte-identical (#21 at 3, #12/#22/#33 at 2, 4 categories / 9 points). Re-verified still-open levers: #21's screen-reader results log is still the empty _yyyy-mm-dd_ placeholder (adc-ACCESSIBILITY-SCREENREADER-PASS.md:60-62), #22 is still chromium-only gating (deploy.yml:505) with firefox/webkit advisory on the Mon/Thu schedule (e2e.yml:131, cron :43), #12 is still scale-pinned (infra/main.bicep:1447) with a monthly out-of-band capacity proof (load-test.yml:18). Adjudicated DEFERRED, not open: the #27 impl 8→9 pseudo-loc candidacy, rejected for a third time (21st, 22nd, 24th) on byte-identical evidence, is now recorded in Deliberate / accepted with its cost and explicit re-open triggers rather than carried as a live candidacy to re-reject a fourth time. Also re-rejected and recorded so they are not re-proposed: #24 impl 8→9 and #33 M3→4 / I8→9; #6 and #30 sit at I9, already at the scheduling target, so their recorded "9→10" candidacies are out of scope for both bands. New: TD-17 under #33 (the Service Bus emulator parity tier is now dispatch-only after hanging to its 8-minute timeout on 7 of 7 runs, so #33's header basis is corrected from "nightly plus recency gate" to "no schedule, no gate") and TD-18 under #15 (the MAUI CI-enforcement gap, recorded rather than fixed because a MAUI CI build cuts against the 2026-07-18 Actions-minute reduction). TD-16 refreshed, worse: HappeningNow.razor.cs is now exactly 400 lines against the enforced cap, and the recorded 360-379 band was stale (SpeakerDetail.razor.cs is 386, not 365); current measured set 400/386/379/376/367/365/362 with two recorded paths corrected. TD-15 was NOT re-verified this run and is left exactly as written; its figures are not restated as re-confirmed. Evidence refresh (no score move): the arch-test suite is now 29 test classes / 31 .cs files / 91 executed methods, re-run green 2026-07-28 (91/91), up from 26/28/82, and 90 of the 91 are inherited from the shared rule library after the §13 alert-runbook pairing gate was lifted upstream (ObservabilityConventionTests.cs:7 is now a bare thin subclass), leaving the TD-14 Profile-form guard (FormsConventionTests.cs:31) as the single ADC-local method; ADR set 001-060. Anchors refreshed repo-wide: deploy.yml e2e-gate :488→job at :500 with browsers: '["chromium"]' at :505 and rationale :478-480:493-499, deploy.needs :791:829, the freshness jobs re-split (cost-guard :488, dr-freshness :513, load-freshness :570, cross-service-freshness :627) with their skip checks at :526/:583/:642, e2e.yml:119:131, infra/main.bicep:1424:1447, load-test.yml:8:18, cross-service-tests.yml emulator job at :142 with its dispatch-only condition at :144. The genuinely-open TD set today is TD-08, TD-15, TD-16, TD-17, TD-18. Reconciled 2026-08-01 (twenty-fifth-cycle full re-score, pin v1.135.0, HEAD 995a7886, no moves): every category re-confirmed at its prior score from evidence read this run, so there are no closures, no new items and no re-ranks: both bands are byte-identical (maturity 4 categories / 9 points, #21 at 3 and #12/#22/#33 at 2; implementation 15 categories / 35 points). Closure needs maturity 4 AND implementation >= 9 independently, and all four maturity-band items are still M3/I8 while none of the 15 implementation-band categories reached 9. All six adversarial adjudications this cycle were proposed implementation lifts and all six were rejected: §5 8→9 (DTOs live in the Shared assembly with horizontal mapper/validation/specification folders, the layered-by-project hybrid is unchanged, and AdcArchitectureMap.cs:12-44 omits MMCA.ADC.Notification.Application from the enforced set), §13 9→10 (three ENABLED production alerts have no runbook triage section and sit outside the pairing gate's scope, including the sev-1 gateway-availability alert at infra/main.bicep:481), §24 8→9 (the named bUnit lever shipped, but client validation does not mirror the server's cross-field and format rules and the error summary covers 7 of 15 MudForm forms: both are now recorded as §24's levers, replacing "not yet identified"), §27 8→9 (fourth rejection, byte-identical evidence plus one new culture-formatting violation), §31 8→9 (the surge/revert automation is not pulled), and §33 8→9 (second rejection: see the rewritten TD-17 below). TD-17 is HALF CLOSED and its blocker text was invalid: the servicebus-emulator-smoke job is back on the weekday nightly since 2026-07-29 (cross-service-tests.yml:144-146 needs: should-run + if: needs.should-run.outputs.run == 'true' under cron: '0 6 * * 1-5' at :26,:30, timeout-minutes: 10 at :148), and the recorded root cause was wrong: the comment at :130-143 records per-test bus re-provisioning against an admin plane throttled at roughly 1 op/sec (IAsyncLifetime plus xUnit per-Fact class instantiation), fixed by hoisting the bus to the collection fixture and wall-clock bounding both startup phases, not the companion SQL image. The remaining half is open: the tier is continue-on-error: true (:149) and gates nothing, since cross-service-freshness keys off the cross-service job (:124-128, gate at deploy.yml:663). TD-16 re-measured, and the headline is no longer true: HappeningNow.razor.cs is 394, not 400, and the high-water mark moved to SessionSelectionDashboard.razor.cs at 395, so the "flush at the cap, zero headroom" framing is retired in favour of 5 lines of headroom; current measured set 395/394/386/376/367/365/362 at HEAD 995a7886, seven files within 38 lines of the 400 cap, still effort S. TD-08 and TD-18 re-confirmed open (TD-18's gating-scan anchor drifted deploy.yml:288:319). TD-15 was NOT re-verified for a second consecutive cycle and is left exactly as written; its cost figures are not restated as re-confirmed. Anchors refreshed repo-wide: deploy.yml e2e-gate :500:531 with browsers :505:541, deploy.needs :829:866, the freshness jobs re-split again (cost-guard :519, dr-freshness :549, load-freshness :606, cross-service-freshness :663) with their skip checks at :562/:619/:678, coverage floor :210-212:254, the gating vuln scan :288:319, --locked-mode restores at :199/:299, e2e.yml:131:144 with the nightly matrix replaced by alternating single-engine crons at :49,:50, infra/main.bicep:1447:1530 (and the SLO/budget/SQL anchors re-derived), Directory.Build.props suppression :49-51:54 and NoWarn :22:26, ADC pin :123:139 at 1.135.0, lock files 65→66, ADR set 001-064. Reconciled 2026-08-14 (twenty-sixth-cycle full re-score, pin v1.152.0, HEAD 19021d93, no moves): every category was re-confirmed at its prior score from evidence read this run, so again there are no closures, no new items and no re-ranks: both bands are byte-identical (maturity 4 categories / 9 points, §21 at 3 and §12/§22/§33 at 2; implementation 15 categories / 35 points). All eight adversarial adjudications were proposed lifts and all eight were rejected: §5 8→9 (DTOs and their mappers still outside the slice, and AdcArchitectureMap.cs:12-43 still has no Module("Notification", ...) entry, now named as TD-19), §7 8→9 (the bidirectional sync-gRPC red flag broadened to a second pair, Identity-Notification), §12 M3→4 (zero commits touched load-test.yml, deploy.yml or Tests/Load/ since the prior HEAD), §13 9→10 (three of the six ENABLED production alerts still have no runbook, including the sev-1 gateway-availability alert, whose anchor moves infra/main.bicep:481:496-502, severity: 1 at :502; §13 sits at I9, outside both bands), §15 7→8 (all three downgrade grounds intact, and the expired suppression is further past its removal condition now that the pin is v1.152.0), §23 8→9 (WASM code-split and image optimization both still open), §28 8→9 (the new state-management bUnit coverage is a within-band improvement) and §31 8→9 (the surge/revert automation is still not pulled, cost-guard.yml:4,:12,:17,:59,:83). New: TD-19 under §5 (the Notification module is absent from the enforced architecture map, effort S), which replaces §5's "lever not yet identified" band row. TD-16 re-measured, and the headroom narrowed: the high-water code-behind rose 395→398 of the 400 cap, leaving 2 lines rather than 5. TD-17 unchanged in substance, anchors corrected: job :145, needs :146, if :147, timeout-minutes :149, continue-on-error :150, schedule workflow_dispatch :26 + cron '0 6 * * 1-5' :31 (the recorded :26,:30 was wrong), gate-keying comment :126-129. TD-15 was NOT re-verified for a third consecutive cycle and is left exactly as written. Evidence refresh (no score move): axe coverage is 31 test methods over roughly 29 distinct pages (AccessibilityTests.cs:21-365), not 17 pages; the routable-page denominator is 49 @page files under Source (48 excluding the MAUI-only DeviceSettings.razor), not 37, so §27's deferred lift now costs roughly 45 pages; the Notification scale pin moves infra/main.bicep:1530:1616; §24's error-summary ratio is 8 of 18 MudForm-bearing pages (19 forms), not 7 of 15; TD-18's MAUI NoWarn CA5392 anchor moves MMCA.ADC.UI.csproj:131:143. Indices hold Maturity 97.2% (311/320) / Implementation 85.6% (685/800), ADR set 001-078.

        +

        ⚠️ Index note (2026-06-27). The 75% / 241-320 figure is the 2026-06-08 single-axis baseline and is not recomputed as items below are ticked: many already-RESOLVED rows (#11, #14, #26, #29, #30, #32, …) have moved the real total well past it. For the current, authoritative scores use the canonical, in-repo ArchitectureScorecard.md (two-axis, at framework v1.160.0: Maturity 97.2% (311/320) / Implementation 85.0% (680/800)). This backlog remains the living what-to-do-next checklist; trust the scorecard for scores. Closed 2026-06-26/27: #32 (TD-01 lock files + blocking supply-chain gates), the #14 coverage floor (TD-05), #26 (Gateway header-regression test), the #29 graceful-shutdown test (scorecard §29 impl 8→9), and #5 (slice-cohesion fitness function, scorecard §5 impl 7→8, on the v1.85.0 sweep). Closed on the v1.86.0 i18n + dark-mode sweep (2026-06-27): the #29 scheduled DR-drill gate (scorecard §29 maturity 3→4), #24 change-password client validation (scorecard §24 impl 8→9), the #20 landing-page brand-token dedupe (scorecard §20 impl 8→9), and #27 i18n flips from N/A to scored (M3/I8, ADR-027 supersedes 011). Activated 2026-06-28: managed-identity SQL DB auth in production (useManagedIdentitySql=true, scorecard §17 impl 8→9; #11 holds at 9, now capped only by the deferred public-network-access epic). The last big open lever is the E2E/axe merge gate (TD-06/07). Reconciled 2026-06-29 (re-score, pin v1.92.0): §29 was REOPENED (scorecard §29 corrected maturity 4→3: the dr-drill.yml cron is scheduled but gates nothing, so it is Consistent/M3 not an automatic CI gate, the same standard §28 is held to), and the prior "#29 DR-drill gate closed maturity 3→4" claim above is withdrawn; #16 was also reopened (scorecard §16 is maturity 3; deleting the orphan-test folder did not by itself reach 4); §32 moved impl 8→9 (CI restore already runs --locked-mode in both gating jobs, deploy.yml:40/:119); and the backlog was caught up to the scorecard by closing #6/#8/#17/#18/#20/#26/#30 (all already at maturity 4). Reconciled 2026-06-30 (enforcement-gate wave): #16/#24/#27/#29/#31 lifted maturity 3→4 by adding CI-enforced governance over already-strong implementation: #24 FormsConventionTests, #27 TranslationCompletenessTests, and #16 FrameworkVersionConsistencyTests run in the CI.slnf arch gate (locally verified green, 74/74 arch tests pass); #31 cost-guard and #29 dr-freshness are wired into deploy.needs (committed, activate on the next push). Reconciled 2026-06-30 (v1.92.0→v1.93.0 sweep, the Common tenth-wave): §5 Vertical Slice Architecture lifted maturity 3→4 (the slice-cohesion fitness function SliceCohesionTests is confirmed a CI merge gate in MMCA.ADC.CI.slnf), and §7 was adversarially FLAG-re-checked (a proposed impl 8→9 lift rejected) and confirmed unchanged at M4/I8. Scorecard now Maturity 94.1% / Implementation 85.9% (HEAD 89d8439, pin v1.93.0); the §21 a11y axe scans were broadened 10→17 pages (impl 7→8 pending a green nightly), and the recorded screen-reader pass remains the §21 maturity lever. Reconciled 2026-07-02 (re-score, pin v1.99.0): three honest recalibrations, no code regressions. §18 UI Architecture was REOPENED (scorecard §18 maturity 4→3: no automated §18 UI-architecture fitness gate exists, so the container/presentational + code-behind conventions are review-enforced only, making §18 Consistent/M3 not Optimized/M4; its prior maturity-4 "UI convention test" basis was actually the route-authorization tests, a §25 gate). §6 impl was corrected 10→9 (the idempotent inbox covers only 2 of 4 consumer services: Conference appsettings.json:32, Identity :29; Engagement/Notification carry none, so real levers remain and 10 was overstated). §27 impl was corrected 8→7 (residual hard-coded English is broader than exception-path only, plus no text-expansion test). Scorecard now Maturity 93.1% (298/320) / Implementation 85.8% (686/800) (pin v1.99.0); the "Scorecard now Maturity 94.1% / Implementation 85.9%" figure above is the frozen v1.93.0 provenance. Reconciled 2026-07-03 (sixteenth-cycle full re-score, pin v1.101.0, HEAD ac43c8d8, all 34 categories CONFIRMED): the 2026-07-02 e2e-gate promotion is now reflected in this ledger: #28 is CLOSED (scorecard §28 maturity 4: the chromium E2E/axe suite is an enforced deploy gate, deploy.yml:303-308 e2e-gate job + :343 in deploy.needs; TD-06 and TD-07 ticked), #21 re-ranked priority 6→3 (scorecard §21 M3/I8 via the same gate; the recorded SR pass remains the cheapest maturity lever), and #19 is REOPENED (scorecard §19 M3/I9: review-enforced conventions, no §19 fitness gate in Tests/Architecture/). One implementation recalibration: §24 impl 9→7 (per-form error summary only on the Profile form; the six create forms surface a generic validation snackbar; raw {ex.Message} in Profile snackbars), tracked as new TD-14 under #24 (the category header stays closed: maturity holds 4 on FormsConventionTests). Scorecard now Maturity 94.1% (301/320) / Implementation 85.6% (685/800) (pin v1.101.0); the 93.1%/85.8% figures in this note are the frozen v1.99.0 provenance. Reconciled 2026-07-03 (same-day i18n completion sweep, ADR-027 Decision 9): #27's impl lever CLOSED (scorecard §27 impl 7→8: zero residual literals, dual CI gates incl. the new LocalizedTextConventionTests, MudBlazor chrome + nav localized; a new impl 8→9 sub-item tracks extending the pseudo-loc text-expansion evidence to ADC pages), and TD-14 NARROWED (raw {ex.Message} snackbars eliminated; the Profile-form gate exclusion + per-form error summaries remain). Scorecard now Maturity 94.1% (301/320) / Implementation 85.8% (686/800). Reconciled 2026-07-06 (eighteenth-cycle full re-score, pin v1.106.0, HEAD 8fc9e0d2, all 34 categories CONFIRMED): every category re-confirmed at its prior score from evidence read this run (no moves). #8's TD-03 CLOSED: the optimistic-concurrency API round-trip is implemented and deploy-gated (EventDTO.cs:16 carries the RowVersion token via IConcurrencyAware, UpdateEventHandler.cs:34 stamps it with SetOriginalRowVersion, OrganizerConcurrencyTests.cs:26 asserts a stale token returns 409 inside the deploy-gating MMCA.ADC.Integration.slnf); scorecard §8 holds impl 9 because the round-trip is Conference-only. #6/TD-02 partially addressed: the genuine broker round-trip test landed as the non-gating nightly MMCA.ADC.CrossService.IntegrationTests (9 tests, Testcontainers RabbitMQ+SQL), so scorecard §6 holds impl 9; the 9→10 lever is now gating it plus enabling the inbox on all 4 consumer services. Evidence counts refreshed: arch-tests 23 classes / 25 files / 74 methods (all thin subclasses, 0 ADC-local), §14 unit 1507/223 plus integration 303 gating methods / four tiers + 9 non-gating CrossService, coverage floor 38→55.5% (actual ~57%), ADR set 001-038, §27 resx 40 base + 40 es. Scorecard indices hold Maturity 94.1% (301/320) / Implementation 85.8% (686/800). Reconciled 2026-07-10 (nineteenth-cycle full re-score, pin v1.110.0, HEAD 246a24dc, all 34 categories held): every category re-confirmed at its prior score from evidence read this run (no moves, no closures, no re-ranks; the below-4 set stays §12/§13/§18/§19/§21/§22/§23/§33 with priorities recomputed byte-identical, and every TD status is unchanged: done TD-01/03/04/05/09/10, open TD-02/06/07/08/13/14). Three first-pass move proposals were adversarially rejected as verified non-moves: §12 impl 8→9 (the Notification app stays pinned maxReplicas: 1, infra/main.bicep:1113, even though the v1.110.0 wave provisioned Azure Managed Redis Balanced B0 and scaled the REST services to maxReplicas: 2), §23 maturity 3→4 (the WebVitals budgets are advisory by design and no §23 fitness gate exists), and §34 impl 9→8 (no governance regression; the untracked workspace-root ArchitecturalAnalysis.md remains the already-weighed 9-not-10 lever). Evidence refresh: ADR set 001-041, pin v1.110.0, arch tests re-run green this cycle (74/74); a contradictory main.bicep Notification scale-pin comment (claiming no Redis backplane while the backplane key is injected at :1056) was corrected in place. Scorecard indices hold Maturity 94.1% (301/320) / Implementation 85.8% (686/800). Reconciled 2026-07-15 (twentieth-cycle full re-score, pin v1.116.0, HEAD 913d088a, five scores up): the remediation-wave candidacies recorded below were adjudicated. Accepted: #18 CLOSED (scorecard §18 maturity 3→4: UIArchitectureConventionTests in the CI.slnf arch gate), #19 CLOSED (scorecard §19 maturity 3→4: StateManagementConventionTests in the same gate, impl held at 9 after a first-pass 9→8 proposal was adversarially rejected as unsupported), #23 CLOSED for maturity (scorecard §23 maturity 3→4: the CWV budgets became enforced assertions inside the deploy-gating chromium e2e-gate on 2026-07-11, superseding the nineteenth-cycle advisory-by-design rejection; the WASM code-split/image sub-item stays open as impl polish), #13's impl half (scorecard §13 impl 8→9 on the SLO workbook + infra/OPERATIONS.md day-2 runbooks), and TD-14 confirmed (scorecard §24 impl 7→8). Rejected, headers corrected below: the #13 maturity 3→4 candidacy (runbooks/dashboards are review-enforced conventions and IaC, not CI-gated fitness functions, so §13 holds M3/I9 and REOPENS), the #22 maturity 3→4 candidacy (the firefox/webkit legs added to the e2e-gate run continue-on-error: true per e2e.yml:74, i.e. advisory inside the gate, so §22 holds M3/I8 and REOPENS), and the #33 impl 8→9 candidacy (broker parity local-RabbitMQ vs prod-Service-Bus is mitigated, not closed, per README.md:74, a live rubric red flag, so §33 holds M3/I8 and REOPENS). Also corrected in the scorecard: §28's false "E2E #5 un-skipped" claim (the test is re-quarantined at SpeakerSelfServiceTests.cs:57; score held M4/I8) and the §34 impl 9→8 downgrade re-rejected. Scorecard indices move to Maturity 96.6% (309/320) / Implementation 86.3% (690/800); the below-4 set narrows to §12/§13/§21/§22/§33. Reconciled 2026-07-17 (twenty-first-cycle full re-score, pin v1.117.0, HEAD c4c01aa5, two scores up): the two 2026-07-16 gate candidacies were adjudicated ACCEPTED. #13 CLOSED (scorecard §13 maturity 3→4: ObservabilityConventionTests machine-enforces the alert-to-runbook pairing in the CI.slnf arch gate, MMCA.ADC.CI.slnf:56 + deploy.yml:57,417; impl holds 9) and #22 CLOSED (scorecard §22 maturity 3→4: the deploy-gating e2e-gate passes all three engines, deploy.yml:309, and e2e.yml:78 scopes continue-on-error to scheduled nightly non-chromium legs, so every invoked engine can fail a deploy; impl holds 8). Rejected: the #27 impl 8→9 pseudo-loc candidacy (PseudoLocalizationTests.cs:51 covers 3 public pages of 30+, a partial extension; §27 holds M4/I8 as a verified non-move). Corrected: a stale nineteenth-cycle draft accidentally committed via PR #15 (2026-07-17) had relabeled the #12 header "RESOLVED M4/I8" and added a mislabeled "2026-07-12 twentieth-cycle" update paragraph; both are reverted below, and §12 stays M3/I8 open per the twentieth-cycle adjudication (re-confirmed this run: the k6 tier is freshness-gated via load-freshness, deploy.yml:348,417, but executes monthly/dispatch out of band, and Notification stays pinned maxReplicas: 1). #33 re-confirmed M3/I8 (the 2026-07-16 Service Bus emulator tier candidacy stands recorded for a future cycle; the tier is nightly, riding the freshness gate rather than in-band). Scorecard indices move to Maturity 97.8% (313/320) / Implementation 86.3% (690/800); the below-4 set narrows to §12/§21/§33. Reconciled 2026-07-21 (twenty-second-cycle full re-score, pin v1.121.0, HEAD 8509a05d, two scores down, neither a quality regression): #22 REOPENED (scorecard §22 maturity 4→3: the 2026-07-18 Actions-minute reduction cut the deploy e2e-gate to browsers: '["chromium"]', deploy.yml:488 with its rationale comment at :478-480, so firefox/webkit run only on the weeknight nightly schedule where e2e.yml:119 keeps them continue-on-error; cross-engine verification is nightly-advisory again, which is M3, and the trade-off is recorded in Deliberate / accepted with its scoring cost stated plainly). §18 implementation 9→8 (the category header stays closed, maturity holds 4 on the UIArchitectureConventionTests gate, but the largest code-behind sits flush at the enforced 400-line cap with zero headroom, HappeningNow.razor.cs:400 vs UIArchitectureConventionTestsBase.cs:22, plus six files in the 360-379 band; tracked as new TD-16 under #18, effort S). Rejected for a second consecutive cycle: the #27 impl 8→9 pseudo-loc candidacy (PseudoLocalizationTests.cs:51 still covers exactly 3 public pages of 36 routable pages, unchanged since the twenty-first-cycle rejection; §27 holds M4/I8). Sub-item closed: #12's deferred prod-Redis provisioning (Redis Enterprise is provisioned, infra/main.bicep:740,753,771), though the SignalR fan-out stays unexercised behind the maxReplicas: 1 pin (:1424), so #12 itself stays open. Corrected: #33's load-bearing README.md:74 quote no longer exists (the file now states the opposite at README.md:80-84, the Service Bus emulator tier having landed), and drifted anchors were refreshed repo-wide (CI.slnf:56:58, deploy.yml:303-309/343/348/417:483-489/:553/:783, e2e.yml:78:119, main.bicep:1113:1424, :341:488). Scorecard indices move to Maturity 97.2% (311/320) / Implementation 85.9% (687/800); the below-4 set widens to §12/§21/§22/§33. Reconciled 2026-07-23 (twenty-third-cycle full re-score, pin v1.123.0, HEAD 160f59f5, no moves): every category re-confirmed at its prior score from evidence read this run (no closures, no new items, no re-ranks, no TD changes; the below-4 set stays §12/§21/§22/§33 with priorities unchanged: #21 at 3, #12/#22/#33 at 2). Two first-pass maturity-lift proposals were adversarially rejected as verified non-moves: §12 M3→4 rejected (the k6 tier still runs monthly/dispatch out of band, load-test.yml:8, with load-freshness a recency-only deploy check, deploy.yml:553, and Notification pinned maxReplicas: 1, infra/main.bicep:1424) and §21 M3→4 rejected (the recorded manual screen-reader pass is still the empty placeholder in ACCESSIBILITY-SCREENREADER-PASS.md, remaining the cheapest maturity lever). The v1.122.0/v1.123.0 lockstep sweeps moved no score. Scorecard indices hold Maturity 97.2% (311/320) / Implementation 85.9% (687/800), ADR set 001-051. Verification pass 2026-07-23 (post-cycle, no score claims): stale claims corrected in place across this ledger and the scorecard: the #6 header's "2 of 4 services" inbox basis (all four services carry EnableInbox=true since the TD-02 close), #26's "pending manual Aspire verification + release" phrasing (shipped and deployed), the #29/#31 "activates on the next push" phrasing (gates live in deploy.needs since 2026-06-30), the scorecard's §8/§28 "re-quarantined" claim (E2E #5 was un-quarantined 2026-07-19, plain [Fact] at SpeakerSelfServiceTests.cs:58), lock-file count 58→65, resx pairs 40→53, MMCA.Common.* pin 1.117.0→1.123.0 in the §16/§32 rows, and drifted anchors (deploy.needs :783:791, load-freshness :548:553, coverage floor :83:210-212, sloWorkbook :278:425, Notification pin :1113:1424, CI.slnf:56:58, OrganizerConcurrencyTests.cs:26:27). The genuinely-open TD set today is TD-08, TD-15, TD-16 (older per-cycle "open TD-..." snapshots above are frozen provenance). The screen-reader-pass runbook lives centralized as adc-ACCESSIBILITY-SCREENREADER-PASS.md in Website docs-src/guides/ (2026-07-20 centralization); bare-name references below predate that move. Reconciled 2026-07-28 (twenty-fourth-cycle full re-score, pin v1.131.0, HEAD 2ec77796, one score down): §15 Best Practices & Code Quality implementation 8→7 (weight 2), the only score move and the only rank change on either axis; maturity holds 4, independently re-derived, so #15 stays in the protect set while taking the top row of the implementation band at implPriority 4. The basis is hygiene drift, not a code-quality regression: an audit suppression expired by its own written removal condition (Directory.Build.props:49-51 vs its comment at :41-48), three undated global NoWarn codes (:22), and the MAUI MMCA.ADC.UI project sitting outside every CI build and outside the CI-audited dependency graph (MMCA.ADC.CI.slnf:25, deploy.yml:288), which is precisely the graph the :8-12 suppressions exist for. Band totals move to 15 categories / 35 gap points (count unchanged, §15 was already in the band) and 95.1% of the 90% attainable ceiling. No closures: closure needs maturity 4 AND implementation >= 9 independently, and all four maturity-band items are still M3 and all four still I8, with none of the 15 implementation-band categories reaching 9, so nothing moves to the protect list and the maturity band is byte-identical (#21 at 3, #12/#22/#33 at 2, 4 categories / 9 points). Re-verified still-open levers: #21's screen-reader results log is still the empty _yyyy-mm-dd_ placeholder (adc-ACCESSIBILITY-SCREENREADER-PASS.md:60-62), #22 is still chromium-only gating (deploy.yml:505) with firefox/webkit advisory on the Mon/Thu schedule (e2e.yml:131, cron :43), #12 is still scale-pinned (infra/main.bicep:1447) with a monthly out-of-band capacity proof (load-test.yml:18). Adjudicated DEFERRED, not open: the #27 impl 8→9 pseudo-loc candidacy, rejected for a third time (21st, 22nd, 24th) on byte-identical evidence, is now recorded in Deliberate / accepted with its cost and explicit re-open triggers rather than carried as a live candidacy to re-reject a fourth time. Also re-rejected and recorded so they are not re-proposed: #24 impl 8→9 and #33 M3→4 / I8→9; #6 and #30 sit at I9, already at the scheduling target, so their recorded "9→10" candidacies are out of scope for both bands. New: TD-17 under #33 (the Service Bus emulator parity tier is now dispatch-only after hanging to its 8-minute timeout on 7 of 7 runs, so #33's header basis is corrected from "nightly plus recency gate" to "no schedule, no gate") and TD-18 under #15 (the MAUI CI-enforcement gap, recorded rather than fixed because a MAUI CI build cuts against the 2026-07-18 Actions-minute reduction). TD-16 refreshed, worse: HappeningNow.razor.cs is now exactly 400 lines against the enforced cap, and the recorded 360-379 band was stale (SpeakerDetail.razor.cs is 386, not 365); current measured set 400/386/379/376/367/365/362 with two recorded paths corrected. TD-15 was NOT re-verified this run and is left exactly as written; its figures are not restated as re-confirmed. Evidence refresh (no score move): the arch-test suite is now 29 test classes / 31 .cs files / 91 executed methods, re-run green 2026-07-28 (91/91), up from 26/28/82, and 90 of the 91 are inherited from the shared rule library after the §13 alert-runbook pairing gate was lifted upstream (ObservabilityConventionTests.cs:7 is now a bare thin subclass), leaving the TD-14 Profile-form guard (FormsConventionTests.cs:31) as the single ADC-local method; ADR set 001-060. Anchors refreshed repo-wide: deploy.yml e2e-gate :488→job at :500 with browsers: '["chromium"]' at :505 and rationale :478-480:493-499, deploy.needs :791:829, the freshness jobs re-split (cost-guard :488, dr-freshness :513, load-freshness :570, cross-service-freshness :627) with their skip checks at :526/:583/:642, e2e.yml:119:131, infra/main.bicep:1424:1447, load-test.yml:8:18, cross-service-tests.yml emulator job at :142 with its dispatch-only condition at :144. The genuinely-open TD set today is TD-08, TD-15, TD-16, TD-17, TD-18. Reconciled 2026-08-01 (twenty-fifth-cycle full re-score, pin v1.135.0, HEAD 995a7886, no moves): every category re-confirmed at its prior score from evidence read this run, so there are no closures, no new items and no re-ranks: both bands are byte-identical (maturity 4 categories / 9 points, #21 at 3 and #12/#22/#33 at 2; implementation 15 categories / 35 points). Closure needs maturity 4 AND implementation >= 9 independently, and all four maturity-band items are still M3/I8 while none of the 15 implementation-band categories reached 9. All six adversarial adjudications this cycle were proposed implementation lifts and all six were rejected: §5 8→9 (DTOs live in the Shared assembly with horizontal mapper/validation/specification folders, the layered-by-project hybrid is unchanged, and AdcArchitectureMap.cs:12-44 omits MMCA.ADC.Notification.Application from the enforced set), §13 9→10 (three ENABLED production alerts have no runbook triage section and sit outside the pairing gate's scope, including the sev-1 gateway-availability alert at infra/main.bicep:481), §24 8→9 (the named bUnit lever shipped, but client validation does not mirror the server's cross-field and format rules and the error summary covers 7 of 15 MudForm forms: both are now recorded as §24's levers, replacing "not yet identified"), §27 8→9 (fourth rejection, byte-identical evidence plus one new culture-formatting violation), §31 8→9 (the surge/revert automation is not pulled), and §33 8→9 (second rejection: see the rewritten TD-17 below). TD-17 is HALF CLOSED and its blocker text was invalid: the servicebus-emulator-smoke job is back on the weekday nightly since 2026-07-29 (cross-service-tests.yml:144-146 needs: should-run + if: needs.should-run.outputs.run == 'true' under cron: '0 6 * * 1-5' at :26,:30, timeout-minutes: 10 at :148), and the recorded root cause was wrong: the comment at :130-143 records per-test bus re-provisioning against an admin plane throttled at roughly 1 op/sec (IAsyncLifetime plus xUnit per-Fact class instantiation), fixed by hoisting the bus to the collection fixture and wall-clock bounding both startup phases, not the companion SQL image. The remaining half is open: the tier is continue-on-error: true (:149) and gates nothing, since cross-service-freshness keys off the cross-service job (:124-128, gate at deploy.yml:663). TD-16 re-measured, and the headline is no longer true: HappeningNow.razor.cs is 394, not 400, and the high-water mark moved to SessionSelectionDashboard.razor.cs at 395, so the "flush at the cap, zero headroom" framing is retired in favour of 5 lines of headroom; current measured set 395/394/386/376/367/365/362 at HEAD 995a7886, seven files within 38 lines of the 400 cap, still effort S. TD-08 and TD-18 re-confirmed open (TD-18's gating-scan anchor drifted deploy.yml:288:319). TD-15 was NOT re-verified for a second consecutive cycle and is left exactly as written; its cost figures are not restated as re-confirmed. Anchors refreshed repo-wide: deploy.yml e2e-gate :500:531 with browsers :505:541, deploy.needs :829:866, the freshness jobs re-split again (cost-guard :519, dr-freshness :549, load-freshness :606, cross-service-freshness :663) with their skip checks at :562/:619/:678, coverage floor :210-212:254, the gating vuln scan :288:319, --locked-mode restores at :199/:299, e2e.yml:131:144 with the nightly matrix replaced by alternating single-engine crons at :49,:50, infra/main.bicep:1447:1530 (and the SLO/budget/SQL anchors re-derived), Directory.Build.props suppression :49-51:54 and NoWarn :22:26, ADC pin :123:139 at 1.135.0, lock files 65→66, ADR set 001-064. Reconciled 2026-08-14 (twenty-sixth-cycle full re-score, pin v1.152.0, HEAD 19021d93, no moves): every category was re-confirmed at its prior score from evidence read this run, so again there are no closures, no new items and no re-ranks: both bands are byte-identical (maturity 4 categories / 9 points, §21 at 3 and §12/§22/§33 at 2; implementation 15 categories / 35 points). All eight adversarial adjudications were proposed lifts and all eight were rejected: §5 8→9 (DTOs and their mappers still outside the slice, and AdcArchitectureMap.cs:12-43 still has no Module("Notification", ...) entry, now named as TD-19), §7 8→9 (the bidirectional sync-gRPC red flag broadened to a second pair, Identity-Notification), §12 M3→4 (zero commits touched load-test.yml, deploy.yml or Tests/Load/ since the prior HEAD), §13 9→10 (three of the six ENABLED production alerts still have no runbook, including the sev-1 gateway-availability alert, whose anchor moves infra/main.bicep:481:496-502, severity: 1 at :502; §13 sits at I9, outside both bands), §15 7→8 (all three downgrade grounds intact, and the expired suppression is further past its removal condition now that the pin is v1.152.0), §23 8→9 (WASM code-split and image optimization both still open), §28 8→9 (the new state-management bUnit coverage is a within-band improvement) and §31 8→9 (the surge/revert automation is still not pulled, cost-guard.yml:4,:12,:17,:59,:83). New: TD-19 under §5 (the Notification module is absent from the enforced architecture map, effort S), which replaces §5's "lever not yet identified" band row. TD-16 re-measured, and the headroom narrowed: the high-water code-behind rose 395→398 of the 400 cap, leaving 2 lines rather than 5. TD-17 unchanged in substance, anchors corrected: job :145, needs :146, if :147, timeout-minutes :149, continue-on-error :150, schedule workflow_dispatch :26 + cron '0 6 * * 1-5' :31 (the recorded :26,:30 was wrong), gate-keying comment :126-129. TD-15 was NOT re-verified for a third consecutive cycle and is left exactly as written. Evidence refresh (no score move): axe coverage is 31 test methods over roughly 29 distinct pages (AccessibilityTests.cs:21-365), not 17 pages; the routable-page denominator is 49 @page files under Source (48 excluding the MAUI-only DeviceSettings.razor), not 37, so §27's deferred lift now costs roughly 45 pages; the Notification scale pin moves infra/main.bicep:1530:1616; §24's error-summary ratio is 8 of 18 MudForm-bearing pages (19 forms), not 7 of 15; TD-18's MAUI NoWarn CA5392 anchor moves MMCA.ADC.UI.csproj:131:143. Indices hold Maturity 97.2% (311/320) / Implementation 85.6% (685/800), ADR set 001-078. Reconciled 2026-08-23 (twenty-seventh-cycle full re-score, pin v1.160.0, HEAD 96f0919a, two scores down): §4 Domain-Driven Design implementation 9→8 (weight 3; public-setter cross-aggregate navigations on Session/Sponsor/Activity, aggregate-external validation of Event's newer optional fields against the repo's own Sponsor convention, and Event.OrganizerContactEmail as a raw string where the Email VO covers the same concept on User/Speaker) and §22 Responsive & Cross-Browser implementation 8→7 (weight 2; the rubric's density-options criterion has zero adoption and content reflow is only partial on the 17 non-DataGrid table pages), so the implementation band grows to 16 categories / 40 gap points: §4 enters the band for the first time (implPriority 3, maturity 4 holds, so #4 stays in the protect set) and §22 rises to the joint top at implPriority 4 alongside §15. No closures (all four maturity-band items still M3 with their levers re-verified open: the SR-pass log still the empty placeholder at adc-ACCESSIBILITY-SCREENREADER-PASS.md:62, #12 still scale-pinned at infra/main.bicep:1648 with its rationale at :1643-1647, #22 still chromium-only at deploy.yml:541, #33's parity tier still advisory at cross-service-tests.yml:150), and the maturity band is byte-identical for a fourth consecutive cycle (4 categories / 9 points). All eight adversarial adjudications were proposed lifts and all eight were rejected (§5, §7, §15, §17 as a 9→10, §18, §21 as an M3→4 + I8→9 pair, §28, §31). New: TD-20 under #28 (the deploy-gating chromium E2E/axe/CWV suite is CONDITIONAL: e2e-gate runs only when the changes job marks the diff UI-affecting, deploy.yml:538 with rationale :533-537, and the deploy job accepts a skipped gate, :896 with comment :880-883, so a backend-only, infra-only or script-only merge deploys with no browser, axe or CWV run; a matching amendment is recorded in Deliberate / accepted), which also names §28's previously unidentified band lever. Wording corrected ledger-wide: the integration-tests job is PR-only (if: github.event_name == 'pull_request', deploy.yml:389) and is NOT in deploy.needs (:866), so the "gates every deploy" / "deploy-gating MMCA.ADC.Integration.slnf" phrasing under #30/#14/#11/#8/#9 is rewritten to "gates every PR (required check on an up-to-date branch)"; TD-03's closure itself stands. TD-16 re-measured, unchanged at the top but wider: high-water 398/394/386 identical to 2026-08-14, but the within-38-lines set grew from seven to eight files (three grew: PublicSessionList.razor.cs 367→398, ADCHome.razor.cs 341→380, EventDetail.razor.cs 365→377), so TWO files now sit at 398. TD-17/TD-18/TD-19 re-confirmed open on current anchors; TD-15 NOT re-verified for a fourth consecutive cycle (no billing read; figures stand as written). Provenance: the §15 band row's "pins v1.135.0 at Directory.Packages.props:139" is doubly stale, now v1.160.0 at Directory.Packages.props:92-110, twenty-five releases past the v1.121.0 SQLite sweep. Indices move to Maturity 97.2% (311/320) / Implementation 85.0% (680/800), ADR set 001-096.

        -

        Scope: 4 categories remain below maturity 4 (§12/§21/§22/§33; the 2026-07-21 twenty-second-cycle reconciliation REOPENED §22 after the 2026-07-18 CI-minute reduction cut the deploy e2e-gate to chromium only, deploy.yml:541, leaving firefox/webkit nightly-advisory, e2e.yml:144, and thinner still since the 2026-07-29 move to alternating single-engine legs, e2e.yml:49,:50); 30 categories score maturity 4 (protect, don't regress); none are N/A. On the implementation axis, 15 categories score implementation <= 8** and are ranked in their own band below (35 gap points); **19 categories sit at maturity 4 AND implementation >= 9, which is the only combination that reaches the protect list.

        +

        Scope: 4 categories remain below maturity 4 (§12/§21/§22/§33; the 2026-07-21 twenty-second-cycle reconciliation REOPENED §22 after the 2026-07-18 CI-minute reduction cut the deploy e2e-gate to chromium only, deploy.yml:541, leaving firefox/webkit nightly-advisory, e2e.yml:144, and thinner still since the 2026-07-29 move to alternating single-engine legs, e2e.yml:49,:50); 30 categories score maturity 4 (protect, don't regress); none are N/A. On the implementation axis, 16 categories score implementation <= 8** and are ranked in their own band below (40 gap points; §4 joined and §22 deepened on 2026-08-23); **18 categories sit at maturity 4 AND implementation >= 9, which is the only combination that reaches the protect list.

        High-leverage fixes that each clear or relieve several items: do them once:

          @@ -159,7 +159,7 @@

          Add an integration/E2E test asserting header presence so it can't regress. → DONE (2026-06-27): MMCA.ADC.Gateway.Tests/SecurityHeadersTests boots the real Gateway via WebApplicationFactory<Program> (no SQL, runs in the fast CI tier / CI.slnf) and asserts /alive carries X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy, Permissions-Policy, CSP frame-ancestors 'none', and HSTS (Production env). A refactor dropping UseCommonSecurityHeaders() now fails CI.
        • Shorten the 30-day refresh cookie; consider SameSite=Strict. → DONE (2026-06-27, with recorded SameSite decision): the session/refresh cookie is already 7 days (not 30): SessionCookieJar (MMCA.Common.API) pins Lifetime = TimeSpan.FromDays(7), "aligned to the refresh-token lifetime so a cookie never outlives the credential it carries." SameSite=Strict is deliberately NOT adopted: SameSite=Lax is load-bearing for the SSR-prerender path ([Authorize] pages opened in a new tab / on F5 / following an external link are cross-site top-level navigations that Strict would strip the cookie from, forcing a spurious /login bounce, the exact scenario ADR-022's cookie scheme exists to serve); CSRF is covered defense-in-depth by the /auth/session/token endpoint's Sec-Fetch-Site check + POST-only + SameSite=Lax.
        -

        [x] #28 · Front-End Testing & Quality · 3 → 4 (weight 3) · RESOLVED 2026-07-02, reconciled here 2026-07-03 (scorecard §28 maturity 4 / impl 8): the chromium E2E/axe suite is an enforced deploy gate (e2e-gate in deploy.needs, deploy.yml:303-308,:343; e2e.yml:31 workflow_call), closing TD-06 and TD-07. Firefox/webkit stay advisory nightly (#22); visual-regression snapshots remain optional polish

        +

        [x] #28 · Front-End Testing & Quality · 3 → 4 (weight 3) · RESOLVED 2026-07-02, reconciled here 2026-07-03 (scorecard §28 maturity 4 / impl 8): the chromium E2E/axe suite is an enforced deploy gate (e2e-gate in deploy.needs, deploy.yml:303-308,:343; e2e.yml:31 workflow_call), closing TD-06 and TD-07. Firefox/webkit stay advisory nightly (#22); visual-regression snapshots remain optional polish. Qualified 2026-08-23 (TD-20): the gate is conditional since 2026-07-29: e2e-gate runs only when the diff is UI-affecting (deploy.yml:538) and deploy accepts a skipped gate (:896), so a backend-only merge deploys with no browser run

        Only one UI test level exists (manual, non-gated E2E).

        • (Medium) UI E2E suite excluded from CI: no front-end merge gate. deploy.yml:40-48 runs only CI.slnf; E2E needs the full Aspire stack and is run manually, so UI regressions can merge to prod undetected.
        • @@ -170,7 +170,7 @@

          Add a bUnit component-test project (conditional rendering / edge states). → DONE (3 module projects): MMCA.ADC.Conference.UI.Tests (bUnit v2 harness, MudServices + loose JSInterop + permissive-auth doubles so <AuthorizeView> renders), in CI.slnf, covering the three public detail pages (Event/Speaker/Session: loaded vs not-found) plus the Session page's <AuthorizeView> action bar (hidden anonymous / shown authenticated); Identity.UI.Tests (a mutable-auth harness, since Identity pages inject AuthenticationStateProvider directly): Profile loaded/error-state bUnit tests + the /users authz fitness test; and Engagement.UI.Tests covering both feedback forms: EventFeedbackTests (dynamic question render by type + per-question upsert skipping unanswered) and now SessionFeedbackTests (2026-06-27): precondition gating (BR-16 unscheduled / BR-91 service / BR-49 status block the form), session-not-found error state, question render by type, and upsert-only-answered. List pages deliberately skipped for bUnit: DataGridListPageBase is infra-heavy (7 injected services + JS interop/PersistentComponentState); its plumbing belongs to MMCA.Common's own tests, the derived page logic is thin.
        • Add a route-authorization fitness test, ManagementRouteAuthorizationTests (reflection over Conference.UI): admin-namespace pages must keep [Authorize(Roles="Organizer")], the set is asserted non-empty (no vacuous pass), and public pages must stay anonymous at the page level. Closes the #25 residual.
        • -
        • Wire axe-core (Deque.AxeCore.Playwright) + ≥1 a11y assertion (TD-06) → DONE (2026-07-02, ticked on the 2026-07-03 reconciliation): the axe-core AccessibilityTests (17 pages, Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/AccessibilityTests.cs) run inside the deploy-gating chromium e2e-gate job (e2e.yml:236 runs the whole E2E project; deploy.yml:343 puts e2e-gate in deploy.needs), so the a11y assertions now gate every deploy.
        • +
        • Wire axe-core (Deque.AxeCore.Playwright) + ≥1 a11y assertion (TD-06) → DONE (2026-07-02, ticked on the 2026-07-03 reconciliation): the axe-core AccessibilityTests (17 pages, Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/AccessibilityTests.cs) run inside the deploy-gating chromium e2e-gate job (e2e.yml:236 runs the whole E2E project; deploy.yml:343 puts e2e-gate in deploy.needs), so the a11y assertions gate every UI-affecting deploy (conditionality recorded 2026-08-23 as TD-20: a skipped gate does not block a non-UI merge, deploy.yml:538,:896).
        • Make a smoke E2E subset an automatic merge gate (TD-07) → DONE (2026-07-02, exceeded): the full chromium suite (not just a smoke subset) is the deploy-gating e2e-gate job (deploy.yml:303-308 uses: ./.github/workflows/e2e.yml with browsers='["chromium"]'; e2e.yml:31 workflow_call), promoted after validation run 28604877733 (first fully green three-browser matrix). The former Blazor-Server-under-load blocker was resolved by the E2E_FORCE_SERVER pin + reload-and-rewait fixes (see the 2026-07-02 notes below).
        • Add Playwright visual-regression snapshots for key pages.
        @@ -213,7 +213,7 @@

        Implement a real erasure path: IAnonymizable + anonymize-on-delete (immediate erasure). (A scheduled-purge backstop for rows soft-deleted by other paths is optional now that delete erases inline.)
      • Redact/tokenize PII before logging: done in UserRegisteredHandler.
      • Add an export/access endpoint: GET /users/{userId}/export (Identity-owned data); cross-service bookmark/notification aggregation is the remaining piececross-service aggregation DONE 2026-07-11 (remediation wave 6): the export now aggregates Engagement (session bookmarks + submitted live-Q&A questions, new user_engagement_export.proto rpc mirroring the bookmark-count pattern) and Notification (inbox items, new user_notification_export.proto rpc on the existing ADR-012 grpc ingress; a new Notification.Shared layer carries the boundary per module-isolation rules). Aggregation is best-effort per section (Available=false + empty lists when a peer is down after the Polly pipeline; the export never fails on a peer outage). Identity gains gRPC edges to both peers (AppHost WithReference without deadlocking WaitFor; bicep env mirroring the existing gRPC-edge mechanism). 9 handler unit tests + a payload-shape integration test (faked peers). Recorded follow-up, deliberately out of scope: event/session feedback answers live in the Conference DB (EventQuestionAnswer/SessionQuestionAnswer), so full-corpus export would need a third (Conference) edge; the recorded §30 residual named only bookmarks + notifications, both now covered. §30 Implementation 9→10 candidacy recorded for the next re-score.
      • -
      • Add a fitness/integration test proving an erasure path exists and that PII is not logged: domain unit tests added; the end-to-end erasure + no-PII-in-logs assertion rides the #14 integration-tier rework. SHIPPED 2026-07-16: ErasureAndPiiLoggingTests (Identity integration tier, deploy-gating): (1) a deleted account is erased from every API surface end to end (login 401, export 404, listing clean) through the real host pipeline; (2) a full register-login-delete lifecycle emits ZERO log lines carrying the account's email or names (every host log line captured via the new PiiLogCapture sink in the test factory, asserted against unique markers). §30 I9→10 candidacy already recorded stands on stronger evidence.
      • +
      • Add a fitness/integration test proving an erasure path exists and that PII is not logged: domain unit tests added; the end-to-end erasure + no-PII-in-logs assertion rides the #14 integration-tier rework. SHIPPED 2026-07-16: ErasureAndPiiLoggingTests (Identity integration tier, gating every PR as a required check; wording corrected 2026-08-23, the integration-tests job is PR-only, deploy.yml:389, not in deploy.needs): (1) a deleted account is erased from every API surface end to end (login 401, export 404, listing clean) through the real host pipeline; (2) a full register-login-delete lifecycle emits ZERO log lines carrying the account's email or names (every host log line captured via the new PiiLogCapture sink in the test factory, asserted against unique markers). §30 I9→10 candidacy already recorded stands on stronger evidence.
        @@ -241,19 +241,19 @@

        🟡 Priority 3: score 3, weight 3 (one rung from a 4)

        [x] #14 · Testability & Test Strategy: 3 → 4 · RESOLVED (see IntegrationTestReworkPlan.md)

          -
        • (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 WebApplicationFactory<Program> tiers (Identity/Conference/Engagement, ~345 tests) over a SQL-service CI container, plus the revived MMCA.Common.API middleware unit tests. In-process JWT override (for AddForwardedJwtBearer), gRPC fakes, broker InProcess short-circuit, Respawn reset. Runs via MMCA.ADC.Integration.slnf and gates every deploy (integration-tests job in deploy.yml, a required dep of deploy). All CI-verified green.
        • +
        • (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 WebApplicationFactory<Program> tiers (Identity/Conference/Engagement, ~345 tests) over a SQL-service CI container, plus the revived MMCA.Common.API middleware unit tests. In-process JWT override (for AddForwardedJwtBearer), gRPC fakes, broker InProcess short-circuit, Respawn reset. Runs via MMCA.ADC.Integration.slnf and gates every PR (required check on an up-to-date branch; wording corrected 2026-08-23: the integration-tests job is PR-only, if: github.event_name == 'pull_request' at deploy.yml:389, and protects production through branch protection, not deploy.needs). All CI-verified green.

        Fix

        • Rework integration tests against the new per-service hosts and re-include them.
        • Wire coverage collection (TD-05, done 2026-06-26): coverage is collected via dotnet-coverage (cobertura) and gated by a 55.5% unit-tier line-coverage floor (ADC's own +MMCA.ADC.*;-*.Tests code, ratcheted to 55.5 after the 2026-07 coverage program, actual ~57%) that hard-fails the deploy-gating build-and-test PR job (deploy.yml:210-212). No longer report-only.
        • -
        • Cross-service handler coverage (Phase 4 headline flows): the consumer-side logic is now re-homed as in-process integration tests on the per-service fixtures (resolve the real IIntegrationEventHandler<T> from the booted host, assert against the real DB; runtime-gated by the SQL integration-tests job): Conference.IntegrationTests/CrossService/CrossServiceUserRegisteredTests.cs (BR-207 name-match auto-link / ambiguous-skip / no-match-skip) + Identity.IntegrationTests/CrossService/CrossServiceSpeakerLinkTests.cs (SpeakerLinkedToUser/SpeakerUnlinkedFromUser set/clear User.LinkedSpeakerId). Pairs with OutboxFidelityTests (which covered the producer side only). Added via a small additive Services accessor on both fixtures; compile 0/0.
        • +
        • Cross-service handler coverage (Phase 4 headline flows): the consumer-side logic is now re-homed as in-process integration tests on the per-service fixtures (resolve the real IIntegrationEventHandler<T> from the booted host, assert against the real DB; PR-gated by the SQL integration-tests job): Conference.IntegrationTests/CrossService/CrossServiceUserRegisteredTests.cs (BR-207 name-match auto-link / ambiguous-skip / no-match-skip) + Identity.IntegrationTests/CrossService/CrossServiceSpeakerLinkTests.cs (SpeakerLinkedToUser/SpeakerUnlinkedFromUser set/clear User.LinkedSpeakerId). Pairs with OutboxFidelityTests (which covered the producer side only). Added via a small additive Services accessor on both fixtures; compile 0/0.
        • [~] Phase 4 broker-transport tier (TD-02), landed 2026-07-06 as a non-gating nightly: the genuine MassTransit broker round-trip (Testcontainers RabbitMQ + dual-host transport/outbox fidelity, not just handler logic) now runs as MMCA.ADC.CrossService.IntegrationTests (9 tests) on cross-service-tests.yml. Optional remaining coverage: speaker analytics and the Conference→Engagement bookmark-count gRPC reads. Making the tier a deploy gate is the shared §6 impl 9→10 lever (see TD-02 under #6).

        [x] #11 · Security: 3 → 4 · RESOLVED

        • (Medium) Rate limiter is inert: named policies but no GlobalLimiter/[EnableRateLimiting]. RESOLVED: MMCA.Common 1.54.0's AddCommonRateLimiting now attaches a GlobalLimiter (429 over 300 req/min per authenticated user; partition name→user_id→IP). Anonymous traffic is deliberately unlimited (public endpoints output-cached, login has its own protection, and Blazor-Server anonymous traffic shares the UI host IP); health//alive/JWKS/application/grpc bypassed. Swept to all 7 services (ADC + Store) on the 1.54.0 bump; CLAUDE.md "100 req/min" claims corrected.
        • -
        • (Medium) No automated server-side authorization gate. RESOLVED: the #14 per-service tier includes the access-denied authz matrices (anonymous→401, attendee→403 across all services, ~55 tests), gating every deploy.
        • +
        • (Medium) No automated server-side authorization gate. RESOLVED: the #14 per-service tier includes the access-denied authz matrices (anonymous→401, attendee→403 across all services, ~55 tests), gating every PR (required check; wording corrected 2026-08-23).
        • (Medium) Prod secrets in Container App secrets + ACR admin password: not a vault/managed identity.

        Fix

        @@ -277,11 +277,11 @@

        Add automated a11y checks and a stated WCAG 2.1 AA target → DONE: Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/AccessibilityTests.cs runs axe-core WCAG 2.1 AA scans (broadened to 17 pages on 2026-06-30; 31 axe test methods over roughly 29 distinct pages as re-counted 2026-08-14, :21-365); the target is stated in CLAUDE.md and ACCESSIBILITY-SCREENREADER-PASS.md. (Deploy-gated since 2026-07-02: the scans ride the chromium e2e-gate job in deploy.needs.) +
      • Add automated a11y checks and a stated WCAG 2.1 AA target → DONE: Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/AccessibilityTests.cs runs axe-core WCAG 2.1 AA scans (broadened to 17 pages on 2026-06-30; 31 axe test methods over roughly 29 distinct pages as re-counted 2026-08-14, :21-365); the target is stated in CLAUDE.md and ACCESSIBILITY-SCREENREADER-PASS.md. (Deploy-gated since 2026-07-02: the scans ride the chromium e2e-gate job in deploy.needs; conditional since 2026-07-29 per TD-20, so a non-UI merge deploys without an axe run. Coverage note 2026-08-23: four routable pages shipped 2026-08-19 with no axe coverage yet.)
      • (impl 7→8 lever) Stand up a backend-less in-process axe merge-gate, mirroring MMCA.Common's gallery-host pattern → SUPERSEDED (2026-07-02): the full axe suite became the deploy-gating e2e-gate, which delivered the impl 8 and the enforcement this scoped backend-less host targeted, so the separate host is no longer needed for the score. (Still available as an architecture option if the full-suite gate ever has to be demoted.)
      • (maturity 3→4, cheapest open win) Record a dated manual screen-reader pass in ACCESSIBILITY-SCREENREADER-PASS.md (needs a human + NVDA/VoiceOver against the running Aspire app; cannot be done headless, so it stays pending a human run).
      • (NEW 2026-07-12, latent contrast in state-gated Warning-outlined surfaces, effort S.) Store's gated axe scan caught that an OUTLINED MudAlert Severity="Severity.Warning" renders its text in the Warning amber (#F57F17, ~2.6:1 on white, AA fail) the moment a state-gated banner actually rendered during a scan (Store run 29191273727; fixed there by switching to Severity.Info outlined). ADC carries the same latent pattern in at least SpeakerDashboard.razor:37 and SessionFeedback.razor:29 (plus amber Variant.Outlined Color.Warning buttons on EventDetail.razor:141 and the bookmarked-state toggle on PublicSessionDetail.razor:136); the 17-page axe gate is green only because those states are not exercised by the scans. FIXED 2026-07-16 (all six sites, two more than recorded): the four outlined Warning alerts switched to Severity.Info outlined (Store parity; the sweep also caught PresenterView.razor:21 and SessionLive.razor:21), and the two outlined amber buttons moved to the AA-passing Secondary teal (EventDetail Unpublish, and the bookmarked state of PublicSessionDetail's toggle, whose filled-star icon keeps the state signal). Repo-wide grep for outlined Warning surfaces is now zero. CI.slnf 2073 green.
      • -
      • (shared with #28) Promote the full axe + E2E suite to a merge gate → DONE (2026-07-02): promoted as the chromium e2e-gate in deploy.needs after validation run 28604877733 (the first fully green three-browser matrix); firefox/webkit stay advisory on the nightly (#22).
      • +
      • (shared with #28) Promote the full axe + E2E suite to a merge gate → DONE (2026-07-02): promoted as the chromium e2e-gate in deploy.needs after validation run 28604877733 (the first fully green three-browser matrix); firefox/webkit stay advisory on the nightly (#22). (Conditional since 2026-07-29, TD-20: runs only on UI-affecting diffs.)

      [x] #18 · UI Architecture & Component Design · 3 → 4 (weight 3) · RESOLVED 2026-07-15 (twentieth-cycle re-score: scorecard §18 maturity 3→4 CONFIRMED on the UIArchitectureConventionTests CI.slnf gate; implementation holds 9). The 2026-07-02 reopening (no §18 UI-architecture fitness gate; the route-auth tests were a §25 gate wrongly credited here) is answered by the wave-2 gate below

        @@ -292,7 +292,7 @@

        Add component tests (shared with #28) + a UI convention test: bUnit projects shipped. The "UI convention test" credited here was ManagementRouteAuthorizationTests, which is a route-authorization gate (§25), not a §18 UI-architecture gate, so it did not on its own earn maturity 4 (corrected on the 2026-07-02 re-score).
      • (maturity 3→4 lever) DONE 2026-07-11 (remediation wave 2): the §18 UI-architecture fitness gate now runs in the CI.slnf arch gate: 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. Verified non-vacuous via a seeded 402-line file. Subsumed TD-13 (below) and additionally forced conforming splits of SessionLive.razor.cs 648→357 (three extracted panels) and PublicSessionList.razor.cs 499→371 (filter bar + view components), which had grown past the cap since TD-13 was recorded. Repo-wide max code-behind is now 387 lines. Candidacy CONFIRMED on the 2026-07-15 twentieth-cycle re-score: scorecard §18 maturity 3→4.
      • TD-13 DONE 2026-07-11 (remediation wave 2, subsumed by the §18 gate above): both named code-behinds split via presentational sub-component extraction, markup moved verbatim (rendered DOM unchanged for the E2E selectors): SessionSelectionDashboard.razor.cs 507→367 (extracted SessionSelectionSpeakerOverlap, SessionSelectionAiScores, and the pure-rules SessionSelectionDisplay helper) and SpeakerDetail.razor.cs 429→368 (extracted SpeakerCategoryItemsPanel). Conference UI bUnit suite green (105/105) after each split.
      • -
      • TD-16 (recorded 2026-07-21, the §18 impl 8→9 lever, effort S): seven code-behinds sit within 38 lines of the convention ceiling, the MaxCodeBehindLines => 400 cap (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/UIArchitectureConventionTestsBase.cs:22), so a method added to any of them fails the gate rather than being caught in review. Re-measured 2026-08-14 (twenty-sixth cycle) at HEAD 19021d93, and the headroom is narrowing again. The high-water mark is Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/SessionSelection/SessionSelectionDashboard.razor.cs at 398, up from 395, so headroom against the 400 cap fell from 5 lines to 2; SessionDetail.razor.cs also rose 376→382. Current measured set: SessionSelectionDashboard 398, HappeningNow 394, SpeakerDetail 386, SessionDetail 382, PublicSessionList 367 (Pages/Public/), EventDetail 365, SessionLive 362 (the twenty-fifth cycle read 395/394/386/376/367/365/362 at HEAD 995a7886; the "flush at the cap, zero headroom" framing stays retired). The item stays open: seven files in a 38-line band is still one refactor away from a red gate. Blocker: none, this is scheduled work. Resolution path: presentational sub-component extraction per the TD-13 pattern above, markup moved verbatim so the rendered DOM and the E2E selectors are unchanged. Effort: S. This is what took scorecard §18 implementation from 9 to 8 in the twenty-second cycle; maturity holds 4 on the gate.
      • +
      • TD-16 (recorded 2026-07-21, the §18 impl 8→9 lever, effort S): seven code-behinds sit within 38 lines of the convention ceiling, the MaxCodeBehindLines => 400 cap (MMCA.Common/Source/Hosting/MMCA.Common.Testing.Architecture/Bases/UIArchitectureConventionTestsBase.cs:22), so a method added to any of them fails the gate rather than being caught in review. Re-measured 2026-08-14 (twenty-sixth cycle) at HEAD 19021d93, and the headroom is narrowing again. The high-water mark is Source/Modules/Conference/MMCA.ADC.Conference.UI/Pages/SessionSelection/SessionSelectionDashboard.razor.cs at 398, up from 395, so headroom against the 400 cap fell from 5 lines to 2; SessionDetail.razor.cs also rose 376→382. Current measured set: SessionSelectionDashboard 398, HappeningNow 394, SpeakerDetail 386, SessionDetail 382, PublicSessionList 367 (Pages/Public/), EventDetail 365, SessionLive 362 (the twenty-fifth cycle read 395/394/386/376/367/365/362 at HEAD 995a7886; the "flush at the cap, zero headroom" framing stays retired). Re-measured 2026-08-23 (twenty-seventh cycle) at HEAD 96f0919a: the top is unchanged (SessionSelectionDashboard 398 / HappeningNow 394 / SpeakerDetail 386, still 2 lines of headroom) but the band WIDENED from seven to eight files, three of which grew since 2026-08-14: PublicSessionList.razor.cs 367→398 (a second file at 398), ADCHome.razor.cs 341→380, and EventDetail.razor.cs 365→377. The item stays open: eight files in a 38-line band is still one refactor away from a red gate. Blocker: none, this is scheduled work. Resolution path: presentational sub-component extraction per the TD-13 pattern above, markup moved verbatim so the rendered DOM and the E2E selectors are unchanged. Effort: S. This is what took scorecard §18 implementation from 9 to 8 in the twenty-second cycle; maturity holds 4 on the gate.

      [x] #8 · Data Architecture · 3 → 4 · RESOLVED 2026-06-29 (scorecard §8 maturity 4 / impl 9); TD-03 concurrency round-trip CLOSED 2026-07-06 (implemented + deploy-gated, Conference-only, so impl holds 9)

        @@ -304,7 +304,7 @@

        Migration model-drift gate: build-and-test now runs dotnet ef migrations has-pending-model-changes for all four modules (Identity/Conference/Engagement/Notification) on the Release build (--no-build, no DB needed). Fails the build (and so the deploy) if an entity changed without a matching migration. Verified locally: all four currently report "No changes" (drift-free).
      • Soft-delete fidelity test: SoftDeleteFidelityTests (Conference integration tier) deletes an Event via the API, asserts it's hidden by the EF global query filter (404), and reads [Conference].[Event] directly to prove the row survives with IsDeleted = 1 (soft- not hard-delete). The fixture now exposes its ConnectionString for raw-table assertions.
      • Outbox-dispatch fidelity: OutboxFidelityTests (Identity tier) registers a user and asserts a UserRegistered row landed in [dbo].[OutboxMessages] (confirmed InProcessEventBus.PublishAsync persists the row transactionally, then marks it processed, the row is retained). The Identity fixture now exposes ConnectionString. (TD-04: done 2026-06-13, effort S.)
      • -
      • TD-03 RESOLVED (2026-07-06): optimistic-concurrency API round-trip now implemented and deploy-gated. The Conference EventDTO carries the RowVersion token via IConcurrencyAware (Conference.Shared/Events/EventDTO.cs:16), UpdateEventHandler.cs:34 stamps the client's last-seen token with SetOriginalRowVersion (a stale token then raises DbUpdateConcurrencyException, which DbUpdateExceptionHandler maps to 409), and OrganizerConcurrencyTests.cs:27 (Update_WithStaleRowVersion_ReturnsConflict) asserts the 409 inside the deploy-gating MMCA.ADC.Integration.slnf (the integration-tests job is in deploy.needs). Round-trip is Conference-only (Identity/Engagement expose no token-carrying update endpoint), so scorecard §8 holds impl 9 (not 10). The Common extension point (SetOriginalRowVersion on the repository) shipped and ADC adopted it on the five Conference update handlers.
      • +
      • TD-03 RESOLVED (2026-07-06): optimistic-concurrency API round-trip now implemented and deploy-gated. The Conference EventDTO carries the RowVersion token via IConcurrencyAware (Conference.Shared/Events/EventDTO.cs:16), UpdateEventHandler.cs:34 stamps the client's last-seen token with SetOriginalRowVersion (a stale token then raises DbUpdateConcurrencyException, which DbUpdateExceptionHandler maps to 409), and OrganizerConcurrencyTests.cs:27 (Update_WithStaleRowVersion_ReturnsConflict) asserts the 409 inside the PR-gating MMCA.ADC.Integration.slnf (wording corrected 2026-08-23: the integration-tests job is a required PR check, PR-only at deploy.yml:389, not in deploy.needs). Round-trip is Conference-only (Identity/Engagement expose no token-carrying update endpoint), so scorecard §8 holds impl 9 (not 10). The Common extension point (SetOriginalRowVersion on the repository) shipped and ADC adopted it on the five Conference update handlers.

      [x] #1 · SOLID Principles (3 → 4 · ctor-dependency-count fitness threshold landed (scorecard §1 stays M4/I9) protect)

        @@ -321,7 +321,7 @@

        [x] #9 · API & Cont
      • (Medium) No OpenAPI served by any running service, yet CLAUDE.md still advertises 4 doc UIs (/swagger, /nswag-swagger, /api-docs, /scalar/v1).
      • Serve OpenAPI per service → all four service hosts now register AddOpenApi() + map /openapi/v1.json (built-in Microsoft.AspNetCore.OpenApi, package wired via the .Service convention in Directory.Build.props). Mapped outside Production only: these are internal services reached through the Gateway, which does not route the endpoint. The ApiExplorer group ('v'VVVv1) matches the default document name, so the controller surface populates.
      • Fixed the stale CLAUDE.md OpenAPI bullet (the four advertised UIs were a carry-over from the deleted WebAPI host; corrected to the /openapi/v1.json document).
      • -
      • Contract test (OpenApiContractTests in MMCA.ADC.Conference.IntegrationTests) boots the real host and asserts the document is served, is well-formed OpenAPI 3.x describing ≥ 10 routes, and still exposes the core public resources (/Events, /Sessions, /Speakers): so an accidental route removal fails CI. Runs in the integration-tests tier, which gates deploy.
      • +
      • Contract test (OpenApiContractTests in MMCA.ADC.Conference.IntegrationTests) boots the real host and asserts the document is served, is well-formed OpenAPI 3.x describing ≥ 10 routes, and still exposes the core public resources (/Events, /Sessions, /Speakers): so an accidental route removal fails CI. Runs in the integration-tests tier, which gates every PR as a required check (wording corrected 2026-08-23).
      • Versioning proven beyond v1.0 (2026-06-19). ServiceInfoController (Conference) serves /ServiceInfo at v1.0 (deprecated) and v2.0, selected by the api-version header: exercising MapToApiVersion routing + deprecation reporting (ReportApiVersions). ApiVersioningTests (integration tier) asserts each version returns its own shape and that the api-supported-versions / api-deprecated-versions headers are emitted, so the versioning machinery is exercised, not merely configured for a single version.
      • Deferred: interactive UI (Scalar/Swagger). The three REST services are h2c-only on cleartext, so a browser can't reach a service-hosted UI directly; a Gateway-routed UI is a small follow-up if wanted.
      @@ -391,12 +391,12 @@

      Idempotent inbox enabled on the consumers (2026-06-19). MessageBus:EnableInbox=true in Identity.Service + Conference.Service appsettings (the two services that consume integration events; each already ships the InboxMessages table via its AddInboxMessages migration). Dedup is now verified in MMCA.Common by EfInboxStoreTests (real SQLite + the production unique index → a redelivered message id records exactly once). Converts consumer idempotency from convention to infrastructure.
    • *§6 Implementation 9→10 lever, TD-02 CLOSED 2026-07-11 (remediation wave 6):* both remaining pieces landed. (1) The broker round-trip now gates the deploy via recency: a cross-service-freshness job in deploy.yml's needs fails a deploy when the latest successful nightly cross-service-tests.yml run is older than 3 days (the dr/load-freshness pattern; the Testcontainers workflow itself still never runs inside the deploy chain, which the Docker constraint forbids and its header comment now documents). (2) MessageBus:EnableInbox=true on all four consumer services: Engagement and Notification appsettings joined Conference + Identity (their InboxMessages tables shipped with the 2026-06-09 AddInboxMessages migrations, applied in prod by the sole-migrator startup path). §6 Implementation 9→10 candidacy recorded for the next re-score. (Historical context: the tier landed 2026-07-06 as 9 Testcontainers RabbitMQ+SQL dual-host tests.)
    -

    [~] #12 · Performance & Scalability · 3 → 4 (weight 2, priority (4-3)×2=2) · OPEN at scorecard §12 M3/I8 (twentieth-cycle adjudication, re-confirmed 2026-07-17; a stale nineteenth-cycle "RESOLVED 2026-07-12 M4/I8" header accidentally committed via PR #15 is corrected here). The k6 proof's recency gates the deploy (load-freshness, deploy.yml:570, in deploy.needs at :829) and the WebVitals budgets are enforced inside the e2e-gate (§23's credit), but the k6 tier itself executes monthly/dispatch out of band (load-test.yml:18) and the Notification app stays pinned maxReplicas: 1 (infra/main.bicep:1447), so maturity holds 3. Re-confirmed 2026-07-21 (twenty-second cycle), with one nuance newly verified: all three recency gates accept a skip_freshness_gates dispatch input with a required justification (checks at deploy.yml:526,583,642), so the k6 recency proof is bypassable-with-justification rather than unconditional (see Deliberate / accepted). Re-confirmed again 2026-07-28 (twenty-fourth cycle) and 2026-08-01 (twenty-fifth cycle) at M3/I8, substance unchanged both times; all anchors in this header were refreshed again on 2026-08-01 (load-freshness :570:606, deploy.needs :829:866, the Notification pin infra/main.bicep:1447:1530, refreshed again 2026-08-14 to :1616 (scale block) with its right-sizing rationale in the comment ending :1614, the break-glass checks :526,583,642:562,619,678)

    +

    [~] #12 · Performance & Scalability · 3 → 4 (weight 2, priority (4-3)×2=2) · OPEN at scorecard §12 M3/I8 (twentieth-cycle adjudication, re-confirmed 2026-07-17; a stale nineteenth-cycle "RESOLVED 2026-07-12 M4/I8" header accidentally committed via PR #15 is corrected here). The k6 proof's recency gates the deploy (load-freshness, deploy.yml:570, in deploy.needs at :829) and the WebVitals budgets are enforced inside the e2e-gate (§23's credit), but the k6 tier itself executes monthly/dispatch out of band (load-test.yml:18) and the Notification app stays pinned maxReplicas: 1 (infra/main.bicep:1447), so maturity holds 3. Re-confirmed 2026-07-21 (twenty-second cycle), with one nuance newly verified: all three recency gates accept a skip_freshness_gates dispatch input with a required justification (checks at deploy.yml:526,583,642), so the k6 recency proof is bypassable-with-justification rather than unconditional (see Deliberate / accepted). Re-confirmed again 2026-07-28 (twenty-fourth cycle) and 2026-08-01 (twenty-fifth cycle) at M3/I8, substance unchanged both times; all anchors in this header were refreshed again on 2026-08-01 (load-freshness :570:606, deploy.needs :829:866, the Notification pin infra/main.bicep:1447:1530, refreshed again 2026-08-14 to :1616 (scale block) with its right-sizing rationale in the comment ending :1614, refreshed again 2026-08-23 to :1648 with the rationale at :1643-1647, the break-glass checks :526,583,642:562,619,678)

    • No load testingDONE: the k6 conference-read-load.js load test runs in CI sized to the measured ~67 peak. The SignalR multi-replica/backplane risk is resolved into a documented single-replica acceptance (Notification pinned maxReplicas: 1, main.bicep:1007-1012).
    • (impl-8 lever) Add client-side Core Web Vitals measurement to the E2E suiteDONE 2026-06-30: a WebVitalsTests Playwright tier (Tests/E2E/MMCA.ADC.E2E.Tests/Workflows/WebVitalsTests.cs + Infrastructure/WebVitalsCollector.cs) injects PerformanceObservers to capture LCP/CLS/FCP/TTFB on /, /conference/events, /login (plus a single-interaction INP sample on the data-grid page), asserts lenient budgets, and emits a dated web-vitals-*.json artifact (wired into e2e.yml via WEB_VITALS_OUTPUT_DIR). Both the backend k6 and the client-side vitals are now measured, closing the residual gap and lifting scorecard §12 Implementation 7→8. Test/CI-only (no MMCA.Common release); builds clean. (Maturity held at 3: the vitals run nightly/dispatch like k6, not as a merge gate.)
    • Deferred (optional), provisioning half DONE: prod Redis is provisioned (infra/main.bicep:740 Microsoft.Cache/redisEnterprise@2024-09-01-preview, database at :753, redis-connection-string secret injected at :771,849-850), so the shared cache / SignalR backplane substrate exists. The fan-out itself stays unexercised: Notification is still pinned maxReplicas: 1 (infra/main.bicep:1447, deliberate right-sizing rationale at :1443-1446; anchors refreshed 2026-07-28), so a verified two-replica hub fan-out remains the §12 impl 8→9 lever and this category stays open.
    • -
    • (maturity 3→4 lever) DONE 2026-07-11 (remediation wave 3): the capacity checks 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 624ms, 4-30x headroom), asserted inside the deploy-gating chromium e2e-gate (e2e.yml runs the whole E2E project). Adjudicated 2026-07-15 (twentieth-cycle re-score): the §23 half was ACCEPTED (scorecard §23 maturity 3→4 on the enforced CWV budgets) but the §12 half was REJECTED: §12 holds M3/I8 (the k6 tier is freshness-gated but still nightly/manual in execution, and the Notification app stays pinned maxReplicas: 1, infra/main.bicep:1113), so this category stays open at maturity 3.
    • +
    • (maturity 3→4 lever) DONE 2026-07-11 (remediation wave 3): the capacity checks 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 624ms, 4-30x headroom), asserted inside the deploy-gating chromium e2e-gate (e2e.yml runs the whole E2E project; conditional since 2026-07-29 per TD-20). Adjudicated 2026-07-15 (twentieth-cycle re-score): the §23 half was ACCEPTED (scorecard §23 maturity 3→4 on the enforced CWV budgets) but the §12 half was REJECTED: §12 holds M3/I8 (the k6 tier is freshness-gated but still nightly/manual in execution, and the Notification app stays pinned maxReplicas: 1, infra/main.bicep:1113), so this category stays open at maturity 3.

    [x] #5 · Vertical Slice Architecture · 3 → 4 (weight 2) · RESOLVED 2026-06-30 (scorecard §5 maturity 4 / impl 8): the slice-cohesion fitness function is now a confirmed CI merge gate (Optimized process maturity); the deliberate layered-by-project hybrid remains the accepted impl-8 cap

      @@ -420,7 +420,7 @@

      [x] #23 · Front-End Performance · RESOLVED 2026-07-15 for MATURITY (twentieth-cycle re-score: scorecard §23 maturity 3→4 CONFIRMED on the enforced CWV budgets inside the deploy-gating chromium e2e-gate; implementation holds 8, the code-split/image polish below stays open)

      • No Core Web Vitals/RUM; WASM not code-split; images unoptimized.
      • -
      • Add CWV tracking → DONE + GATED (2026-07-11, remediation wave 3): CWV was measured per E2E run since 2026-06-30 (WebVitalsTests); the budgets are now the enforced Core Web Vitals "good" band asserted inside the deploy-gating chromium e2e-gate (see the #12 wave-3 note above), closing the "advisory by design" hold from the nineteenth-cycle re-score. Candidacy CONFIRMED on the 2026-07-15 twentieth-cycle re-score: scorecard §23 maturity 3→4.
      • +
      • Add CWV tracking → DONE + GATED (2026-07-11, remediation wave 3): CWV was measured per E2E run since 2026-06-30 (WebVitalsTests); the budgets are now the enforced Core Web Vitals "good" band asserted inside the deploy-gating chromium e2e-gate (see the #12 wave-3 note above; conditional since 2026-07-29 per TD-20: a non-UI merge deploys without a CWV assertion), closing the "advisory by design" hold from the nineteenth-cycle re-score. Candidacy CONFIRMED on the 2026-07-15 twentieth-cycle re-score: scorecard §23 maturity 3→4.
      • (Impl polish, open) code-split WASM; optimize images.

      [x] #31 · Cost Efficiency / FinOps · 3 → 4 (weight 2) · RESOLVED 2026-06-30 (scorecard §31 maturity 4 / impl 8): cost-guard.yml is now a workflow_call reusable workflow invoked as a cost-guard job in deploy.needs, so a deploy is blocked while a surge is un-reverted (live in deploy.needs, deploy.yml:791, since 2026-06-30)

      @@ -449,16 +449,16 @@

      🔵 Implementation band (implementation <= 8, ranked by implPriority)

      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 scheduled against - each other: the maturity index reached 97.2% while implementation sits at 85.6%. Ranked from the - current scorecard (2026-08-14 twenty-sixth cycle, no score moves): 15 categories, 35 gap points, - byte-identical to the prior cycle's band. The 35 points are what stands between 85.6% and a full + each other: the maturity index reached 97.2% while implementation sits at 85.0%. Ranked from the + current scorecard (2026-08-23 twenty-seventh cycle, two implementation down-moves): 16 categories, + 40 gap points. §4 entered the band for the first time (impl 9→8) and §22 rose to the joint top + (impl 8→7, joining §15 at implPriority 4). The 40 points are what stands between 85.0% and a full 800; the "90% attainable ceiling" framing used here before 2026-08-01 is retired, since a 10 is now awardable for an almost perfect implementation and the index reads directly against 100%. - Six of these rows were re-proposed for a lift on 2026-08-01 and all six were rejected on evidence - (§5, §13 as a 9→10, §24, §27, §31, §33); the 2026-08-14 cycle re-proposed eight (§5, §7, §12 as an - M3→4, §13 as a 9→10, §15, §23, §28, §31) and rejected all eight, which is why the band did not - move: the work is named and small in most cases, it simply has not shipped. §5 gained its name this - cycle as TD-19.

      + Eight of these rows were re-proposed for a lift on 2026-08-23 and all eight were rejected on + evidence (§5, §7, §15, §17 as a 9→10, §18, §21, §28, §31), the third consecutive all-rejected + cycle: the work is named and small in most cases, it simply has not shipped. §28 gained its name + this cycle as TD-20; §22's lever is now named from the down-move basis.

      Four of these categories (§12, §21, §22, §33) also sit in the maturity band above and keep their existing item there; this band records only their implementation half. Levers are cited only where the ledger or scorecard already records one: an unnamed lever is named at the next re-score, never @@ -480,7 +480,23 @@

      🔵 Imple Best Practices & Code Quality 2 7 - The single highest row on this band (impl 8→7 on 2026-07-28; unchanged 2026-08-01, anchors re-derived). Two hygiene items, effort S: (1) delete the expired audit suppression GHSA-2m69-gcr7-jv3q (Directory.Build.props:54, in the ItemGroup at :53-55), whose own comment (:45-52, updated 2026-07-20) says it "stays only until the next MMCA.Common release and consumer pin sweep carry the fix through the published package graph, then it must be removed": that condition is met and has strengthened (ADC pins v1.135.0 at Directory.Packages.props:139, fourteen releases past the v1.121.0 SQLite sweep; MMCA.Common removed its own entry and pins the patched bundle 3.0.5 directly at MMCA.Common/Directory.Packages.props:42; and ADR-038 already records the accepted-advisory list as empty); (2) justify-or-drop the three undated global NoWarn codes CS1591/RMG020/EXTEXP0001 (:26, where a fourth code S8970 is dated and justified at :22-25, which is the standard the other three fail). Verify with a full-solution package-mode restore, not CI.slnf, since the MAUI graph is exactly what CI.slnf omits; if MAUI still needs the entry, narrow it to that project with a fresh dated justification rather than keeping a stale global one. Store carries the identical entry (MMCA.Store/Directory.Build.props:54) and should be swept in the same pass. The structural half is TD-18 below + Joint-top row on this band (with §22 since 2026-08-23; impl 8→7 on 2026-07-28, re-verified open 2026-08-23 with all three grounds byte-intact). Two hygiene items, effort S: (1) delete the expired audit suppression GHSA-2m69-gcr7-jv3q (Directory.Build.props:54, in the ItemGroup at :53-55), whose own comment (:45-52, updated 2026-07-20) says it "stays only until the next MMCA.Common release and consumer pin sweep carry the fix through the published package graph, then it must be removed": that condition is met and has strengthened (ADC pins v1.160.0 at Directory.Packages.props:92-110, twenty-five releases past the v1.121.0 SQLite sweep; the recorded "v1.135.0 at :139" was doubly stale; MMCA.Common removed its own entry and pins the patched bundle 3.0.5 directly at MMCA.Common/Directory.Packages.props:42; and ADR-038 already records the accepted-advisory list as empty); (2) justify-or-drop the three undated global NoWarn codes CS1591/RMG020/EXTEXP0001 (:26, where a fourth code S8970 is dated and justified at :22-25, which is the standard the other three fail). Verify with a full-solution package-mode restore, not CI.slnf, since the MAUI graph is exactly what CI.slnf omits; if MAUI still needs the entry, narrow it to that project with a fresh dated justification rather than keeping a stale global one. Store carries the identical entry (MMCA.Store/Directory.Build.props:54) and should be swept in the same pass. The structural half is TD-18 below + + + 4 + §22 + Responsive & Cross-Browser + 2 + 7 + Entered the top block 2026-08-23 (impl 8→7). Lever named from the down-move basis (replacing "not yet identified"): adopt the rubric's density-options criterion, which has zero adoption anywhere in ADC, and complete content reflow on the 17 non-DataGrid table pages, including the data-dense conference-day surfaces (the DataGrid pages already degrade to card lists). The chromium-only gate remains the maturity half (see the open #22 item above). Effort M + + + 3 + §4 + Domain-Driven Design + 3 + 8 + Entered the band 2026-08-23 (impl 9→8; maturity 4 holds, so #4 stays in the protect set and this is an implementation-only entrant). Lever not yet identified, name it at the next re-score: the down-move basis (public-setter cross-aggregate navigations, aggregate-external validation of Event's optional fields, OrganizerContactEmail as a raw string) is recorded in the scorecard §4 row, but which of those to schedule first is not yet adjudicated. Do not promote the old "Money/Address VOs live in Common" nit into the lever: it was already priced into the prior 9 3 @@ -496,7 +512,7 @@

      🔵 Imple UI Architecture & Components 3 8 - TD-16 under #18 above, re-measured 2026-08-14: seven code-behinds sit within 38 lines of the enforced 400-line cap, led by SessionSelectionDashboard.razor.cs at 398 (up from 395, so 2 lines of headroom) and HappeningNow.razor.cs at 394. Effort S + TD-16 under #18 above, re-measured 2026-08-23: EIGHT code-behinds now sit within 38 lines of the enforced 400-line cap (was seven), TWO of them at 398 (SessionSelectionDashboard.razor.cs and the newly grown PublicSessionList.razor.cs, 367→398), with ADCHome.razor.cs (341→380) and EventDetail.razor.cs (365→377) also growing since 2026-08-14. Effort S 3 @@ -512,7 +528,7 @@

      🔵 Imple Front-End Testing & Quality 3 8 - not yet identified + TD-20 below (named 2026-08-23, replacing "not yet identified"), two halves: (1) ADC has zero visual-regression/snapshot tests while the reusable MarkupSnapshot helper already ships in MMCA.Common.Testing.UI for consumer reuse and is consumed only by Common's own PrimitivesSnapshotTests (the workspace's own precedent for a Front-End-Testing 8→9, Common's, was earned by closing exactly this gap); (2) the E2E/axe layer is a CONDITIONAL deploy gate, not a merge gate (TD-20). Effort S-M 2 @@ -528,7 +544,7 @@

      🔵 Imple Performance & Scalability 2 8 - see the open #12 item above (Notification pinned maxReplicas: 1, infra/main.bicep:1616 with its right-sizing rationale in the comment ending :1614, anchor refreshed 2026-08-14 from the drifted :1530); re-confirmed open this cycle + see the open #12 item above (Notification pinned maxReplicas: 1, infra/main.bicep:1648 with its right-sizing rationale at :1643-1647, anchor refreshed 2026-08-23 from the drifted :1616); re-confirmed open this cycle 2 @@ -540,14 +556,6 @@

      🔵 Imple 2 - §22 - Responsive & Cross-Browser - 2 - 8 - not yet identified (the chromium-only gate is the maturity half) - - - 2 §23 Front-End Performance 2 @@ -595,10 +603,11 @@

      🔵 Imple DEFERRED 2026-07-28, do not re-propose without new evidence (it was re-proposed anyway on 2026-08-01 and rejected a fourth time, which is the cost this entry exists to prevent; that run also surfaced one new culture-aware-formatting violation, so the evidence moved slightly against the lift). Pseudo-loc breadth: PseudoLocalizationTests.cs:51-56 covers 3 public pages (public by design, :31) of 49 routable @page files, re-counted 2026-08-14 (48 excluding the MAUI-only DeviceSettings.razor; the recorded 37 was stale, so the denominator moved further against the lift). Proposed and rejected in four cycles (21st, 22nd, 24th, 25th; the 23rd rejected §12/§21, not this) on byte-identical evidence: the tier is untouched since c5e6f653 on 2026-07-11 and no .resx has landed since 2026-07-20. Worth 1 weighted point of 800 against authenticated-login plumbing plus expansion assertions on roughly 45 pages, the weakest cost-to-benefit ratio on either band. Re-open triggers and full rationale in Deliberate / accepted below -

      Tactical sub-items on this band (§15 and §5 have no maturity-band item to nest under: both score maturity 4 and sit in the protect list, so their TD-NN items live here with their rows):

      +

      Tactical sub-items on this band (§15, §5 and §28 have no maturity-band item to nest under: all three score maturity 4 and sit in the protect list, so their TD-NN items live here with their rows):

      • TD-18 (recorded 2026-07-28, under §15, effort L) · the MAUI app is outside every CI build and outside the CI-audited dependency graph. MMCA.ADC.CI.slnf:25 lists only UI.Web and UI.Web.Client; no workflow installs the maui-android workload, so MMCA.ADC.UI is never compiled in CI and its analyzers, TreatWarningsAsErrors and its own NoWarn CA5392 (Source/Hosts/UI/MMCA.ADC.UI/MMCA.ADC.UI.csproj:143, its comment at :142; anchor refreshed 2026-08-14 from the drifted :131) are review-enforced only. The gating vulnerable-package scan runs against CI.slnf too (deploy.yml:319, exit at :328; anchor refreshed 2026-08-01 from the drifted :288), so the MAUI graph that the Directory.Build.props:8-12 System.Private.Uri suppressions exist for is the one graph never audited. This is what caps §15 at implementation 8 even after the two effort-S hygiene items land. Blocker (and why this is recorded, not scheduled): adding a MAUI leg means installing the maui-android workload on a runner, which is a multi-minute install on every run and cuts directly against the deliberate 2026-07-18 Actions-minute reduction that also unscheduled the emulator tier (TD-17) and cut the E2E gate to chromium (#22). A cheaper partial is auditing the MAUI graph alone (dotnet list package --vulnerable over that project, no build), which would close the supply-chain half without the workload cost. Resolution path: either the cheap audit-only step, or a scheduled (not per-PR) MAUI build leg; then re-propose §15 impl 8→9. Do not describe §15's maturity-4 enforcement as repo-wide while this is open: it is CI.slnf-wide.
      • TD-19 (recorded 2026-08-14, under §5, effort S) · the enforced architecture map covers 3 of the 4 modules. AdcArchitectureMap.DefineLayers() declares Framework + Identity + Conference + Engagement only and carries no Module("Notification", ...) entry at all (Tests/Architecture/MMCA.ADC.Architecture.Tests/AdcArchitectureMap.cs:12-43; its doc comment at :4-5 names only Identity, Conference and Engagement), so MMCA.ADC.Notification.Application / .API / .Shared sit outside every map-driven fitness rule (slice cohesion, layer dependency, transport-at-the-edge) even though all three build in the CI gate (MMCA.ADC.CI.slnf:30-32). This is the same omission the 2026-08-01 and 2026-08-14 §5 8→9 rejections cited, and it is the named lever for that row. Blocker: none, this is scheduled work. Resolution path: add the Notification module entries to the map (Application, API and Shared anchors, mirroring the Identity/Conference/Engagement blocks) and fix whatever the rules then catch. Effort: S. The deliberate layered-by-project hybrid stays the accepted impl-8 cap and does not cover this: an enforcement-coverage gap is not an accepted trade-off.
      • +
      • TD-20 (recorded 2026-08-23, under §28, effort S-M) · the deploy-gating chromium E2E/axe/CWV suite is CONDITIONAL, not unconditional. e2e-gate runs only when the diff is UI-affecting (if: github.event_name != 'pull_request' && needs.changes.outputs.ui == 'true', deploy.yml:538, rationale :533-537, the ui output described at :53-55), and the deploy job explicitly accepts a skipped gate (needs.e2e-gate.result == 'success' || needs.e2e-gate.result == 'skipped', :896, comment :880-883). A backend-only, infra-only or script-only merge therefore reaches production with no browser run at all: no chromium E2E, no axe scan, no Core Web Vitals assertion. Until this cycle the conditionality was unrecorded in ADC governance (no hit for the ui scoping anywhere in the ledger or scorecard), while several entries called the gate unconditional; that language is now qualified in place (#12/#21/#23/#28). Blocker (deliberate): the 2026-07-29 Actions-minute saving; the workflow names the post-deploy smoke gate as the intended backstop (deploy.yml:535-537). Resolution path: either add a cheap UI-independent smoke leg that always runs, or accept and record the conditionality permanently; in both cases keep the ledger's gate claims accurate. Effort: S-M. Paired with the Deliberate / accepted amendment below; this also names §28's implementation-band lever (with the zero-visual-regression half in its row above).

      🟢 Resolved 2026-07-25 (performance program 2)

      Second evidence-led performance pass over Common/ADC/Store. ADC's share shipped as three PRs plus @@ -631,17 +640,17 @@

      Deliberate /
    • #1 SOLID (AuthenticationService 7-ctor-dependency cohesive auth facade) accepted as-is; the ctor-count fitness threshold (ConstructorDependencyCountTests, ≤7) is now landed on the v1.86.0 sweep, so #1 is closed (scorecard §1 stays M4/I9).
    • #5 Vertical Slice, deliberate layered-by-project hybrid: cross-cutting handled in the decorator pipeline; the hybrid is the accepted choice that caps implementation at 8, and the slice-cohesion line is held by a CI-gated fitness test (SliceCohesionTests, in MMCA.ADC.CI.slnf) that lifted scorecard §5 to maturity 4 (#5 closed, scorecard §5 M4/I8). Scoping clause (2026-08-14): this accepted hybrid is the impl-8 cap, and it does not absorb TD-19. The absence of MMCA.ADC.Notification.* from AdcArchitectureMap.cs:12-43 is an enforcement-coverage gap and schedulable work, not an accepted trade-off.
    • #20 Design System Common-side residuals: accepted as out-of-ADC-scope: BrandColorTokenTests guards the Primary token only (Secondary has no drift test), and a few !important overrides + Store-specific cart CSS live in MMCA.Common's shared app.css. These are MMCA.Common changes, not ADC-local; ADC's §20 is maturity 4 / impl 9.
    • -
    • Chromium-only deploy E2E gate (recorded 2026-07-18, CI-minute reduction; amended 2026-08-01): deploy.yml's e2e-gate invokes one browser leg instead of three (the job is at deploy.yml:531 with browsers: '["chromium"]' at :541; anchors refreshed 2026-08-01 from the drifted :500 / :505, substance re-confirmed and the job is still in deploy.needs at :866); firefox/webkit cross-engine coverage moved to the nightly e2e.yml, where continue-on-error (e2e.yml:144, refreshed from :131) keeps them advisory. Amendment (2026-07-29, recorded here 2026-08-01): the nightly was thinned again to ALTERNATING single-engine legs. Two separate crons now run Monday firefox and Thursday webkit (e2e.yml:49,:50, rationale :44-48, the leg chosen from the cron string that fired), so each non-chromium engine is verified once a week instead of both engines twice a week. This is a further deliberate CI-minute choice, recorded with the same shape as the parent entry; the earlier "Mon/Thu nightly matrix" phrasing used here and under #22 implied both engines on both nights and has been corrected. Scoring consequence: none beyond the existing one, since §22 already sits at M3/I8 on the chromium-only gate. The alternating schedule makes the nightly signal thinner, not the gate weaker. Recorded as a deliberate cost choice, with its scoring consequence stated plainly: it costs §22 its maturity 4, so the category reopens at M3/I8 (see #22). This is a trade-off, not a closure; option (b) under #22 (a cross-browser-freshness gate) would recover the maturity without restoring the runner minutes.
    • +
    • Chromium-only deploy E2E gate (recorded 2026-07-18, CI-minute reduction; amended 2026-08-01): deploy.yml's e2e-gate invokes one browser leg instead of three (the job is at deploy.yml:531 with browsers: '["chromium"]' at :541; anchors refreshed 2026-08-01 from the drifted :500 / :505, substance re-confirmed and the job is still in deploy.needs at :866); firefox/webkit cross-engine coverage moved to the nightly e2e.yml, where continue-on-error (e2e.yml:144, refreshed from :131) keeps them advisory. Amendment (2026-07-29, recorded here 2026-08-01): the nightly was thinned again to ALTERNATING single-engine legs. Two separate crons now run Monday firefox and Thursday webkit (e2e.yml:49,:50, rationale :44-48, the leg chosen from the cron string that fired), so each non-chromium engine is verified once a week instead of both engines twice a week. This is a further deliberate CI-minute choice, recorded with the same shape as the parent entry; the earlier "Mon/Thu nightly matrix" phrasing used here and under #22 implied both engines on both nights and has been corrected. Scoring consequence: none beyond the existing one, since §22 already sits at M3/I8 on the chromium-only gate. The alternating schedule makes the nightly signal thinner, not the gate weaker. Recorded as a deliberate cost choice, with its scoring consequence stated plainly: it costs §22 its maturity 4, so the category reopens at maturity 3 (see #22; implementation dropped separately to 7 on 2026-08-23 on the density/reflow gaps). This is a trade-off, not a closure; option (b) under #22 (a cross-browser-freshness gate) would recover the maturity without restoring the runner minutes. Second amendment (2026-07-29 change, recorded here 2026-08-23): the gate is now also CONDITIONAL on the change set. e2e-gate runs only when the changes job's ui output is true (deploy.yml:538, rationale :533-537, output described :53-55) and deploy treats a skipped gate as pass (:896, comment :880-883), so backend-only, infra-only and script-only merges deploy with no browser, axe or CWV run at all; the workflow names the post-deploy smoke gate as the accepted backstop. Same shape as the parent entry: a deliberate Actions-minute trade-off with its consequence stated plainly, paired with TD-20 as the work that would restore an unconditional signal.
    • Freshness-gate break-glass: the three recency gates (dr-freshness, load-freshness, cross-service-freshness) each accept a skip_freshness_gates workflow_dispatch input with a required justification (declared at deploy.yml:13-18, with the per-gate checks at :562, :619, :678; anchors refreshed 2026-08-01 from the drifted :526,583,642, substance re-confirmed, and the gate jobs themselves are dr-freshness :549, load-freshness :606, cross-service-freshness :663 alongside cost-guard :519), so every one of those proofs is bypassable by an operator. Recorded as an accepted escape hatch; it slightly qualifies the "enforced deploy precondition" language used under #6, #12, #29, and #33.
    • Service Bus emulator smoke is advisory by design (recorded 2026-07-24 as "unscheduled", reconciled 2026-07-28, REWRITTEN 2026-08-01 because the code now says the opposite): the §33 broker-parity tier was cut to dispatch-only on 2026-07-24, and it was restored to the weekday nightly on 2026-07-29 (cross-service-tests.yml:145 for the job, needs: should-run at :146 + if: needs.should-run.outputs.run == 'true' at :147, under workflow_dispatch :26 + cron: '0 6 * * 1-5' at :31, timeout-minutes: 10 at :149; anchors corrected 2026-08-14). The RESCHEDULED comment at :130-143 also records a different root cause than the 2026-07-24 entry claimed: per-test bus re-provisioning against an admin plane throttled at roughly 1 op/sec (IAsyncLifetime plus xUnit per-[Fact] class instantiation), fixed by hoisting the bus to the collection fixture and wall-clock bounding both startup phases. The "floating companion SQL image" blocker text is withdrawn, and the "no schedule / dispatch-only" framing is deleted. What survives as the deliberate choice: the tier is continue-on-error: true (:150) and advisory by design, and nothing gates on it, since cross-service-freshness keys off the cross-service job (:126-129, gate at deploy.yml:663). So §33 still holds M3/I8 on the no-gate half alone, and this tier must not be described as gating. Paired with TD-17 (now half closed), which is the work that would make it authoritative. Same shape as the chromium-only entry above: a trade-off, not a closure.
    • Pseudo-localization breadth DEFERRED (§27, recorded 2026-07-28 after a third rejection): the §27 implementation 8→9 lever, broadening PseudoLocalizationTests beyond its three public pages (PseudoLocalizationTests.cs:51, public by design per :31) across the authenticated authoring surface, is adjudicated deferred rather than open. Rationale stated plainly: it is worth 1 weighted point of 800 (weight 1, one implementation rung) and costs authenticated-login plumbing plus text-expansion and overflow assertions across roughly 45 of the 49 routable @page files (48 excluding the MAUI-only DeviceSettings.razor; figures re-counted 2026-08-14 from the stale 34-of-37), the weakest cost-to-benefit ratio on either band. The identical proposal has now been adversarially rejected in four cycles (21st, 22nd, 24th, 25th) against byte-identical evidence: the tier is untouched since c5e6f653 (2026-07-11) and no .resx has landed since 2026-07-20, so each cycle re-spent an adversarial verify pass to reach the same conclusion. The 2026-08-01 pass additionally found a citable culture-aware-formatting violation that was not previously recorded, so the fresh evidence points away from the lift, not toward it. §27 keeps its implementation-band row at implPriority 1 (band membership is numeric, implementation <= 8), but the lever is not to be re-proposed without new evidence. Re-open triggers: a second locale beyond es, any RTL locale, or a reported layout regression on an authenticated page. Maturity 4 is unaffected and remains doubly CI-gated (TranslationCompletenessTests + LocalizedTextConventionTests, both in MMCA.ADC.CI.slnf:58, run at deploy.yml:219; anchor refreshed 2026-08-01 from the drifted :194).
    • BR-130 room double-booking overlap check accepted as a SOFT guard (recorded 2026-08-01, BugHunt M42): SessionRoomScheduling.ValidateRoomAssignmentAsync is a read-then-write advisory check with no transaction, lock, or DB exclusion constraint tying check to write, so two concurrent organizer writes for the same room with overlapping windows can both pass. Accepted rather than hardened, with each alternative rejected on evidence at the 2026-08-01 BugHunt verification: a transactional re-check cannot close the race below SERIALIZABLE (and with no index on (RoomId, StartsAt) that isolation escalates to key-range/table locks across the Sessionize import path); IDistributedLock's own contract forbids sole-guard use on a correctness invariant and it silently degrades to the in-process implementation when Redis is absent while Conference scales to maxReplicas: 2; sp_getapplock needs an EF/SqlClient reference the Application layer forbids (the same layering constraint that produced CreateSessionHandler's message-based collision detection). The spec's BR-130 text promises only the cross-event room check (422); the overlap guard is code-only, organizer-gated, milliseconds wide at the 2026 load, and a double-booking is repairable by editing either session. Documented in the class XML doc plus a SOFT note at the ExistsAsync call (ADC PR #94; same shape as the BR-231 soft-cap precedent). No scoring consequence claimed. Re-open triggers: organizer concurrency materially above today's handful, a real double-booking incident, or a cheap DB-level range-exclusion capability appearing.
    • -
    • FLAG re-checks: This re-score's (v1.93.0 sweep) only FLAG is §7 (M4/I8, in protect): a proposed impl 8→9 lift was adversarially rejected, the bidirectional Conference↔Engagement gRPC pair caps it in the Strong band, so it is a verified non-move. The prior 2026-06-29 re-score's other re-checks have since settled: §5 was lifted to M4 on the v1.93.0 sweep (slice-cohesion CI gate, no longer flagged), while §25 (M4/I8, closed; route-auth fitness tests CI-gated) and §13 (M3/I8, open under Priority 2) are now plain CONFIRMED. A FLAG is a verified non-move, not a closure. Update (2026-07-03 full re-score): all 34 categories returned CONFIRMED with no new FLAGs; §7 remains the standing verified non-move (M4/I8: the bidirectional Conference↔Engagement gRPC pair caps it in the Strong band), and the §24 impl 9→7 recalibration is a tracked substance gap (TD-14), not an accepted trade-off, so it does not enter this section. Update (2026-07-10 nineteenth-cycle full re-score): the FLAG set shifted. §7 returns plain CONFIRMED (M4/I8, no longer flagged; the bidirectional gRPC pair is a settled cap). The three verified non-moves this cycle are: §12 (M3/I8: a proposed impl 8→9 was adversarially rejected because the Notification app stays pinned maxReplicas: 1, infra/main.bicep:1113, while the backplane key is injected at :1056; the stale no-backplane bicep comment was corrected this cycle), §23 (M3/I8: a proposed maturity 3→4 was rejected; the WebVitals budgets are advisory by design, no §23 fitness gate exists, and the k6/vitals tiers run nightly/dispatch, not as a merge gate), and §34 (M4/I9: a proposed impl 9→8 downgrade was rejected as unsupported; the untracked workspace-root ArchitecturalAnalysis.md remains the already-weighed 9-not-10 lever). Each is a verified non-move (score held), not a closure. Update (2026-07-15 twentieth-cycle full re-score): the FLAG set shifted again. §12 returns plain CONFIRMED (M3/I8, no longer flagged) and §23 exits as a lift (maturity 3→4 on the now-enforced CWV budgets, superseding its nineteenth-cycle rejection). This cycle's adversarial adjudications: §19 (a first-pass impl 9→8 downgrade was rejected as unsupported while the maturity 3→4 lift was confirmed, so §19 closes at M4/I9), §28 (M4/I8 verified non-move, but its row carried a materially false claim now corrected in place: E2E #5 is re-quarantined at SpeakerSelfServiceTests.cs:57, not "un-skipped/active", plus three drifted line anchors), §33 (M3/I8: a proposed impl 8→9 was rejected on the open broker-parity red flag, README.md:74), and §34 (M4/I9: the identical impl 9→8 downgrade re-proposed and re-rejected). Each non-move is a held score, not a closure. Update (2026-07-17 twenty-first-cycle full re-score): one FLAG this cycle: §27 (M4/I8 verified non-move: the recorded impl 8→9 candidacy, extending the pseudo-loc text-expansion evidence to ADC pages, was adversarially rejected because PseudoLocalizationTests.cs:51 covers only 3 public pages of 30+ routable pages, a partial extension; §27 stays in the protect set at its held score). §12 and §33 return plain CONFIRMED at M3/I8 (their twentieth-cycle adjudications re-derived from fresh evidence, including the load-freshness gate and the Service Bus emulator tier, neither sufficient for a move). A FLAG is a held score, not a closure. Update (2026-07-21 twenty-second-cycle full re-score): the single FLAG is again §27 (M4/I8): the identical impl 8→9 pseudo-loc candidacy was re-proposed and re-rejected on unchanged evidence (PseudoLocalizationTests.cs:51 covers exactly 3 public pages against 36 routable pages), so it stays a verified non-move in the protect set. The §33 sentence in earlier updates that quoted README.md:74 is superseded: that admission no longer exists in the file (see the #33 header for the rewritten basis). Update (2026-07-23 twenty-third-cycle full re-score): the FLAG set shifted: §27 returns plain CONFIRMED (M4/I8, in the protect set; the impl 8→9 pseudo-loc candidacy was not re-proposed this cycle). The two verified non-moves are §12 (M3/I8: a proposed maturity 3→4 was adversarially rejected because the k6 capacity proof executes monthly/dispatch out of band with load-freshness a recency-only check, deploy.yml:553, and Notification stays pinned maxReplicas: 1, infra/main.bicep:1424) and §21 (M3/I8: a proposed maturity 3→4 was rejected because the manual screen-reader pass is still unrecorded in ACCESSIBILITY-SCREENREADER-PASS.md, the cheapest maturity 3→4 lever). §22 and §33 are plain CONFIRMED at M3/I8. A FLAG is a held score, not a closure. Update (2026-07-28 twenty-fourth-cycle full re-score, pin v1.131.0, HEAD 2ec77796): one score moved, and it moved down. §15 Best Practices & Code Quality implementation 8→7 (weight 2), which takes it to the top of the implementation band at implPriority 4; maturity holds 4, so #15 stays in the protect set and the maturity band is unchanged. A down-move is not a FLAG: it is a CONFIRMED move, adversarially verified, on three gaps read fresh this run (an audit suppression expired by its own written removal condition, three undated global NoWarn codes, and the MAUI project outside every CI build and outside the CI-audited graph). The single FLAG this cycle is §27 (M4/I8 verified non-move): a first pass proposed impl 8→9 for the third time and the adversarial pass rejected it on byte-identical evidence, correcting the score back to the prior values. Because the corrected values equal the prior ones, all 34 categories are evidence-backed this run even though the indices are labeled "33 rescored + 1 prior". That lever is now adjudicated DEFERRED with its cost and re-open triggers recorded above, so it should not return as a candidacy. §12/§21/§22/§33 return plain CONFIRMED at M3/I8, and the #33 re-confirmation rests on a weaker basis than last cycle (its parity tier is now dispatch-only, TD-17). Also re-rejected: #24 impl 8→9 and #33 M3→4 / I8→9; #6 and #30 sit at I9, already at the scheduling target of 9, so their recorded "9→10" candidacies are out of scope for both bands. Update (2026-08-01 twenty-fifth-cycle full re-score, pin v1.135.0, HEAD 995a7886): no score moved on either axis, and this cycle produced the largest FLAG set yet: six, every one of them a proposed implementation lift, every one rejected against current source. §5 8→9 rejected (the rubric's first §5 criterion wants the DTO in the slice; ADC's live in the Shared assembly with horizontal mapper/validation/specification folders, the layered-by-project hybrid is unchanged, and AdcArchitectureMap.cs:12-44 omits MMCA.ADC.Notification.Application, so enforcement covers 3 of 4 modules). §13 9→10 rejected (three ENABLED production alerts have no runbook triage section and sit outside the pairing gate's scope, including the sev-1 gateway-availability alert at infra/main.bicep:481; that is an unmet criterion, not the trivial polish the recalibrated top rung allows). §24 8→9 rejected (the named bUnit lever shipped and is CI-gated, but client validation does not mirror the server's cross-field and format rules and the error summary reaches 7 of 15 MudForm forms; both are now named as §24's levers). §27 8→9 rejected a fourth time on byte-identical evidence plus one new culture-formatting violation. §31 8→9 rejected (the surge/revert automation is not pulled). §33 8→9 rejected a second time (the AppHost provisions RabbitMQ only, and the restored Service Bus nightly is continue-on-error and gates nothing). Six rejections and zero moves is not a stalled cycle: it is six categories each sitting one criterion short, with the criterion now named in the band for five of them (§7, §16, §21, §22, §25, §28 remain "lever not yet identified"). A FLAG is a held score, not a closure, and none of these six changed band membership. Update (2026-08-14 twenty-sixth-cycle full re-score, pin v1.152.0, HEAD 19021d93): no score moved on either axis and the FLAG set grew to eight, every one a proposed lift, every one rejected against current source. §5 8→9 rejected (the DTO-in-the-slice criterion is still unmet and AdcArchitectureMap.cs:12-43 still omits the Notification module: that half is now named as TD-19). §7 8→9 rejected (the bidirectional sync-gRPC red flag did not close, it broadened to a second pair, Identity-Notification, across 7 sync client registrations in 4 services). §12 M3→4 rejected (zero commits touched load-test.yml, deploy.yml or Tests/Load/ since the prior cycle's HEAD, so the out-of-band capacity proof behind a recency-only gate is byte-for-byte intact). §13 9→10 rejected a second time (three ENABLED production alerts still carry no runbook triage section, including the sev-1 gateway-availability alert, anchor refreshed infra/main.bicep:481:496-502; §13 sits at I9, outside both bands). §15 7→8 rejected (all three downgrade grounds intact, and the expired SQLite suppression is further past its own removal condition now that ADC pins v1.152.0). §23 8→9 rejected (WASM code-split and image optimization, the category's own named lever, are both still open). §28 8→9 rejected (the genuine new state-management bUnit coverage is a within-band improvement, not a band change). §31 8→9 rejected a second time (the conference-day surge is still manual with a manual reset instruction and no automated revert). A FLAG is a held score, not a closure, and none of these eight changed band membership.
    • +
    • FLAG re-checks: This re-score's (v1.93.0 sweep) only FLAG is §7 (M4/I8, in protect): a proposed impl 8→9 lift was adversarially rejected, the bidirectional Conference↔Engagement gRPC pair caps it in the Strong band, so it is a verified non-move. The prior 2026-06-29 re-score's other re-checks have since settled: §5 was lifted to M4 on the v1.93.0 sweep (slice-cohesion CI gate, no longer flagged), while §25 (M4/I8, closed; route-auth fitness tests CI-gated) and §13 (M3/I8, open under Priority 2) are now plain CONFIRMED. A FLAG is a verified non-move, not a closure. Update (2026-07-03 full re-score): all 34 categories returned CONFIRMED with no new FLAGs; §7 remains the standing verified non-move (M4/I8: the bidirectional Conference↔Engagement gRPC pair caps it in the Strong band), and the §24 impl 9→7 recalibration is a tracked substance gap (TD-14), not an accepted trade-off, so it does not enter this section. Update (2026-07-10 nineteenth-cycle full re-score): the FLAG set shifted. §7 returns plain CONFIRMED (M4/I8, no longer flagged; the bidirectional gRPC pair is a settled cap). The three verified non-moves this cycle are: §12 (M3/I8: a proposed impl 8→9 was adversarially rejected because the Notification app stays pinned maxReplicas: 1, infra/main.bicep:1113, while the backplane key is injected at :1056; the stale no-backplane bicep comment was corrected this cycle), §23 (M3/I8: a proposed maturity 3→4 was rejected; the WebVitals budgets are advisory by design, no §23 fitness gate exists, and the k6/vitals tiers run nightly/dispatch, not as a merge gate), and §34 (M4/I9: a proposed impl 9→8 downgrade was rejected as unsupported; the untracked workspace-root ArchitecturalAnalysis.md remains the already-weighed 9-not-10 lever). Each is a verified non-move (score held), not a closure. Update (2026-07-15 twentieth-cycle full re-score): the FLAG set shifted again. §12 returns plain CONFIRMED (M3/I8, no longer flagged) and §23 exits as a lift (maturity 3→4 on the now-enforced CWV budgets, superseding its nineteenth-cycle rejection). This cycle's adversarial adjudications: §19 (a first-pass impl 9→8 downgrade was rejected as unsupported while the maturity 3→4 lift was confirmed, so §19 closes at M4/I9), §28 (M4/I8 verified non-move, but its row carried a materially false claim now corrected in place: E2E #5 is re-quarantined at SpeakerSelfServiceTests.cs:57, not "un-skipped/active", plus three drifted line anchors), §33 (M3/I8: a proposed impl 8→9 was rejected on the open broker-parity red flag, README.md:74), and §34 (M4/I9: the identical impl 9→8 downgrade re-proposed and re-rejected). Each non-move is a held score, not a closure. Update (2026-07-17 twenty-first-cycle full re-score): one FLAG this cycle: §27 (M4/I8 verified non-move: the recorded impl 8→9 candidacy, extending the pseudo-loc text-expansion evidence to ADC pages, was adversarially rejected because PseudoLocalizationTests.cs:51 covers only 3 public pages of 30+ routable pages, a partial extension; §27 stays in the protect set at its held score). §12 and §33 return plain CONFIRMED at M3/I8 (their twentieth-cycle adjudications re-derived from fresh evidence, including the load-freshness gate and the Service Bus emulator tier, neither sufficient for a move). A FLAG is a held score, not a closure. Update (2026-07-21 twenty-second-cycle full re-score): the single FLAG is again §27 (M4/I8): the identical impl 8→9 pseudo-loc candidacy was re-proposed and re-rejected on unchanged evidence (PseudoLocalizationTests.cs:51 covers exactly 3 public pages against 36 routable pages), so it stays a verified non-move in the protect set. The §33 sentence in earlier updates that quoted README.md:74 is superseded: that admission no longer exists in the file (see the #33 header for the rewritten basis). Update (2026-07-23 twenty-third-cycle full re-score): the FLAG set shifted: §27 returns plain CONFIRMED (M4/I8, in the protect set; the impl 8→9 pseudo-loc candidacy was not re-proposed this cycle). The two verified non-moves are §12 (M3/I8: a proposed maturity 3→4 was adversarially rejected because the k6 capacity proof executes monthly/dispatch out of band with load-freshness a recency-only check, deploy.yml:553, and Notification stays pinned maxReplicas: 1, infra/main.bicep:1424) and §21 (M3/I8: a proposed maturity 3→4 was rejected because the manual screen-reader pass is still unrecorded in ACCESSIBILITY-SCREENREADER-PASS.md, the cheapest maturity 3→4 lever). §22 and §33 are plain CONFIRMED at M3/I8. A FLAG is a held score, not a closure. Update (2026-07-28 twenty-fourth-cycle full re-score, pin v1.131.0, HEAD 2ec77796): one score moved, and it moved down. §15 Best Practices & Code Quality implementation 8→7 (weight 2), which takes it to the top of the implementation band at implPriority 4; maturity holds 4, so #15 stays in the protect set and the maturity band is unchanged. A down-move is not a FLAG: it is a CONFIRMED move, adversarially verified, on three gaps read fresh this run (an audit suppression expired by its own written removal condition, three undated global NoWarn codes, and the MAUI project outside every CI build and outside the CI-audited graph). The single FLAG this cycle is §27 (M4/I8 verified non-move): a first pass proposed impl 8→9 for the third time and the adversarial pass rejected it on byte-identical evidence, correcting the score back to the prior values. Because the corrected values equal the prior ones, all 34 categories are evidence-backed this run even though the indices are labeled "33 rescored + 1 prior". That lever is now adjudicated DEFERRED with its cost and re-open triggers recorded above, so it should not return as a candidacy. §12/§21/§22/§33 return plain CONFIRMED at M3/I8, and the #33 re-confirmation rests on a weaker basis than last cycle (its parity tier is now dispatch-only, TD-17). Also re-rejected: #24 impl 8→9 and #33 M3→4 / I8→9; #6 and #30 sit at I9, already at the scheduling target of 9, so their recorded "9→10" candidacies are out of scope for both bands. Update (2026-08-01 twenty-fifth-cycle full re-score, pin v1.135.0, HEAD 995a7886): no score moved on either axis, and this cycle produced the largest FLAG set yet: six, every one of them a proposed implementation lift, every one rejected against current source. §5 8→9 rejected (the rubric's first §5 criterion wants the DTO in the slice; ADC's live in the Shared assembly with horizontal mapper/validation/specification folders, the layered-by-project hybrid is unchanged, and AdcArchitectureMap.cs:12-44 omits MMCA.ADC.Notification.Application, so enforcement covers 3 of 4 modules). §13 9→10 rejected (three ENABLED production alerts have no runbook triage section and sit outside the pairing gate's scope, including the sev-1 gateway-availability alert at infra/main.bicep:481; that is an unmet criterion, not the trivial polish the recalibrated top rung allows). §24 8→9 rejected (the named bUnit lever shipped and is CI-gated, but client validation does not mirror the server's cross-field and format rules and the error summary reaches 7 of 15 MudForm forms; both are now named as §24's levers). §27 8→9 rejected a fourth time on byte-identical evidence plus one new culture-formatting violation. §31 8→9 rejected (the surge/revert automation is not pulled). §33 8→9 rejected a second time (the AppHost provisions RabbitMQ only, and the restored Service Bus nightly is continue-on-error and gates nothing). Six rejections and zero moves is not a stalled cycle: it is six categories each sitting one criterion short, with the criterion now named in the band for five of them (§7, §16, §21, §22, §25, §28 remain "lever not yet identified"). A FLAG is a held score, not a closure, and none of these six changed band membership. Update (2026-08-14 twenty-sixth-cycle full re-score, pin v1.152.0, HEAD 19021d93): no score moved on either axis and the FLAG set grew to eight, every one a proposed lift, every one rejected against current source. §5 8→9 rejected (the DTO-in-the-slice criterion is still unmet and AdcArchitectureMap.cs:12-43 still omits the Notification module: that half is now named as TD-19). §7 8→9 rejected (the bidirectional sync-gRPC red flag did not close, it broadened to a second pair, Identity-Notification, across 7 sync client registrations in 4 services). §12 M3→4 rejected (zero commits touched load-test.yml, deploy.yml or Tests/Load/ since the prior cycle's HEAD, so the out-of-band capacity proof behind a recency-only gate is byte-for-byte intact). §13 9→10 rejected a second time (three ENABLED production alerts still carry no runbook triage section, including the sev-1 gateway-availability alert, anchor refreshed infra/main.bicep:481:496-502; §13 sits at I9, outside both bands). §15 7→8 rejected (all three downgrade grounds intact, and the expired SQLite suppression is further past its own removal condition now that ADC pins v1.152.0). §23 8→9 rejected (WASM code-split and image optimization, the category's own named lever, are both still open). §28 8→9 rejected (the genuine new state-management bUnit coverage is a within-band improvement, not a band change). §31 8→9 rejected a second time (the conference-day surge is still manual with a manual reset instruction and no automated revert). A FLAG is a held score, not a closure, and none of these eight changed band membership. Update (2026-08-23 twenty-seventh-cycle full re-score, pin v1.160.0, HEAD 96f0919a): two scores moved, both down, both CONFIRMED moves adversarially verified rather than FLAGs: §4 implementation 9→8 (public-setter cross-aggregate navigations, aggregate-external validation of Event's optional fields, primitive obsession on OrganizerContactEmail; the prior row's citations had all drifted and the fresh read placed the substance in the Strong band) and §22 implementation 8→7 (zero density-option adoption plus partial content reflow on the 17 non-DataGrid table pages, which names the lever this band had carried as "not yet identified"). The FLAG set held at eight, every one a proposed lift, every one rejected: §5 8→9 rejected a third time (DTOs and horizontal validators still outside the slice, the enforced validator rule exempting exactly the population that exists, ArchitectureRules.Slices.cs:38-39; the forgot-password vertical is fresh proof the hybrid still edits switchboards; TD-19 still open). §7 8→9 rejected (the synchronous-coupling red flag broadened rather than closed). §15 7→8 rejected a second time (all three downgrade grounds byte-intact; the expired suppression now twenty-five releases past its removal condition). §17 9→10 rejected (no CI/CD substance changed since the prior basis commit; the SQL public-network-access cap is verbatim open; the tightened 3d/keep-3 ACR purge narrows the rollback image window rather than widening it). §18 8→9 rejected (the cap-pressure gap WIDENED: eight code-behinds within 38 lines, two at 398; TD-16). §21 M3→4 and I8→9 both rejected (the SR-pass placeholder is still empty at adc-ACCESSIBILITY-SCREENREADER-PASS.md:62, and four routable pages shipped 2026-08-19 with no axe coverage: a new gap, not a lift). §28 8→9 rejected (zero visual-regression tests with the shared MarkupSnapshot helper unused, and the E2E layer is a conditional deploy gate, not a merge gate: named as TD-20). §31 8→9 rejected a third time (cost-guard.yml byte-unchanged since caf31e09; the surge is still a manual play with a manual reset). A FLAG is a held score, not a closure; the only band-membership changes this cycle came from the two confirmed down-moves.

    ✅ Already at level 4: protect, don't regress

    #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 Architecture · #9 API & Contract Design · #10 Cross-Cutting Concerns · #11 Security · #13 Observability & Operability · #14 Testability & Test Strategy · #15 Best Practices & Code Quality · #16 Maintainability & Evolvability · #17 DevOps & Deployment · #18 UI Architecture & Components · #19 State Management & Data Flow · #20 Design System · #23 Front-End Performance · #24 Forms & UX Safety · #25 Navigation & Information Arch · #26 Front-End Security · #27 Internationalization · #28 Front-End Testing & Quality · #29 Resilience & Business Continuity · #30 Compliance & Privacy · #31 Cost Efficiency / FinOps · #32 Dependency & Supply-Chain · #34 Architecture Governance & Docs (30 categories at maturity 4) - (The pattern/layer/governance categories are auto-enforced by the architecture fitness functions in the deploy gate; the rest reached maturity 4 via the remediation tracked above. Keeping those gates green is the regression guard. UPDATE 2026-06-30: §16/§24/§27/§29/§31 joined the protect set via the enforcement-gate wave: #24/#16/#27 by new CI.slnf fitness tests, #31/#29 by the cost-guard/dr-freshness deploy.needs gates (all live in deploy.needs, deploy.yml:791). The 2026-06-29 §29 reopening is superseded. UPDATE (v1.93.0 sweep, 2026-06-30): #5 Vertical Slice Architecture also joined the protect set, its slice-cohesion fitness test confirmed a CI merge gate in CI.slnf. UPDATE (2026-07-02 re-score): #18 UI Architecture left the protect set because scorecard §18 maturity was corrected 4→3 (no automated §18 UI-architecture fitness gate; the container/presentational + code-behind conventions are review-enforced only), so it is reopened as an active priority-3 item and the count is now 26. UPDATE (2026-07-03 reconciliation): #28 Front-End Testing joined the protect set (scorecard §28 maturity 4 via the deploy-gating chromium e2e-gate) and #19 State Management left it (scorecard §19 maturity corrected 4→3 on the fifteenth cycle: no §19 fitness gate), so the membership swapped and the count stays 26. UPDATE (2026-07-15 twentieth-cycle re-score): #18 UI Architecture, #19 State Management, and #23 Front-End Performance joined the protect set (the §18/§19 fitness gates now run in the CI.slnf arch gate and the §23 CWV budgets are enforced inside the deploy-gating e2e-gate), taking the count to 29. UPDATE (2026-07-17 twenty-first-cycle re-score): #13 Observability and #22 Responsive & Cross-Browser joined the protect set (the ObservabilityConventionTests alert-runbook pairing gate runs in the CI.slnf arch gate, and all three e2e-gate browser legs now block the deploy per e2e.yml:78), taking the count to 31. UPDATE (2026-07-21 twenty-second-cycle re-score): #22 Responsive & Cross-Browser LEFT the protect set (scorecard §22 maturity corrected 4→3: the 2026-07-18 Actions-minute reduction cut the deploy e2e-gate to chromium only, deploy.yml:488, leaving firefox/webkit nightly-advisory under e2e.yml:119), taking the count to 30. #18 stays in the protect set: its maturity 4 gate is intact and only its implementation moved 9→8 (TD-16). The maturity-4 set is exactly the 30 categories other than §12/§21/§22/§33. UPDATE (2026-07-28 twenty-fourth-cycle re-score): membership and count are unchanged at 30. #15 stays in the protect set for the same reason #18 did: its maturity-4 gate is intact and only its implementation moved (8→7, TD-18 plus two effort-S hygiene items). UPDATE (2026-08-01 twenty-fifth-cycle re-score): membership and count are again unchanged at 30, and no category crossed either threshold; the six adversarial adjudications this cycle were all rejected implementation lifts, so nothing entered or left this list. This list is the maturity-4 set, not the fully-closed set. Of these 30, only 19 also score implementation >= 9, which is the pairing that means "done on both axes"; the other 11 (§5, §7, §15, §16, §18, §23, §24, §25, §27, §28, §31) keep a live row in the implementation band below. Protect what is here, but do not read presence here as "nothing left to do".)

    + (The pattern/layer/governance categories are auto-enforced by the architecture fitness functions in the deploy gate; the rest reached maturity 4 via the remediation tracked above. Keeping those gates green is the regression guard. UPDATE 2026-06-30: §16/§24/§27/§29/§31 joined the protect set via the enforcement-gate wave: #24/#16/#27 by new CI.slnf fitness tests, #31/#29 by the cost-guard/dr-freshness deploy.needs gates (all live in deploy.needs, deploy.yml:791). The 2026-06-29 §29 reopening is superseded. UPDATE (v1.93.0 sweep, 2026-06-30): #5 Vertical Slice Architecture also joined the protect set, its slice-cohesion fitness test confirmed a CI merge gate in CI.slnf. UPDATE (2026-07-02 re-score): #18 UI Architecture left the protect set because scorecard §18 maturity was corrected 4→3 (no automated §18 UI-architecture fitness gate; the container/presentational + code-behind conventions are review-enforced only), so it is reopened as an active priority-3 item and the count is now 26. UPDATE (2026-07-03 reconciliation): #28 Front-End Testing joined the protect set (scorecard §28 maturity 4 via the deploy-gating chromium e2e-gate) and #19 State Management left it (scorecard §19 maturity corrected 4→3 on the fifteenth cycle: no §19 fitness gate), so the membership swapped and the count stays 26. UPDATE (2026-07-15 twentieth-cycle re-score): #18 UI Architecture, #19 State Management, and #23 Front-End Performance joined the protect set (the §18/§19 fitness gates now run in the CI.slnf arch gate and the §23 CWV budgets are enforced inside the deploy-gating e2e-gate), taking the count to 29. UPDATE (2026-07-17 twenty-first-cycle re-score): #13 Observability and #22 Responsive & Cross-Browser joined the protect set (the ObservabilityConventionTests alert-runbook pairing gate runs in the CI.slnf arch gate, and all three e2e-gate browser legs now block the deploy per e2e.yml:78), taking the count to 31. UPDATE (2026-07-21 twenty-second-cycle re-score): #22 Responsive & Cross-Browser LEFT the protect set (scorecard §22 maturity corrected 4→3: the 2026-07-18 Actions-minute reduction cut the deploy e2e-gate to chromium only, deploy.yml:488, leaving firefox/webkit nightly-advisory under e2e.yml:119), taking the count to 30. #18 stays in the protect set: its maturity 4 gate is intact and only its implementation moved 9→8 (TD-16). The maturity-4 set is exactly the 30 categories other than §12/§21/§22/§33. UPDATE (2026-07-28 twenty-fourth-cycle re-score): membership and count are unchanged at 30. #15 stays in the protect set for the same reason #18 did: its maturity-4 gate is intact and only its implementation moved (8→7, TD-18 plus two effort-S hygiene items). UPDATE (2026-08-01 twenty-fifth-cycle re-score): membership and count are again unchanged at 30, and no category crossed either threshold; the six adversarial adjudications this cycle were all rejected implementation lifts, so nothing entered or left this list. UPDATE (2026-08-23 twenty-seventh-cycle re-score): membership and count are unchanged at 30. #4 stays in the protect set for the same reason #18 and #15 did: its maturity-4 gate is intact and only its implementation moved (9→8). This list is the maturity-4 set, not the fully-closed set. Of these 30, only 18 also score implementation >= 9, which is the pairing that means "done on both axes"; the other 12 (§4, §5, §7, §15, §16, §18, §23, §24, §25, §27, §28, §31) keep a live row in the implementation band below. Protect what is here, but do not read presence here as "nothing left to do".)


    Suggested sequencing: updated 2026-06-11

      diff --git a/platform.html b/platform.html index 84e2ef3..c9c31ad 100644 --- a/platform.html +++ b/platform.html @@ -375,8 +375,8 @@

      A two-axis architecture scorecard

      Implementation - - 85.6% + + 85.0% diff --git a/sitemap.xml b/sitemap.xml index 33acf12..e76f3a2 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -722,12 +722,12 @@ https://ivanball.github.io/docs/governance/adc-ArchitectureScorecard.html - 2026-08-14 + 2026-08-23 0.6 https://ivanball.github.io/docs/governance/adc-RemediationBacklog.html - 2026-08-14 + 2026-08-23 0.6