Architecture governance
MMCA.Store: Architecture Remediation Backlog
Derived from ArchitectureScorecard.md: Maturity 97.5% (308/316) / Implementation 83.2% (657/790), evidence as of 2026-10-18 at MMCA.Common.* v1.244.0 (lockstep, HEAD b40baf14, clean tree). Items are ranked on both scorecard axes, one band per axis. The maturity band holds categories scoring maturity < 4, ranked by priority = (4 − maturity) × weight. The implementation band holds categories scoring implementation <= 8**, ranked by **implPriority = max(0, 9 − implementation) × weight**; the scheduling target is **9, not 10**, because a 10 is recognition earned at re-score time rather than scheduled work, and ranking against 10 would put nearly every strong category in the band and drown the real gaps. Ties break by weight descending, then category number. A category leaves each band independently and reaches the protect list only at **maturity 4 AND implementation >= 9; the indices keep their × 4 and × 10 denominators, and the 9-target governs scheduling only.
The scorecard is the source of truth for scores, ../MMCA.Common/FACTS.md for framework-wide facts, and the workspace-internal Docs/Architecture/ArchitectureRemediation.md (not published) for the cross-repo [C→A] roll-up. Tactical deferrals are recorded here as TD sub-items under their category (the single-ledger model). Status: [ ] open, [~] under way, [x] resolved.
Open work at a glance
Triage 2026-10-19. The S2 wave (Store #213, MMCA.Common v1.245.0) shipped the #10, #15, #17 (rollback report), #22 (phone viewport), #25 (Roles item) and #27 levers and corrected the SQL comment under #11; each is marked shipped below, with its score movement pending the next re-score. The Data Protection wrap of the cached Stripe secret was dropped by user decision. Every other open item was weighed for over-engineering: the ones closed are under Deliberate and accepted (Closed as over-engineering, 2026-10-19) and must not be re-proposed without new evidence. Scores are unchanged, so every row keeps its band position. Three items stayed open at triage: the seed-account check, the screen-reader pass (both need a human) and typed route parameters; the typed route parameters have since shipped (Store #214), so two remain.
| Priority | # | Category | M / I | Band | Next action |
|---|---|---|---|---|---|
| 6 | #11 | Security | M4 / I7 | Implementation | SQL comment corrected (Store #213); the Stripe secret wrap is dropped by decision (Sales registers no shared key ring) |
| 6 | #11 | Security | M4 / I7 | Implementation | Open (user-side): rotate or remove the identity accounts seeded before the Seeding:IncludeSampleUsers gate |
| 3 | #10 | Messaging & Integration Architecture | M3 / I8 | Maturity + Implementation | Error-queue alert-as-code and runbook shipped (Store #213); re-score pending |
| 3 | #21 | Accessibility (a11y) | M3 / I8 | Maturity + Implementation | Open (needs a human): record a dated manual NVDA/VoiceOver pass in the screen-reader runbook |
| 2 | #15 | Best Practices & Code Quality | M4 / I8 | Implementation | Weekly MAUI head build shipped (Store #213); re-score pending |
| 2 | #17 | DevOps & Deployment | M4 / I8 | Implementation | Truthful rollback report shipped (Store #213); Bicep what-if and probe-only rollback closed |
| 2 | #20 | Design System & UI Consistency | M4 / I8 | Implementation | Closed 2026-10-19: the StoreHome palette is cosmetic and passes the dark-palette axe scan |
| 2 | #22 | Responsive & Cross-Browser | M4 / I8 | Implementation | Phone-viewport E2E shipped (Store #213); density and touch-target CSS closed |
| 2 | #25 | Navigation & Information Arch | M4 / I8 | Implementation | Roles item fixed (Store #213); NavigationContractTests closed; detail routes typed {Id:int} (shipped, Store #214); re-score pending |
| 1 | #27 | Internationalization (i18n) | M4 / I8 | Implementation | Plurals and accents shipped (Store #213); re-score pending |
Open items
- [closed] #11 Security, implementation 7: secret handling. Triage 2026-10-19: the stale SQL comment is corrected (Store #213), and protecting the cached signing secret is dropped by user decision, because the Sales host registers no shared Data Protection key ring, so a wrap would need key-ring infrastructure for one secret already guarded by Redis access (recorded under Deliberate and accepted). No scheduled work remains in this item. The original finding follows. Three red flags hold the row at 7. The Stripe webhook signing secret is cached in plaintext in the shared Redis with a 365-day lifetime (
SharedCacheStripeWebhookSecretStore.cs:79, lifetime at:54); the registration service never prints it (StripeWebhookRegistrationService.cs:200-202). One shared identity isdb_ownerin all three databases (infra/SQL-MANAGED-IDENTITY.md:80), and every per-app Service Bus SAS rule carries Manage rights (infra/main.bicep:1069,1081,1093), kept because MassTransit creates the topology at runtime (:1052-1057); both are accepted at this scale (Deliberate and accepted below), so the signing-secret cache is the one red flag with scheduled work. A stale comment also says every service still connects with the SQL server administrator login althoughUSE_MANAGED_IDENTITY_SQL=true(infra/main.bicep:869-870). The internal Customers gRPC edge requires a user or service token (Resolved below). Next: protect the cached signing secret, and correct the stale comment. Effort: S. - #10 Messaging, maturity 3 / implementation 8: the broker error queue has no operating procedure. Shipped 2026-10-19 (Store #213):
store-alert-broker-error-queuematches "moved to the error queue" (infra/main.bicep:510),infra/OPERATIONS.md:175holds the inspect, replay and discard runbook, and the outbox runbook names all five consumer flows. The[EventName]rule is already required:Tests/Architecture/MMCA.Store.Architecture.Tests/Contracts/EventConventionTests.cs:3subclasses Common'sEventConventionTestsBase. The real-broker round-trips and theProductVariantChangedordering declaration are closed (Deliberate and accepted). Re-score pending. The original finding follows. The rubric requires a dead-letter path with an operational procedure: alert, inspect, replay or discard (ArchitectureEvaluationCriteria.md:336). The only messaging alert matches the outbox text "dead-lettered" (infra/main.bicep:462), while a consumer that exhausts its retries logs "moved to the error queue" (CommonFaultIntegrationEventConsumer.cs:57), which no Store alert matches and noinfra/*.bicepalert covers.infra/OPERATIONS.mdhas an outbox-row replay (:166) and no error-queue section, and ADR-087 records no alert, runbook or replay path for that queue (087-broker-poison-message-handling.md:202). Nothing requires[EventName]on every event: Common keys the contract by type name when the attribute is absent (ArchitectureRules.Events.cs:42), and all four Store events carry it by convention only. The same gap holds ADC's §10 at M3. Store-specific residuals: the dead-letter runbook names one live consumer flow where five run (OPERATIONS.md:164; the consumers are atSales.Service/Program.cs:261andCatalog.Service/Program.cs:274),CustomerErasedandProductInfoChangedhave no real-broker round-trip test, andProductVariantChangeddeclares no ordering expectation (IntegrationEventContractTests.cs:12), so a late redelivery could overwrite newer labels. Next: an alert-as-code on the error-queue log line or the broker metric, anOPERATIONS.mdrunbook with a replay-or-discard procedure, and a fitness rule requiring[EventName](that rule is Common-side). Effort: S-M. - #11 Security: rotate or remove the identity seed accounts created before the environment gate. Kept open on 2026-10-19: a weak-password admin account in production would be a live defect, and the check is a single query. The framework leaves the choice to the app:
IdentityModuleDbSeederBase.ShouldSeeddefaults totrue(IdentityModuleDbSeederBase.cs:57), directly under a security notice saying the seed credentials are deliberately weak plaintext values and that deployed environments must disable seeding (:31-35). Store overrides it onSeeding:IncludeSampleUsers, default false (IdentityModuleSeeder.cs:28-30; the AppHost opts in atAppHost/Program.cs:194-195, the integration fixture atIdentityIntegrationTestFixture.cs:61,69,infra/main.bicepsets nothing,IdentityModuleSeederTestspins it), the same shape as ADC (MMCA.ADC.Identity.API/IdentityModuleSeeder.cs:22-30). The seeded set is an admin account plus a customer account with weak plaintext passwords, one of them a real personal address (MMCA.Store.Identity.Infrastructure/.../IdentityModuleDbSeeder.cs:23,37-38), and the per-accountEmailExistsAsyncprobe (:45-52) skips an account that already exists. Next: check the deployed Store Identity database for accounts seeded by builds that predate the gate (Store PR #124) and rotate or remove them (user-side; not provable from the repo). - [~] #21 Accessibility, maturity 3 → 4: record a dated manual screen-reader pass. The axe + Playwright suite gates the deploy (
e2e-gate, chromium,workflow_callintoe2e.yml, indeploy.yml'sneeds; UI-scoped and skippable, an accepted trade-off under Deliberate and accepted) with 32 axe scans over the public, shopper, and Catalog/Sales/Identity admin surfaces (AccessibilityTests.cs, dark-palette home scan at:495) plus the cart-drawer keyboard test (CartDrawerKeyboardTests.cs:21, on the sharedKeyboardOperabilityTestsBase), which earns Implementation 8. The rubric pairs axe-in-CI with a recorded manual screen-reader pass: thestore-ACCESSIBILITY-SCREENREADER-PASS.mdrunbook (Websitedocs-src/guides/) exists, but its results log holds only the placeholder row (:70, header:68-69). Next: a dated NVDA/VoiceOver pass against the running Aspire app (needs a human; cannot be done headless). - #15 Best Practices & Code Quality, implementation 8 → 9: the MAUI head is outside CI analyzer enforcement. Shipped 2026-10-19 (Store #213): the weekly
maui-audit.ymlbuildsMMCA.Store.UIfornet10.0-androidin Release with analyzers and TreatWarningsAsErrors (.github/workflows/maui-audit.yml:198). Detection only, no per-PR leg. Re-score pending. The original finding follows. The MAUI head is in neither.slnf(MMCA.Store.CI.slnf:24-25lists onlyUI.WebandUI.Web.Client;maui-audit.yml:17and.editorconfig:841say so), so its analyzers and TWAE run only in a full local solution build. Its vulnerability audit is covered by the weeklymaui-audit.yml(:111-163). Next: put the MAUI head's analyzers and TWAE under a CI check. - #17 DevOps & Deployment, implementation 8 → 9: the rollback covers only a failed activation. First half shipped 2026-10-19 (Store #213): the step prints a per-app outcome and a counted summary (
deploy.yml:1521,:1529) and its comment matches the skip-live behavior (:1491). Probe-only rollback and a Bicep what-if are closed (Deliberate and accepted). Re-score pending. The original finding follows. The smoke step (deploy.yml:1355) skips every app whose newest revision is live (:1497), so a failure caught only by the tier-2 HTTP probes rolls nothing back, and the step still prints "All apps rolled back" (:1525); its comment says a healthy newest revision steps back one revision (:1487-1488), and underactiveRevisionsMode: 'Single'(infra/main.bicep:1469) the predecessor is already inactive, so the active-revision filter (deploy.yml:1504) finds no candidate. ADR-080 keeps the skip-live guard and accepts single-revision mode; no rollback rehearsal is recorded. The other residuals: no Bicep build, lint or what-if beforeazure/arm-deploy(:1315-1317), a prod-only environment (:41), and SQL dual-auth with public network access (infra/main.bicep:831-835,deploy.yml:1146). Next: make the rollback summary report what it did and align the comment (Store-local, S); then decide whether a probe-only failure should roll back, and add a Bicep what-if step before merge. Effort: S-M. - [closed] #20 Design System, implementation 8 → 9: the StoreHome palette (closed 2026-10-19, see Deliberate and accepted). The StoreHome landing stylesheet hard-codes a hex palette behind
!importantwith only--mmca-primarytokenized (Catalog.UI/Pages/Home/StoreHome.razor.css:218,289; one stylesheet in Catalog.UI serves both hosts). Next: tokenize the palette onto the theme variables in that shared stylesheet, drop the!importantoverrides, and extendBrandColorTokenTeststo the full literal set. - [~] #22 Responsive & Cross-Browser, implementation 8 → 9: density and touch. The mobile-viewport half shipped 2026-10-19 (Store #213,
Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PhoneViewportTests.cs, in the chromiume2e-gate); the density toggle and coarse-pointer CSS are closed (Deliberate and accepted). Re-score pending. The original finding follows. Density is hard-codedDense="true"with no comfortable/compact option (ProductList.razor:61,ReviewList.razor:57), and there is no coarse-pointer/touch-target CSS and no mobile-viewport E2E coverage (zeroSetViewportSizehits underTests/E2E). Common'sDataGridListPageBasecarries aDenseGridtoggle, so the density half is a Common-adoption task. - #25 Navigation, implementation 8 → 9: the navigation doc has no test to catch drift. Triage 2026-10-19: the Roles menu item now names the Admin role its page requires (Store #213, pinned by
Tests/Modules/Identity/MMCA.Store.Identity.UI.Tests/IdentityUIModuleTests.cs), and the fullNavigationContractTestsis closed (Deliberate and accepted). Shipped 2026-10-19 (Store #214): the eight detail routes are typed{Id:int}the way ADC's are (for exampleSource/Modules/Catalog/MMCA.Store.Catalog.UI/Pages/Products/ProductDetail.razor:1, with a typedint Idparameter atProductDetail.razor.cs:27in place of theId.Parse<T>()coercion), so a malformed id answers with the router's not-found page;/roles/{Role}stays an untyped role name, as in ADC, and thestore-NavigationFlow.mdRoute Contract rows now carry the constraint. Score movement is pending the next re-score. The original finding follows.store-NavigationFlow.mdmatches the shell:/usersis listed as route-tested (:15, governed byIdentityRouteAuthorizationTests.cs:26-32),/orders/{Id}is[AllowAnonymous](:66,:225, matchingOrderDetail.razor:11), the notification pages are hidden (:236,UI.Web/appsettings.json:18), and a Route Contract table lists every routable Store page with its required access (:188). No Store test reads that contract (no*Navigation*Tests.csunderTests/), so a new@pageor a changed attribute can drift from the doc unnoticed. Two smaller residuals: route parameters are untyped (ProductDetail.razor.cs:28), and the Roles menu item is shown by capability (IdentityUIModule.cs:29-35) while/rolesrequires the Admin role (RoleList.razor:2). Next: add a StoreNavigationContractTeststhat reads the Route Contract (store-NavigationFlow.md:188) and compares it with every@pageroute and its authorization attributes (the ADC pattern). Effort: M. - #27 Internationalization, implementation 8 → 9: pluralization. Shipped 2026-10-19 (Store #213):
ProductReviewsPanel.resx:9-10and theCatalogBrowserating label resolve throughL.Pluralwith.One/.Otherkeys, and the dead(s)base keys are removed in en and es. Re-score pending. The original finding follows. Money formatting is culture-aware (MoneyExtensions.cs:54,90, inside the v1.244.0 pin atDirectory.Packages.props:19). Common shipsStringLocalizerPluralExtensions.cs:46and the cart strings have.One/.Otherkeys, butProductReviewsPanel.resx:9andCatalogBrowse.resx:31are single-form count keys ("(1 reviews)"renders atProductReviewsPanel.razor:10), and single-form(s)keys survive inCartDrawer.resx:9(Aria.ItemsInCart) and:22,CartButton.resx:7,10,CatalogProductDetail.resx:65andShoppingCartList.resx:11. Store-local, not[C→A]. - #18 UI Architecture & Components, implementation 8: create and detail forms share one field block. Shipped 2026-10-19 (Store #215): each create page and its detail page's inline editor render one field block bound to one abstract form model with create and edit subclasses, so the two forms cannot drift apart:
CategoryFormFieldsoverCategoryFormModel(Catalog.UI/Pages/Categories/CategoryCreate.razor:35,CategoryDetail.razor:42),ProductFormFieldsoverProductFormModel(Products/ProductCreate.razor:34,ProductDetail.razor:43) andCustomerFormFieldsoverCustomerFormModel(Identity.UI/Pages/Customers/CustomerCreate.razor:27,CustomerDetail.razor:42). The detail editors validate through the model's DataAnnotations instead of page-local checks, and each block has a bUnit test (CategoryFormFieldsTests,ProductFormFieldsTests,CustomerFormFieldsTests). Re-score pending; the 400-line code-behind cap is unchanged. - TD · #27: six Spanish count values drop the accent. Shipped 2026-10-19 (Store #213): the plural forms now carry the accent their base keys had. The original finding follows. The
.Oneand.Othervalues inShoppingCartList.es.resx:12-13,CartDrawer.es.resx:23-24and theAria.ItemsInCart.One/.Otherpair atCartDrawer.es.resx:10-11spell the noun without its accent while the base keys they pair with (ShoppingCartList.es.resx:11,CartDrawer.es.resx:9,22) keep it, so a Spanish shopper sees two spellings of the same word depending on the count. User-visible, Store-local. Blocker: none. Resolution path: correct the six resx values;TranslationCompletenessTestschecks presence, not spelling, so add the accented form to the pseudo-loc leak probe only if this recurs. Effort: S.
Implementation band
22 categories, 54 gap points, the largest of the three repos. Levers are cited only where this ledger or the scorecard records one; an unnamed lever is named at the next re-score, never invented here. Ties break by priority desc, then weight desc, then category asc. The rows and levers below are as scored on 2026-10-18. After the 2026-10-19 triage, the #10, #15, #17 (rollback report), #22 (phone viewport) and #27 levers have shipped (Store #213, re-score pending); every row marked "not yet identified", and the #20, #22 (density, touch) and #25 (contract test) levers, are closed (Deliberate and accepted). The #25 typed routes have since shipped (Store #214, re-score pending), and so has the #18 field-block reuse (Store #215, re-score pending). The remaining scheduled work is the #11 seed-account check and the #21 screen-reader pass.
| implPriority | # | Category | w | Impl | Recorded lever |
|---|---|---|---|---|---|
| 6 | #11 | Security | 3 | 7 | the plaintext Redis cache of the Stripe signing secret (SharedCacheStripeWebhookSecretStore.cs:79, 365-day lifetime :54); the shared db_owner identity (infra/SQL-MANAGED-IDENTITY.md:80) and the Manage-rights SAS rules (infra/main.bicep:1069,1081,1093, rationale :1052-1057) are accepted caps under Deliberate and accepted; seed-account rotation is operational |
| 3 | #1 | SOLID Principles | 3 | 8 | the accepted constructor ceilings cap it: three controllers at 8 dependencies, the rubric's 8+ red flag (controller ceiling 8 at ConstructorDependencyCountTests.cs:53), with AuthenticationService at the service ceiling of 6 (AuthenticationService.cs:26-32, ConstructorDependencyCountTests.cs:35), recorded under Deliberate and accepted; lowering a ceiling means splitting the class that holds it |
| 3 | #7 | Microservices Readiness | 3 | 8 | the accepted lockstep deploy caps it: one azure/arm-deploy of the whole main.bicep (deploy.yml:1317), all five images on one IMAGE_SHA (:1145), whole-fleet rollback block (:1481-1527, app list :1363), recorded under Deliberate and accepted; no lever scheduled |
| 3 | #10 | Messaging & Integration Architecture | 3 | 8 | the broker error queue has no alert, runbook or replay path (infra/main.bicep:462 matches only the outbox text; Common FaultIntegrationEventConsumer.cs:57), plus the stale consumer inventory at infra/OPERATIONS.md:164, no real-broker round-trip for CustomerErased/ProductInfoChanged, and no declared ordering for ProductVariantChanged; #10 is also in the maturity band |
| 3 | #18 | UI Architecture & Components | 3 | 8 | field-block reuse shipped 2026-10-19 (Store #215, re-score pending): each create page and its detail page's inline editor render one shared CategoryFormFields, ProductFormFields or CustomerFormFields block over one abstract *FormModel; as scored on 2026-10-18 the lever was not yet identified (held at 8: four code-behinds sit near the 400-line cap, ProductReviewsPanel.razor.cs at 389 lines the largest, 11 under the cap; the cap stays at 400, see Deliberate and accepted) |
| 3 | #19 | State Management & Data Flow | 3 | 8 | not yet identified (held at 8: no client-side staleness or invalidation strategy, ShoppingCartService.cs:18, with the IUiReadCache opt-in withdrawn) |
| 3 | #21 | Accessibility (a11y) | 3 | 8 | not yet identified (the dated SR pass is the maturity half; #21 is also in the maturity band) |
| 3 | #28 | Front-End Testing & Quality | 3 | 8 | not yet identified (held at 8: no markup or visual-regression tier) |
| 2 | #5 | Vertical Slice Architecture | 2 | 8 | the layered-by-project hybrid, a deliberate design choice capping impl at 8 |
| 2 | #6 | CQRS & Event-Driven | 2 | 8 | not yet identified (held at 8: the ProductVariantChanged Added path is transactional, AddVariantCommand.cs:25 is ITransactional and the publish is a durable InternalCommands row in the same transaction, AddVariantHandler.cs:79-82,92-97, but a refused schedule is logged at Error and not retried, :98-100) |
| 2 | #12 | Performance & Scalability | 2 | 8 | not yet identified (the monthly k6 run plus load-freshness is the accepted posture, see Deliberate and accepted) |
| 2 | #15 | Best Practices & Code Quality | 2 | 8 | the MAUI head's analyzers/TWAE are review-only (MMCA.Store.CI.slnf:24-25, maui-audit.yml:17, .editorconfig:841) |
| 2 | #17 | DevOps & Deployment | 2 | 8 | the post-deploy rollback covers only a failed activation: it skips every app whose newest revision is live (deploy.yml:1497) and still reports "All apps rolled back" (:1525); then a Bicep what-if before merge (:1315-1317), a non-prod environment (:41), and SQL dual-auth with public access (infra/main.bicep:831-835) |
| 2 | #20 | Design System & UI Consistency | 2 | 8 | tokenize the StoreHome hard-coded !important hex palette in the one stylesheet both hosts share (Catalog.UI/Pages/Home/StoreHome.razor.css:218,289) and extend BrandColorTokenTests to cover it |
| 2 | #22 | Responsive & Cross-Browser | 2 | 8 | density is hard-coded Dense="true" with no comfortable/compact option (ProductList.razor:61, ReviewList.razor:57), and there is no coarse-pointer/touch-target CSS and no mobile-viewport E2E coverage; Common's DataGridListPageBase carries a DenseGrid toggle, so the density half is a Common-adoption task |
| 2 | #23 | Front-End Performance | 2 | 8 | not yet identified |
| 2 | #24 | Forms, Validation & UX Safety | 2 | 8 | the accepted cap: client validation is DataAnnotations-level with per-field parity, and full FluentValidation parity on the client is not pursued (Deliberate and accepted); no lever scheduled |
| 2 | #25 | Navigation & Information Arch | 2 | 8 | a Store NavigationContractTests that reads the Route Contract in the published store-NavigationFlow.md (:188) and compares it with every @page route and its authorization attributes (the ADC pattern); the doc itself matches the shell (:15, :66, :225, :236) |
| 2 | #30 | Compliance, Privacy & Governance | 2 | 8 | not yet identified, named at the next re-score (consent capture is the candidate: a lawful basis per [Pii] member is recorded in MMCA.Store/PRIVACY.md:121-125, section 12, and checked by Tests/Architecture/MMCA.Store.Architecture.Tests/Privacy/PiiInventoryTests.cs:13, but no consent capture exists in Source; the data-subject-rights surface has a UI, the Profile "Your data" card with export and self-service delete, MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.UI/Pages/Users/Profile/YourDataCard.razor, mounted at Profile.razor:111) |
| 2 | #31 | Cost Efficiency / FinOps | 2 | 8 | not yet identified (held at 8: the surge guard is read-only, cost-guard.yml:7, the recorded posture under Deliberate and accepted, and the budget is whole-RG with no tag filter, infra/main.bicep:793-821; per-service service tags exist on every container app and service database, :982, :1458, :1640, :1764, :1916, :2045) |
| 2 | #33 | Developer Experience & Inner Loop | 2 | 8 | not yet identified (the Service Bus emulator tier gates through recency rather than per PR; see Deliberate and accepted) |
| 1 | #27 | Internationalization (i18n) | 1 | 8 | pluralization: ProductReviewsPanel.resx:9 and CatalogBrowse.resx:31 are single-form count keys ("(1 reviews)" renders at ProductReviewsPanel.razor:10) and single-form (s) keys survive (CartDrawer.resx:9,22, CartButton.resx:7,10, CatalogProductDetail.resx:65, ShoppingCartList.resx:11); Store-local, not [C→A]; the accent defect is the TD under #27 |
Maturity band
3 categories, 8 gap points. Ties break by priority desc, then weight desc, then category asc.
| priority | # | Category | w | Mat | Recorded lever |
|---|---|---|---|---|---|
| 3 | #10 | Messaging & Integration Architecture | 3 | 3 | an operational procedure for the broker error queue (ArchitectureEvaluationCriteria.md:336): an alert-as-code on the error-queue log line or broker metric, an infra/OPERATIONS.md replay-or-discard runbook, and a fitness rule requiring [EventName] (Common-side); the same levers as ADC's §10 |
| 3 | #21 | Accessibility (a11y) | 3 | 3 | a dated manual NVDA/VoiceOver pass in store-ACCESSIBILITY-SCREENREADER-PASS.md (needs a human; the results log holds only the placeholder row at :70, header :68-69) |
| 2 | #12 | Performance & Scalability | 2 | 3 | the accepted cap: no latency check runs on the merge path, and the monthly k6 run plus load-freshness is the recorded posture (Deliberate and accepted); the load-freshness break-glass skip is at deploy.yml:818-819 (job at :807), the gate keys on the latest SUCCESSFUL k6 run (:810-811) so a threshold failure blocks nothing, and deploy.needs at :1084 contains no perf job; no lever scheduled |
Deliberate and accepted
- Closed as over-engineering, 2026-10-19. Each was weighed against one developer and about 67 peak users; none has a defect behind it that an existing gate misses, so each would add permanent machinery for a score point. Do not re-propose without new evidence (a named re-open trigger, an incident, or an explicit ask).
- #11 Data Protection wrap of the cached Stripe signing secret (user decision): the Sales host registers no shared key ring, so the wrap would first need key-ring infrastructure, for a secret whose only exposure is read access to Redis.
- #10 real-broker round-trips for
CustomerErased/ProductInfoChangedand an ordering declaration forProductVariantChanged: the contract snapshot and the required[EventName]rule cover the wire-name failure; the ordering race is theoretical at this load. - #17 Bicep what-if before
arm-deploy, and probe-only rollback: what-if needs Azure credentials in pull-request workflows,arm-deployalready fails on bad Bicep, and ADR-080 keeps skip-live. - #20 StoreHome palette tokenization: cosmetic; the dark-palette axe scan passes and the brand token is already guarded.
- #22 density toggle and coarse-pointer touch-target CSS: no shopper need; the phone-viewport E2E now guards the shopper pages.
- #25 full
NavigationContractTestsagainst the Website Route Contract: it protects a document and would make every route change wait on a Website PR; the route-authorization tests already gate every Admin page. - #28 port of ADC's
ComponentsSnapshotTests: brittle on every MudBlazor bump. - #6
AddVariantHandlerreturning a failure on a refused schedule: the lever is wrong. The handler reports success on purpose, because a retry with a null SKU would create a duplicate variant. - Rows with no recorded lever (#18, #19, #23, #30, #31, #33): #18 is under the cap, which forces a split when due (the field-block reuse that has since shipped, Store #215, is recorded under Open items); #19's client cache was withdrawn after staleness failures; #23's product-image derivatives are a storage redesign while the browse CWV budgets pass; #30's consent capture has no processing that needs consent (no marketing mail); #31 and #33 are recorded postures below.
- TD product-image derivatives (#12, #23): closed with #23 above. Re-open: a browse-page Web Vitals budget failure.
- §16 AI-Native Application Architecture is N/A (rubric v2, ADR-110): no product feature calls a language model, so its weight 2 leaves both denominators until one does.
- #5 layered-by-project hybrid. Cross-cutting concerns live in the pipeline and module projects keep a layered split; a deliberate design choice that caps §5 at Implementation 8, not a maturity deduction.
- Chromium-only deploy E2E gate (#22, #21): accepted CI-cost trade-off. The deploy-gating
e2e-gateruns a single engine (job atdeploy.yml:752, rationale:743-751,browsers: '["chromium"]'at:765); firefox and webkit run on the advisory schedule, one alternating engine per week (thebrowsermatrix expression ate2e.yml:139-142, Monday firefox:140, Thursday webkit:141, crons:46-47, schedule-scopedcontinue-on-errorat:149). Thecross-browser-freshnessgate bounds each engine's blind window at 10 days as a harddeploycondition (deploy.yml:857,:1084,:1114). Revisit if a webkit-only or firefox-only defect reaches production, which is the risk being priced. The UI-scoping of the gate is the separate entry below. - UI-scoped
e2e-gate(#21, #22, #28): accepted Actions-minute trade-off, next to the chromium-only entry. Thee2e-gatejob runs only whenneeds.changes.outputs.ui == 'true'(deploy.yml:762, job at:752, rationale comment:754-761, chromium-only input at:765), and thedeployjob accepts it assuccessORskipped(:1117, withe2e-gatein the deployneedslist at:1084). On a backend-only merge the 32 WCAG 2.1 AA axe scans (Tests/E2E/MMCA.Store.E2E.Tests/Workflows/AccessibilityTests.cs) and the Playwright workflow suite do not run. Backend tests precede every code deploy: the requiredbuild-and-testcheck runs theMMCA.Store.CI.slnfunit + architecture + bUnit tier (deploy.yml:255, test run at:338), branch protection enforces admins with strict up-to-date required checks, so the merged tree is the PR-tested tree, and the post-deploy smoke gate probes Catalog and Sales through the gateway. Revisit if a UI defect reaches production through a backend-only merge. - TD · #22: the
cross-browser-freshnessgate is break-glass skippable. The gate proves a green firefox and webkit leg within 10 days (deploy.yml:873), not that this exact tree passes on those engines, and theskip_freshness_gatesworkflow_dispatchinput (declared atdeploy.yml:26) that also skips #12'sload-freshnessskips it with a justification (:880-881). Accepted as priced on the ADC precedent, whose row carries the identical caveat. Blocker: none; a per-change three-engine gate is the Actions-minute cost the chromium-only entry prices. Resolution path: none scheduled; revisit if a single-engine defect reaches production or the break-glass is used outside an incident. Effort: S (denying the skip is oneif:change). - #33 Service Bus emulator tier gates through recency.
Tests/Integration/MMCA.Store.ServiceBusEmulator.IntegrationTestsruns MassTransit v8 against the official Service Bus emulator (pinned 2.0.1) with the realProductVariantChangedcontract, andcross-service-freshnessrequires both it and the RabbitMQ tier to have succeeded in the same nightly run (thecross-service-freshnessstep of thefreshnessjob,deploy.yml:826, required jobs:841-843,freshnessin deployneedsat:1084). The tier needs Docker, so it stays out of both solution filters and reaches the deploy chain through the recency check rather than per PR. Mirrors ADC's TD-17; ADR-066 records the gated end state in both consumers. - TD · Product-image derivatives (#12, #23): closed 2026-10-19. Images are full-size DB blobs streamed as-is and rendered as card thumbnails with no
srcset/dimensions, so a 12-card browse grid can pull 12 full-size assets. Fixing it is a storage-design decision, not a local change; closed as over-engineering (above). - Single-region deployment: accepted in
infra/DISASTER-RECOVERY.md(real load does not justify multi-region cost). - All per-service DBs on one physical SQL server: logical isolation is complete; the shared server is a cost choice (minor §7/§8).
- #7 lockstep deploy: per-service image parameters are not adopted. The deploy is one
azure/arm-deployof the wholemain.bicep(deploy.yml:1317) with all five images on oneIMAGE_SHA(:1145) and a whole-fleet rollback (:1481-1527). Splitting the image parameters per service would add permanent complexity to the deploy and rollback logic for a five-app, one-developer fleet, so the lockstep deploy is accepted and caps section 7 at Implementation 8. The build half stays per-image incremental (per-image dirty flags in thechangesjob,deploy.yml:80-218, matrix:956). - #11 per-service database identities and Bicep-created Service Bus topology: not adopted. One shared identity is
db_ownerin all three databases (infra/SQL-MANAGED-IDENTITY.md:80) and every per-app SAS rule keeps Manage rights (infra/main.bicep:1069,1081,1093) because MassTransit creates the topology at runtime (:1052-1057). Pre-creating the topology in Bicep would make every new consumer a Bicep edit, and a missing subscription loses messages silently, so the shared identity and SAS Manage are accepted at this scale. They remain red flags on the section 11 row. - #12 in-band k6 on every deploy, and a shopper script that writes to production: not adopted. Either would load production on every deploy at a peak of about 67 users. The monthly
load-test.ymlrun (:21, dispatch at:12) plus the 35-dayload-freshnessdeploy gate (deploy.yml:807, window:816) is the accepted posture, with the Web Vitals budgets asserted per deploy in the chromiume2e-gate; section 12 stays at Maturity 3 on it. - #18 code-behind cap lowered to 300: not adopted. Lowering the cap would force about a dozen extractions at once; the cap stays at 400 (
UIArchitectureConventionTests.cs:9), and only the files near 400 are candidates for extraction (ProductReviewsPanel.razor.csat 389 lines is the largest). - #31 scheduled revert of surge drift: not adopted.
cost-guardstays read-only (cost-guard.yml:7, manual reset printed at:109), because a scheduled revert would act on production unwatched (schedule detection, never action). The guard still blocks the deploy on drift (deploy.yml:730,1113) and runs on the Monday cron (cost-guard.yml:21); it caps section 31 at Implementation 8, the same basis as ADC's. - SQL public network access (no VNet): the documented acceptance behind the §17 cap, the same caveat ADC carries.
- ADR-042 device capability abstraction: latent. The core extension point is converged (Store wires browser + MAUI capabilities via
UseMauiDeviceCapabilities/AddBrowserDeviceCapabilitiesand renders the sharedOfflineBanner), but Store consumes no further capability in its own product UI: noExternalLink, noDeviceUIModule/DeepLinkListener, no app actions. No Store product page carries an external anchor, so the WebView dead-end risk is latent, not a live defect. AdoptExternalLinkon any Store product page that grows an external anchor, and register a StoreIUIModulewithDeepLinkListener(the way ADC'sDeviceUIModuledoes) if a Store MAUI feature surface is wanted. The framework side is complete in Common (18 capability contracts + theMMCA.Common.UI.Mauipackage), so this is consumer-side only, not[C→A]. - ADR-043 adoption (mobile deep links / app association / native OAuth callback): deferred. Adoption is feature-scale (Store-scheme deep links, iOS/Android manifest entries, associated domains) and rides the same trigger as ADR-042: adopt when a Store MAUI surface is actively wanted.
- ADR-044 adoption (native push, third notification channel): deferred. Store has no user-notification pipeline (no ADR-024 inbox, no SignalR channel), so adoption means adopting the whole notification stack first: a product decision, not remediation. Schedule only if Store wants user notifications.
- #14 dedicated rate-limit fixtures: skipped. The WAFs neutralize the limiter and a tight-limit variant is low value for the volume; revisit only if abuse is observed.
- #11 login throttle shared by Blazor Server circuits. The per-IP anti-spray throttle inherited from Common's
AuthControllerBasecounts every Server-circuit user against the UI host's IP (a 30/min window), because Blazor Server issues the login call server-side. Watch the 429 rate; tune withAddCommonRateLimiting(authIpPermitLimit: N). - #1 constructor ceilings (services 6, controllers 8, handlers 7). Set in
ConstructorDependencyCountTests.cs:35,53,71:AuthenticationServiceholds the service ceiling at 6 (AuthenticationService.cs:26-32), and three controllers sit at 8 (ShoppingCartsController.cs:51,ReviewsController.cs:55,ProductVariantsController.cs:37). The controller ceiling sits at the rubric's 8+ dependency red flag by recorded choice, which caps §1 at Implementation 8 the way the layered hybrid caps §5. The clock fitness rule carries one allowlist entry, the backfill-onlyOrder.RepublishFulfillment(ClockReadTests.cs:18). - #11 threat model: not maintained, by decision. No threat-model document is kept for Store, matching the Common backlog; §11 is scored on its other red flags, and a re-score does not add a threat-model item.
- #23 catalog trade-offs: price sorts fall back to Newest; search is Name-contains.
- #24 server-only and inline rules: SKU uniqueness stays server-only (it needs the DB); the Profile-form password fields stay inline by design, in both consumers.
- #24 full FluentValidation parity on the client: not adopted. All four create forms (
CategoryCreate,ProductCreate,CustomerCreate,InventoryItemCreate) source their field rules from a DataAnnotations form model through Common'sModelValidationbridge, and the existing per-field parity is kept: email format (mirroringCustomerEmailRules' algorithm) on CustomerCreate and CustomerDetail, and NewPassword min-length/128-cap plus confirm-match on the Profile change-password form (localized en + es keys). Full parity would duplicate the server rule set, so it caps section 24 at Implementation 8. - #19 silent cart initialization:
CartStateService.InitializeAsyncloads the cart without raisingOnChange; initialization is silent and only user-visible mutations notify (pinned intended behavior). OrderDetailwithout[Authorize]: a recorded deliberate choice from the E2E gap review.
Resolved
| # | Category | M / I | Evidence |
|---|---|---|---|
| #1 | Controllers split by sub-resource | M4 / I8 | ReviewModerationController.cs:48, ReviewEligibilityController.cs:27, ProductAttributesController.cs:42, OrderFulfillmentController.cs:39: same routes, verbs, authorization, cache, idempotency and If-Match attributes |
| #2 | Design Patterns | M4 / I9 | Factory→Result, state machine, saga, specification (Order.cs:191,149-158, Orders/Saga/OrderCancelledSagaHandler.cs) |
| #3 | Clean Architecture | M4 / I9 | StoreArchitectureMap.cs:14-43 + LayerDependencyTests/DomainPurityTests; DecoratorPipelineOrderTests and HandlerResultConventionTests in the arch tier |
| #4 | Domain-Driven Design | M4 / I9 | Order.cs (Money :58, OrderInvariants, OrderIdentifierType) |
| #7 | Database-per-service | M4 / I8 | Store_Catalog/Store_Sales/Store_Identity, each with its own outbox (infra/main.bicep:966-999); the legacy MMCAStore DB is archived to a bacpac and absent from IaC (:954-966) |
| #8 | Data Architecture | M4 / I9 | Atomic conditional-UPDATE decrement (InventoryAllocationService.cs:71-73), CHECK backstop (InventoryItemConfiguration.cs:57-59), single-transaction checkout write phase (CheckOutHandler.cs:80), stale-version round trips answering 412 / 428 in Catalog and Sales (StaleRowVersionConflictTests.cs:19,41, OrderTransitionConcurrencyTests.cs:25,64), raw-IQueryable ban with an EMPTY allowlist (RawQueryableConventionTests.cs:14 in MMCA.Store.CI.slnf:53); protect by keeping the decrement atomic and the CHECK in place |
| #8 | Expand/contract migration guard | M4 / I9 | Step in the required build-and-test job (deploy.yml:431-482, fail-closed at :460, job at :255), policy at CONTRIBUTING.md:56 |
| #9 | API & Contract Design | M4 / I9 | v2 ServiceInfoController (ServiceInfoController.cs:19) + seven contract-guard files in the required integration-tests check; ProtoContractTests.cs:9 |
| #11 | Customers gRPC edge requires a user or service token | M4 / I7 | .RequireAuthorization(ServiceTokenDefaults.InternalCallerPolicy) at Identity.Service/Program.cs:343 (scheme :173), Sales AddServiceTokenClient at Sales.Service/Program.cs:289, Key Vault secret Grpc--ServiceToken--SigningKey at infra/main.bicep:1366-1369, deploy fail-fast on a missing GRPC_SERVICE_TOKEN_SIGNING_KEY (deploy.yml:1226-1230), pinned by CustomersGrpcAuthorizationTests.cs:40 |
| #11 | Seed-account environment gate (code) | M4 / I8 | IdentityModuleSeeder.cs:28-30 on Seeding:IncludeSampleUsers (default false), pinned by IdentityModuleSeederTests |
| #11 | UI host hardening taken from Common | M4 / I8 | UI.Web/Program.cs:24,78,86,95,227; host-wiring facts UiRateLimitingTests.cs:29, BoundedCircuitHandlerTests.cs:30; /hubs exempt by declaration (UiRateLimitingExtensions.cs:42); ADR-088 |
| #11 | Stripe signing secret kept off stderr | M4 / I7 | The secret goes only to the secret store; the Critical log names the endpoint id (StripeWebhookRegistrationService.cs:200-202) |
| #11 | Password-spray throttle | M4 / I8 | Common's per-IP throttle on the inherited AuthControllerBase.LoginAsync/RegisterAsync |
| #12 | Batched bulk-inventory existence check | M3 / I8 | One GetExistingIdsAsync round trip (Sales.Application/Inventory/UseCases/BulkSet/BulkSetInventoryHandler.cs:36-39, rationale :31-35); GetUnitPricesAsync is not reusable because it drops variants whose Price is null |
| #12 | Output cache shared across replicas | M3 / I8 | Catalog calls Common's AddRedisOutputCaching (Catalog.Service/Program.cs:99), which registers the Redis output-cache store (MMCA.Common.Aspire/Caching/RedisCachingExtensions.cs:91, :99), so tag eviction reaches every replica (ADR-040 amendment) |
| #12 | Order hot predicates indexed | M3 / I8 | Filtered (Status, LastModifiedOn, CreatedOn) index for the reconciliation sweep; (CustomerId, CreatedOn) for the order grids |
| #12 | Admin order search | M3 / I8 | OrderList sends an equality match on the int Id when the input parses (no unsupported CONTAINS) |
| #12 | Warm-up tasks | M3 / I8 | Closes the finding that warm-up primed keys the storefront never requests and called [Authorize] endpoints unauthenticated |
| #12 | Customer enrichment | M3 / I8 | GetByIdsAsync issues one request via the Id IN (...) operator |
| #12 | Cart variant lookup | M3 / I8 | GetVariantCartInfoHandler projects rather than loading whole Product aggregates |
| #12 | Post-checkout Stripe polling | M3 / I8 | Banded 2s/3s/4s/6s back-off; Stripe is not called before attempt 5 |
| #12 | Load and CWV coverage | M3 / I8 | catalog-read-load.js exercises the real storefront key plus two cache-miss shapes, hit/miss thresholds split by tag; product detail has a CWV budget |
| #12 | Projected order-line count | M3 / I8 | Order.LineCount (Order.cs:108, maintained at :227), OrderConfiguration.cs:52, OrderDTO.cs:53, 20260831011924_AddOrderLineCount.cs, grid column OrderList.razor:121 (rationale :118) |
| #13 | Observability & Operability | M4 / I9 | ObservabilityConventionTests.cs:3,13 pairs every sloAlertSpecs key with an infra/OPERATIONS.md section; workbook main.bicep:745 |
| #14 | Testability & Test Strategy | M4 / I9 | Self-filtered line floor 51.6 (deploy.yml:350-376) and Application-layer branch floor 72.8 with a 3-assembly vacuity guard (:378-409, Store commit 89692c78); real-SQL integration tier (money paths, GDPR, refresh rotation, contract guards, [Idempotent] replay) on SqlServerIntegrationTestFixtureBase; cross-service-freshness recency gate |
| #15 | Global NoWarn rationale |
M4 / I8 | Every global code documented (Directory.Build.props:16-30), RMG020 scoped to .Application (:60-68), test-tier group (:50-58) |
| #15 | Audit suppressions current | M4 / I8 | No advisory is accepted (Directory.Build.props:45-46 declare NuGetAudit / NuGetAuditMode=all; the accept-an-advisory procedure is at :36-44); MAUI vulnerability audit weekly (maui-audit.yml:35, :111-163) |
| #15 | Checkout decomposition | M4 / I8 | CheckOutHandler.HandleAsync is 45 lines (CheckOutHandler.cs:50-94); reads in CheckOutPreflight.cs:30, deadline arming in CheckOutDeadlineScheduler.cs:25; 7 constructor dependencies |
| #15, #17, #34 | Readiness probes for the h2c services | M4 / - | Catalog and Identity expose an Http1 probe listener on HealthProbe:Port 8081 with the three-probe httpGet form |
| #18 | UI architecture gate | M4 / I8 | UIArchitectureConventionTests.cs:9 (400-line code-behind, 120-line @code caps); OrderSummaryPanel, OrderLinesPanel, ProductVariantsPanel extracted |
| #19 | State management gate | M4 / I8 | StateManagementConventionTests.cs:10; Sales.UI/DependencyInjection.cs:34 (TryAddScoped) |
| #19 | Drawer state through the notify path | M4 / I8 | ICartStateService.cs:45, :102-108; CartStateService.cs:59, :331-341; CartDrawer.razor:3 |
| #19 | Superseded-load guard | M4 / I8 | All seven detail pages incl. CategoryDetail.razor.cs:70, CustomerDetail.razor.cs:93, InventoryItemDetail.razor.cs:66, plus UserDetail.razor.cs:90,98 |
| #19 | Cart service on the framework base | M4 / I8 | CartStateService on AuthenticatedServiceBase (Store #166/#167) |
| #20 | Inline-style sweep | M4 / I8 | Zero Style=/CellStyle= in razor; global classes in store.css:28 and app.css:70 (MudBlazor-rendered markup carries no isolation scope); pinned by CategoryListTests.cs:127 |
| #20 | Brand-color token guard | M4 / I8 | BrandColorTokenTests.cs:10 (commit 5fbd003); the shared home CSS (Catalog.UI/Pages/Home/StoreHome.razor.css, embedded at MMCA.Store.Architecture.Tests.csproj:10) on var(--mmca-primary) |
| #21 | Axe breadth | M3 / I8 | 32 scans (AccessibilityTests.cs:30-623) in the deploy-gating chromium e2e-gate |
| #21 | Cart-drawer keyboard gate | M3 / I8 | CartDrawerKeyboardTests.cs:21 on the shared KeyboardOperabilityTestsBase: skip link and focus indicators on home and catalog, drawer open/trap/Escape contract |
| #22 | Cross-browser freshness gate | M4 / I8 | cross-browser-freshness (deploy.yml:857, window :873) in deploy.needs (:1084), required at :1114 |
| #23 | Front-End Performance | M4 / I8 | Server-paged CatalogBrowse (GetPagedAsync + bounded MobileInfiniteScrollList), batch variant lookup (CartStateService.cs:415-430), hard CWV budgets (WebVitalsTests.cs:75-76,101, asserted by Common WebVitalsCollector.cs:137) |
| #24 | Forms gate and model-sourced rules | M4 / I8 | FormsConventionTests (MinimumCreateForms=4); DataAnnotations models through ModelValidation on all four create forms |
| #25 | Route authorization | M4 / I8 | *RouteAuthorizationTests in MMCA.Store.CI.slnf:40,46,52, governing all 19 Admin-gated pages (IdentityRouteAuthorizationTests.cs:26-32 lists the /users pair) |
| #25 | Notification-pages framework gate | M4 / I8 | Common NotificationPageGate.cs:29-31 hides the pages when no notification UI module is registered; Store opts in at UI.Web/appsettings.json:18 and UI.Web.Client/wwwroot/appsettings.json:16 (the MAUI head does not set it) |
| #26 | Front-End Security | M4 / I9 | ServerTokenStorageService.cs:9-12, BlazorCspPolicyProvider.cs:105-107 (script-src, style-src, img-src), :113 (frame-ancestors 'none'); the same-origin data-call proxy lever is Common-owned and recorded once as TD-08 |
| #10 | Broker error-queue alert and runbook | M3 / I8 | infra/main.bicep:510, infra/OPERATIONS.md:175, five consumer flows in the outbox runbook (Store #213); [EventName] required by Contracts/EventConventionTests.cs:3 |
| #15 | MAUI head compiled weekly | M4 / I8 | .github/workflows/maui-audit.yml:198 (Release, analyzers, TWAE; Store #213) |
| #17 | Rollback reports what it did | M4 / I8 | Per-app outcome and counted summary at deploy.yml:1521,1529, comment at :1491 (Store #213) |
| #22 | Phone-viewport E2E | M4 / I8 | Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PhoneViewportTests.cs in the chromium e2e-gate (Store #213) |
| #23 | Pinned EFIGS ICU shard | M4 / I8 | MMCA.Store.UI.Web.Client.csproj:17 (BlazorIcuDataFileName; Store #213) |
| #25 | Roles item matches its page guard | M4 / I8 | IdentityUIModule.cs gates Roles on RoleNames.Admin, pinned by IdentityUIModuleTests.cs (Store #213) |
| #27 | Plurals and Spanish accents | M4 / I8 | ProductReviewsPanel.resx:9-10 (.One/.Other), (s) keys retired in en and es (Store #213) |
| #27 | Culture-aware money formatting | M4 / I8 | MoneyExtensions.cs:54,90 (CurrentCulture), currency symbol at :18-20,54-59, pin at Directory.Packages.props:19 |
| #27 | Literal sweep and pseudo-localization | M4 / I8 | LocalizedTextConventionTests.cs:18, TranslationCompletenessTests.cs:17, PseudoLocalizationTests.cs:41 |
| #28 | Front-End Testing & Quality | M4 / I8 | E2E + axe deploy gate; bUnit 364 facts across 47 files |
| #29 | Resilience & Business Continuity | M4 / I9 | dr-freshness in deploy.needs (deploy.yml:1084, :1114), weekly dr-drill.yml:33, GracefulShutdownTests.cs |
| #29 | DR drill targets the live databases | M4 / I9 | ISO-week rotation across Store_Catalog/Store_Sales/Store_Identity (MMCAStore by dispatch only), 60-min timeout, skip_freshness_gates break-glass with mandatory justification on the three freshness gates; each live DB dispatch-drilled PASS (3.6 / 2.1 / 1.3 min) |
| #30 | Personal-data inventory with lawful basis | M4 / I8 | PRIVACY.md:121-125 (section 12: purpose, lawful basis and retention per [Pii] member), checked by Privacy/PiiInventoryTests.cs:13 in the architecture tier |
| #31 | Cost-guard deploy gate | M4 / I8 | cost-guard job in deploy.needs (deploy.yml:730,1084,1113); the SQL check accepts only Basic or Standard S0, so a scaled-up Standard objective reads as drift (cost-guard.yml:62-68, :102, Store commit 398e70fc) |
| #31 | Alert receivers | M4 / I8 | alertEmailAddress is a required parameter and deploy.yml fails fast naming a missing ALERT_EMAIL |
| #32 | Dependency & Supply-Chain | M4 / I9 | --locked-mode restores (deploy.yml:283,545,655), gating suppress-aware audit, SBOM hard gate (43d733f) |
| #33 | Service Bus emulator tier authoritative | M4 / I8 | No continue-on-error on servicebus-emulator-smoke (cross-service-tests.yml:141, do-not-re-add note :133); both-jobs check deploy.yml:826-847, break-glass :846-847 |
| #33 | README | M4 / I8 | Getting-started path |
| #34 | Architecture Governance & Docs | M4 / I9 | 62 fitness-test classes under Tests/Architecture/MMCA.Store.Architecture.Tests/ (64 .cs files with the map and GlobalUsings.cs), including Governance/SubstitutabilityTests.cs:14 and Privacy/PiiInventoryTests.cs:13; framework-wide fitness figures at MMCA.Common/FACTS.md:51; the single per-repo ledger |
| - | Framework adoption E2/E4/E7/E8 | - | PaymentReconciliationService on PeriodicBackgroundService, the shared SQL readiness check, the observability and graceful-shutdown test bases |
| S-1 | Stripe network errors in the Result pattern | - | StripePaymentService handles HttpRequestException in both resilience predicates and maps it to Payment.Stripe.SessionCreationFailed / Payment.Stripe.SessionRetrievalFailed |
| S-2 | Zero-decimal currency guard | - | Two-decimal allow-list (USD, EUR; extend together with Currency.All), Payment.Stripe.UnsupportedCurrency, AwayFromZero rounding |
| S-3 | BuyNow drawer half-failure parity | - | CheckoutAndPayAsync returns CheckoutOutcome(StripeUri, OrderId) and persists the orphan-recovery token on failure; CartDrawer navigates to /orders/{id} |
| S-4 | Case-insensitive Admin check in GDPR handlers | - | DeleteUserHandler/ExportUserDataHandler use UserRole.IsAdmin |
| - | Checkout and upload hygiene | - | "Buy now" reuses the cart checkout flow (idempotency key included); one Stripe failure-mapping helper; image uploads checked by content; Sales handlers on the injected clock (Store #166/#167) |
| - | UI and test consolidation | - | StoreHome once in Catalog.UI; inventory restore keeps its Result; catalog sort and category E2E tests assert real results; OrderDetailTestBase; fakes from MMCA.Common.Testing; LatestLoadGuard on nine pages; read-repository and clock fitness rules (Store #166/#167) |