to navigate Enter to open "…" all these words ANDOR to combine

Architecture governance

MMCA.Store: Architecture Remediation Backlog

Derived from ArchitectureScorecard.md (two-axis: Maturity 97.8% (309/316) / Implementation 83.9% (663/790) after the rubric v2 rebase of 2026-09-04 (§10 re-weighted to 3 at carried I8, so its implPriority rises 2→3; §16 N/A; Σweight 80→79; no score moved; ADR-110), full re-score 2026-07-28, re-confirmed with no score moves on the 2026-08-14, 2026-08-23, 2026-09-01 and 2026-09-04 full re-scores (the last being the first scored against rubric v2), framework pin v1.185.0, HEAD 04afa39d). Items are ranked on both scorecard axes, one band per axis (two-axis policy adopted 2026-07-28, replacing the previous "or a notable implementation gap" wording, which had no number behind it and so never scheduled anything):

  • Maturity band: categories scoring maturity < 4, ranked by priority = (4 − maturity) × weight.
  • Implementation band: categories scoring implementation <= 8, ranked by implPriority = max(0, 9 − implementation) × weight. The scheduling target is 9, not 10 (updated 2026-08-14 for the 2026-08-01 recalibration: a 10 is now awardable for an almost perfect implementation, but ranking against 10 would 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).

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, so the trend line stays comparable; the 9-target governs scheduling only. This is Store's first in-repo remediation ledger; 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.

Status legend: ☐ open · ◐ in progress · ☑ done


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

The former single biggest maturity lever: #28 cleared 2026-07-03; #22 cleared on the 2026-07-17 re-score (the gate flip verified live) and reopened on the 2026-07-28 re-score when the 2026-07-18 chromium-only cut was scored rather than frozen. #21 remains the highest open lever, pending a recorded screen-reader pass.

Maturity band (maturity < 4, ranked by priority)

Computed from the 2026-07-28 re-score, re-confirmed unchanged on the 2026-08-14 re-score (pin v1.152.0), again on the 2026-08-23 re-score (pin v1.160.0, HEAD 063c90dd) again on the 2026-09-01 re-score (pin v1.179.0, HEAD 44d9304c) and again on the 2026-09-04 re-score (pin v1.185.0, all 17 packages lockstep at Directory.Packages.props:8-105, HEAD 04afa39d): 3 categories, 7 gap points.

priority # Category w Mat Recorded lever
3 #21 Accessibility (a11y) 3 3 OPEN, recorded below: a dated manual NVDA/VoiceOver pass in store-ACCESSIBILITY-SCREENREADER-PASS.md (needs a human; the results log still holds only the placeholder row, re-verified 2026-08-14 at :68, header :66-67)
2 #12 Performance & Scalability 2 3 OPEN, recorded below: add a latency-regression check to the merge path, or record the monthly k6 cadence as the accepted posture (ADC's stance). Unchanged on the 2026-08-14, 2026-08-23, 2026-09-01 and 2026-09-04 re-scores; the load-freshness break-glass skip re-anchored 2026-09-04 to deploy.yml:729-745 (skip branch at :732, job at :714), and deploy.needs at :999 still contains no perf job
2 #22 Responsive & Cross-Browser 2 3 OPEN, reopened 2026-07-28, posture WIDENED since: add a cross-browser-freshness job to deploy.needs on the dr-freshness pattern, or promote firefox/webkit back into the gate. Since 2026-07-29 the nightly matrix runs ONE alternating engine per week (Mon firefox, Thu webkit; re-anchored 2026-09-01: crons e2e.yml:46-47, browser matrix expression :133-136), so the per-engine blind window is now 7 days. The chromium-only trade-off itself is recorded under Deliberate / accepted

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

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

🟠 Priority: execution-quality gaps (impl, not maturity)

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

Ranked 2026-07-28 when the ledger gained its second ranked axis. Until then the items in this section were closed history plus two open levers, with no ranking and no inclusion rule, which is why maturity reached 98.4% while implementation sat at 83.6%. Computed from the current scorecard (2026-07-28 full re-score, re-confirmed unchanged on the 2026-08-14 re-score, again on the 2026-08-23 re-score, again on the 2026-09-01 re-score, and again on the 2026-09-04 re-score at pin v1.185.0 / HEAD 04afa39d): 20 categories, 48 gap points (2026-09-04 rubric v2 rebase: the #16 row retired (−2) and #10 re-weighted to 3 (+1)), the largest of the three repos. (The former "attainable ceiling" comparison line is retired per the 2026-08-01 recalibration; the index reads against 100%.) Levers are cited only where this ledger or the scorecard already records one; an unnamed lever is named at the next re-score, never invented here.

implPriority # Category w Impl Recorded lever
4 #20 Design System & UI Consistency 2 7 OPEN, recorded below, re-verified 2026-08-23: ProductList's three cited attributes are converted (commit a1de5a89, semantic classes in store.css:28-40), but 30 Style=/CellStyle= occurrences remain across 14 razor files (count corrected from 31 on 2026-08-23, a counting fix: zero razor files changed), five byte-identical to the new classes (CategoryList.razor:23,79,88; OrderList.razor:22; CustomerList.razor:81); the sweep onto the now-existing classes is the lever, and the classes are mirrored in the MAUI head's app.css:38,42,47, a second sweep target
3 #7 Microservices Readiness 3 8 not yet identified (all per-service DBs on one physical server is a recorded accepted cap)
3 #10 Messaging & Integration Architecture 3 8 **Rubric v2 (2026-09-04, ADR-110): category replaced in place, carried at the prior I8 and re-weighted to the v2 default 3 (implPriority 2→3), re-scored against the new criteria on 2026-09-04 and CONFIRMED M4/I8 (broker topology per ADR-066, poison handling per ADR-087, saga compensation, the frozen event contract, the both-tier broker freshness gate at deploy.yml:770/:999); the lever for 9 is not yet identified, name it at the next re-score
3 #11 Security 3 8 OPEN, recorded below (lever named 2026-08-18 by the ADR audit): Store's identity seeder has no environment gate, so weak plaintext seed accounts are created in every environment, production included
3 #18 UI Architecture & Components 3 8 residual inline-style logic (the basis on which the 2026-07-16 impl bump to 9 was rejected)
3 #19 State Management & Data Flow 3 8 lever candidate named 2026-08-14: route the drawer open/close through the service notify path. IsDrawerOpen is publicly settable on the contract (re-anchored 2026-09-01: ICartStateService.cs:41) and mutated outside NotifyStateChanged by the component (CartDrawer.razor:3 two-way bind, CartDrawer.razor.cs:153 direct set; re-anchored 2026-09-04 after the ShoppingCarts/ folder move), the red flag on which the 8→9 bump has now been rejected three times (2026-07-28, 2026-08-14 and 2026-09-01; the 2026-09-01 rejection also named the partially applied M88 superseded-load guard and the withdrawn IUiReadCache client cache)
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
2 #5 Vertical Slice Architecture 2 8 the layered-by-project hybrid, recorded as a deliberate design choice capping impl at 8
2 #6 CQRS & Event-Driven 2 8 not yet identified
2 #12 Performance & Scalability 2 8 not yet identified
2 #15 Best Practices & Code Quality 2 8 lever named 2026-08-14 (the verify pass proposed I7 on these; user-adjudicated hold at 8), narrowed 2026-09-01: (1) CLOSED: the expired GHSA-2m69-gcr7-jv3q audit suppression is gone from Directory.Build.props, whose NuGetAuditSuppress block is now three System.Private.Uri advisories at :8-12 with its rationale at :7; (2) still open, re-scoped: the three undocumented global NoWarn codes CS1591/RMG020/EXTEXP0001 remain at :26 (the fourth code S8970 IS documented at :22-25), but the "uncommented duplicates" half is now half true, since :35-43 carries a rationale for the test-wide suppressions while five test csprojs still declare their own NoWarn; (3) half CLOSED 2026-09-03 (Store PR #123, drift D2): the MAUI head is still in neither .slnf (re-verified against MMCA.Store.CI.slnf, which lists only UI.Web:24 and UI.Web.Client:25), so the analyzers/TWAE half stays open, its build enforcement still happening only in a full local solution build. The vulnerability-audit half is closed by maui-audit.yml: a weekly (Sundays 06:00 UTC, :35) suppress-aware dotnet list package --vulnerable --include-transitive over Source/Hosts/UI/MMCA.Store.UI/MMCA.Store.UI.csproj on net10.0-android, exiting 1 on any advisory not in the NuGetAuditSuppress set (:103-151). Advisory, not a deploy gate, and the Apple TFMs stay uncovered because a Linux runner cannot restore them (:19-25)
2 #16 Maintainability & Evolvability 2 8 Retired 2026-09-04 (rubric v2, ADR-110): §16 is now AI-Native Application Architecture and N/A for this repo. The former category's coupling and tech-debt criteria score under #34, lockstep upgrades under #32, onboarding under #33. Struck through for the record; not counted in the band total.
2 #22 Responsive & Cross-Browser 2 8 not yet identified (the chromium-only gate is the maturity question, tracked above; the 2026-07-28 proposal to drop impl to 7 was adversarially rejected)
2 #23 Front-End Performance 2 8 not yet identified
2 #24 Forms, Validation & UX Safety 2 8 OPEN, narrowed 2026-09-01 (drift plan D4), recorded below: the authoring-mechanism half is CLOSED (all 4 create forms now source field rules from a DataAnnotations form model through Common's ModelValidation bridge, and FormsConventionTests was tightened to ADC's marker shape), so the remaining lever is only the rule-engine half: client validation is DataAnnotations-level, not full FluentValidation parity with the server rules. Score unchanged pending the next re-score
2 #25 Navigation & Information Arch 2 8 lever named 2026-09-04 (the verify pass rejected a proposed M4→3 and confirmed I8): store-NavigationFlow.md:166 claims each actor's menu shows only the routes in its diagram, but the framework shell renders /profile/sessions for every signed-in user (MMCA.Common.UI/Layout/NavMenu.razor:142) and /notifications/send is routable behind a bare [Authorize] (NotificationSend.razor:6) with no notification services wired in the Store UI host; reconcile the doc and the shell (hide or document both routes). Also re-anchored: the three *.UI.Tests projects sit at MMCA.Store.CI.slnf:40,46,52, not :39,45,51
2 #30 Compliance, Privacy & Governance 2 8 not yet identified
2 #31 Cost Efficiency / FinOps 2 8 not yet identified
2 #33 Developer Experience & Inner Loop 2 8 the §33 I8→9 candidacy recorded 2026-07-16 on the Service Bus emulator tier was NOT granted on 2026-07-28 on the grounds that the tier was nightly and non-gating; that grounds is retired 2026-09-01 (drift plan D1): the servicebus-emulator-smoke job is authoritative (no continue-on-error) and the cross-service-freshness deploy gate now requires BOTH it and cross-service to have concluded success in the same nightly run, mirroring ADC's 2026-08-31 TD-17 promotion. The residuals are that the tier still needs Docker (so it stays out of both .slnf filters and reaches the deploy chain through the recency check rather than per PR). Candidacy re-recorded; score unchanged pending the next re-score. See Deliberate / accepted
1 #27 Internationalization (i18n) 1 8 OPEN, half-closed 2026-08-14: the $-glyph half is FIXED in Common (per-currency symbol resolution, MoneyExtensions.cs:18-20,54-59, re-anchored 2026-08-23; Common change 2026-08-05, inside the v1.160.0 pin). Remaining: amounts still format with CultureInfo.InvariantCulture (:69-70) and pluralization stays the "{0} item(s)" / "{0} articulo(s)" workaround (CartDrawer.resx:20, ShoppingCartList.es.resx:11). The fix lands in MMCA.Common, so it is [C→A], not Store-local (2026-08-23 caveat: re-verified against Common source at HEAD, not the published v1.160.0 package body)
  • #11 Security, OPEN (found 2026-08-18 by the ADR audit): the identity seed accounts have no environment gate. The framework leaves the choice to the app: IdentityModuleDbSeederBase.ShouldSeed defaults to true (IdentityModuleDbSeederBase.cs:57), directly under a security notice saying the seed credentials are deliberately weak plaintext values and that deployed environments must disable seeding (:31-35). ADC overrides it behind Seeding:IncludeSampleUsers, default false, so a production host that sets nothing seeds no accounts (MMCA.ADC.Identity.API/IdentityModuleSeeder.cs:22-30). Store does not override it: its SeedAsync constructs and runs the DB seeder unconditionally (MMCA.Store.Identity.API/IdentityModuleSeeder.cs:19-24, re-anchored 2026-09-04), and ADR-030 records that the startup owner runs every module seeder in all environments. The seeded set is an admin account plus a customer account with weak plaintext passwords, one of them a real personal address (MMCA.Store.Identity.Infrastructure/.../IdentityModuleDbSeeder.cs:17,30-32); the only thing between them and production is the per-account EmailExistsAsync probe (:38-43), which skips an account that is already there but does nothing to stop the first seed. Lever: mirror ADC (override ShouldSeed on a Seeding:IncludeSampleUsers flag defaulting to false), then check the deployed Store Identity database for already-seeded accounts and rotate or remove them. Not a re-score on its own: it is recorded here so the #11 lever stops reading "not yet identified".
  • #14 Testability, impl 6 → 9. DONE (2026-07-01 wave, re-scored 2026-07-02). The floor step now measures Store's own code (reportgenerator +MMCA.Store.*;-*.Tests) at floor 42.0 with 46% actual (deploy.yml:82-86), the ADC-parity self-filtered gate; bUnit page-level breadth reached ADC parity (8 files). Unit-coverage program (2026-07-05): floor ratcheted 42.0 to 51.6 (54% measured) after Stripe money-path, Sales UI, and GDPR-handler unit tests. Integration-coverage expansion (2026-07-06): ~100 new integration tests over real SQL closed the money-path gaps (Stripe webhook signature contract, order state machine, deliberate 404-not-403 order ownership), GDPR erasure/export end-state, refresh-token rotation, preferences, cross-service ProductVariantChanged consistency, checkout concurrency, contract guards (OpenAPI + RFC 9457) for Sales/Identity, and the ProductImages/PUT leftovers; [Idempotent] was wired onto the Sales money POSTs with a replay contract test. The Catalog+Sales fixtures were consolidated onto SqlServerIntegrationTestFixtureBase. Cross-service broker tier (2026-07-11, drift plan D5): a non-gating nightly MMCA.Store.CrossService.IntegrationTests (Testcontainers RabbitMQ + SQL, cross-service-tests.yml) now exercises the genuine outbox-to-broker-to-consumer round-trip (Catalog ProductVariantChanged to Sales' zero-stock InventoryItem); the first run needs a manual workflow_dispatch (done 2026-07-11, green). Gated by recency same day (remediation wave 6): a cross-service-freshness job in deploy.needs fails a deploy when the latest successful nightly is older than 3 days (mirrors ADC TD-02; the Testcontainers workflow itself stays out of the deploy chain). Deliberately skipped: dedicated rate-limit fixtures (the WAFs neutralize the limiter; a tight-limit variant is low value for the volume, revisit only if abuse is observed).
  • #23 Front-End Performance, impl 6 → 8. DONE (2026-07-03, drift plan D4 + D10). Public CatalogBrowse moved to server-side paging (GetPagedAsync + bounded MobileInfiniteScrollList); cart enrichment resolves names via the targeted products/variant-lookup batch endpoint (CartStateService.cs:282-296); Core Web Vitals (LCP/CLS/TTFB/FCP) are measured per E2E run and uploaded as CI artifacts (Workflows/WebVitalsTests.cs). Accepted trade-offs (2026-07-02 approval): price sorts fall back to Newest; search is Name-contains.

🟡 Priority: minor / accept-or-polish

  • #32 Dependency & Supply-Chain, impl 7 → 8. DONE (2026-07-03, drift plan D8 + D9). Vulnerability gate is NuGetAudit + TreatWarningsAsErrors at restore, which fails build-and-test (a needs: of deploy) and every Docker image build; accepted advisories are the documented NuGetAuditSuppress set (Directory.Build.props:16,8-12,38-40). The SBOM became a hard gate in 43d733f with supply-chain added to deploy's needs. All three CI restores now run --locked-mode (committed-lock drift fails the build) and the vuln report is promoted to the suppress-aware gating audit mirroring ADC.
  • [~] #20 Design System, impl 7 → 8 lever OPEN (marker corrected 2026-07-17: the wave-6 done-claim over-reached; the 2026-07-16, 2026-07-17, 2026-07-28, and 2026-08-14 re-scores all hold §20 at M4/I7; anchors refreshed 2026-08-14: the previously cited ProductList.razor:23,95,104 residuals are CONVERTED by commit a1de5a89 to .list-search-field/.grid-cell-count/.grid-cell-actions in store.css:28-40, but 31 Style=/CellStyle= occurrences remain across 14 razor files, five byte-identical to those classes: CategoryList.razor:23,79,88, OrderList.razor:22, CustomerList.razor:81, plus CatalogBrowse/CatalogProductDetail/OrderLinesPanel/CustomerDetail; the 2026-08-14 proposed impl 7→8 was adversarially REJECTED on exactly this residual set). Maturity 4 stands (2026-07-01). The brand-color token guard BrandColorTokenTests shipped in 5fbd003 (CI-enforced, both UI hosts' home CSS repointed to var(--mmca-primary)). The remaining impl lever landed: every plain-HTML inline style= in Catalog.UI and Sales.UI moved verbatim into 17 new scoped .razor.css files as semantic classes (cart drawer, product images/variants, catalog cards, order/inventory pages; classes only added, DOM structure unchanged). Recorded residuals: MudBlazor Style=/CellStyle= parameters on components with no plain-HTML ancestor in their own markup, which Blazor CSS isolation cannot target without a DOM-changing wrapper (list in the wave-6 commit); no hard-coded brand hex anywhere. CI.slnf 1397 green incl. all fitness gates.
  • #9 API & Contract, impl 8 → 9. DONE (2026-07-03, drift plan D16). The v2 demonstration shipped: ServiceInfoController carries [ApiVersion("1.0", Deprecated = true)] + [ApiVersion("2.0")] mirroring ADC, and the two deploy-gating contract-guard integration tests (Contract/ApiVersioningTests + Contract/OpenApiContractTests, mirroring MMCA.ADC.Conference.IntegrationTests/Contract/) run in the integration-tests job. (Scorecard §9 refreshed to M4/I9 on 2026-07-11, drift plan D12.)
  • [~] #24 Forms, maturity 3 → 4 DONE (2026-07-11, drift plan D11); impl 8 → 9 lever OPEN (marker corrected 2026-07-17: the wave-6 done-claim over-reached; the 2026-07-16 and 2026-07-17 re-scores both hold §24 at M4/I8 because client validation stays MudForm-level, not full FluentValidation parity with the server rules). Maturity 4 is earned by the CI-gated FormsConventionTests (MinimumCreateForms=4) machine-enforcing the four create forms' guard/dirty/validated-MudForm/Required markers, matching ADC. The impl lever landed: client-side parity added where server rules were genuinely unmirrored: email-format validation (mirroring CustomerEmailRules' exact algorithm) on CustomerCreate + CustomerDetail, and per-field NewPassword min-length/128-cap plus confirm-match on Profile change-password (localized en+es keys). Audited-and-already-covered rules (Product/Category/Inventory lengths, ranges, SKU cap) and deliberately server-only rules (SKU uniqueness needs the DB) recorded in the wave-6 commit. No FormsConventionTests markers weakened. Update 2026-09-01 (drift plan D4): the authoring-mechanism half of the lever is CLOSED. All four create forms (CategoryCreate, ProductCreate, CustomerCreate, InventoryItemCreate) now source their field rules from a DataAnnotations form model through Common's ModelValidation bridge (Model="_model" plus Validation="@_validate" plus ModelValidation.IsRequired) instead of spelling requiredness per field in markup, which is the shape ADC's 8 Conference create forms already used; FormsConventionTests was tightened to ADC's marker set and gained Store's copy of the requiredness fact that reads off the model, with MinimumCreateForms still 4 so the gate stays non-vacuous. The Profile-form password fields stay inline by design on both sides. What remains of the impl lever is the rule-engine half only: the client rules are DataAnnotations, not full FluentValidation parity with the server rule set. Score arithmetic left to the next re-score.
  • #7 Microservices / single shared DB NOT A GAP (corrected): Store runs database-per-service (Store_Catalog/Store_Sales/Store_Identity); the legacy MMCAStore DB is a retained read-only archive only.

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

Four reviewed product defects fixed in one wave; every behavior change flipped its pinning test in the same change.

  • S-1 Stripe network errors escaped the Result pattern. StripePaymentService now handles HttpRequestException in both resilience predicates (retry + circuit breaker) and maps it to Payment.Stripe.SessionCreationFailed / Payment.Stripe.SessionRetrievalFailed instead of letting it propagate past the catch blocks.
  • S-2 Zero-decimal currency guard. Checkout-session creation validates the currency against a deliberate two-decimal allow-list (USD, EUR; must be extended together with Currency.All) before the * 100 smallest-unit conversion, returning the new Payment.Stripe.UnsupportedCurrency error, and rounds AwayFromZero instead of truncating fractional cents.
  • S-3 BuyNow drawer half-failure parity. CheckoutAndPayAsync now returns CheckoutOutcome(StripeUri, OrderId); on a payment-session failure it also persists the orphan-recovery token (previously only the success path did) and CartDrawer navigates to /orders/{id} with the order-id warning, matching Catalog's BuyNow fallback.
  • S-4 Case-sensitive Admin check in the GDPR handlers. DeleteUserHandler/ExportUserDataHandler use the new case-insensitive UserRole.IsAdmin instead of != UserRole.Admin, so a lowercase admin role claim authorizes the cross-user path.
  • Reviewed, not fixed (deliberate): CartStateService.InitializeAsync loading the cart without raising OnChange stays pinned intended behavior (initialization is silent; only user-visible mutations notify). OrderDetail's missing [Authorize] remains a recorded deliberate choice (E2E gap program review), out of scope for this wave.

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

  • ADR-042 device capability abstraction (latent, drift plan D8). The core extension point is converged (Store wires browser + MAUI capabilities via UseMauiDeviceCapabilities/AddBrowserDeviceCapabilities and renders the shared OfflineBanner), but Store consumes no further capability in its own product UI: no ExternalLink, no DeviceUIModule/DeepLinkListener, no app actions. Because no Store product page carries an external anchor today, the WebView dead-end risk is LATENT, not a live defect, so there is nothing to convert now. Adopt ExternalLink on any future Store product page that grows an external anchor, and register a Store IUIModule with DeepLinkListener (the way ADC's DeviceUIModule does) if a Store MAUI feature surface is ever wanted. The framework side is complete in Common (18 capability contracts + the MMCA.Common.UI.Maui package), so this is consumer-side only, not [C→A].
  • #5 Vertical Slice (M3) RESOLVED (2026-07-17 re-score): §5 is M4/I8, granted on the CI-gated SliceCohesionTests (sealed subclass of the shared non-vacuous base, in the deploy-gating MMCA.Store.CI.slnf:53), the identical gate ADC credits at M4/I8. The layered-by-project hybrid remains a deliberate design choice, now correctly recorded as an implementation-axis cap (holds impl at 8), not a maturity deduction. Moved to the protect list.
  • #27 Internationalization N/A RETIRED (2026-07-02): ADR-027 superseded ADR-011; Store ships full en-US + es localization with the CI-gated TranslationCompletenessTests. §27 is scored and included in the indices. Updated 2026-07-03 (i18n completion sweep): §27 is M4/I8 with zero residual hard-coded literals (35 snackbars incl. cart/checkout/Stripe, 33 breadcrumb labels, nav items, both StoreHome hosts), a second CI gate (LocalizedTextConventionTests), the completeness floor raised 20→25, and MudBlazor chrome localized via the framework's ResxMudLocalizer. Impl 8→9 lever DONE (2026-07-11, remediation wave 6): Tests/E2E/MMCA.Store.E2E.Tests/Workflows/PseudoLocalizationTests.cs extends the pseudo-loc text-expansion evidence to Store's own public pages (/, /catalog, /login): activates qps-Ploc via the production /culture/set cookie mechanism (the circuit handshake carries cookies, not query strings), asserts the [!! sentinel, Common's exact no-horizontal-overflow expression, and a per-page resx-owned en-US leak probe, plus a default-culture sentinel guard. No host/AppHost change needed; rides the deploy-gating chromium e2e-gate (first genuine run in CI). §27 Implementation 8→9 candidacy recorded for the next re-score. Candidacy GRANTED on the 2026-07-17 re-score (user-adjudicated: the lever's test is real and rides the deploy-gating chromium e2e-gate): §27 was M4/I9. REVERSED on the 2026-07-28 re-score: §27 is M4/I8 and is back in the implementation band. Not a regression, and not a withdrawal of the lever: PseudoLocalizationTests.cs:64,100 is intact and un-skipped and both arch gates still run in MMCA.Store.CI.slnf:53. The I9 was an over-grant because it scored the lever rather than the category: two of the rubric's five criteria are unmet in current code, namely culture-aware number formatting (Money.ToDisplayString() hard-codes a $ glyph and formats with CultureInfo.InvariantCulture, MMCA.Common .../MoneyExtensions.cs:20,41, an explicit rubric red flag) and mechanism-driven pluralization (the "{0} item(s)" / "{0} articulo(s)" workaround, CartDrawer.resx:20, ShoppingCartList.es.resx:11). The first pass proposed 7; 8 was adjudicated. Both defects live in shared MMCA.Common code, so the fix is [C→A] and the same deduction may apply to Common's and ADC's §27 at their next re-scores. Update 2026-08-14: the $-glyph half is FIXED (Common resolves the symbol from the price's own currency since 2026-08-05, MoneyExtensions.cs:18-20,54-59, re-anchored 2026-08-23, inside the v1.160.0 pin Store consumes); the CultureInfo.InvariantCulture amount formatting (:69-70) and the pluralization workaround remain, so §27 holds I8 and stays in the implementation band (re-verified 2026-08-23: CartDrawer.resx:20, ShoppingCartList.es.resx:11).
  • Single-region deployment: accepted in infra/DISASTER-RECOVERY.md (real load doesn't justify multi-region cost).
  • All per-service DBs on one physical SQL server: logical isolation complete; shared server for cost (minor §7/§8).
  • 2026-07-16 re-verification note: #9 (M4/I9) and #32 came back FLAG on the full re-score (first-pass scorers proposed regressions that the adversarial verify pass disproved against committed evidence). #9 stands at M4/I9. #32 was re-adjudicated the same day by the drift-analysis fold: a capability-level ADC comparison (adversarially verified) found no mechanism behind ADC's I9 that Store lacks, so §32 is now M4/I9; the earlier FLAG had reasoned from stale scorecard text (including a stale 49 lock-file count, actual 55).
  • ADR-043 adoption (mobile deep links / app association / native OAuth callback): recorded DEFERRED (2026-07-16 drift fold). The drift analysis lands this in Store, but adoption is feature-scale (Store-scheme deep links, iOS/Android manifest entries, associated domains) and rides the same trigger as the recorded ADR-042 latency: adopt when a Store MAUI surface is actively wanted. Not scheduled; revisit with the ADR-042 entry above.
  • #33 broker-parity tier SHIPPED (2026-07-16, mirrors ADC): Tests/Integration/MMCA.Store.ServiceBusEmulator.IntegrationTests runs MassTransit v8 against the official Service Bus emulator (pinned 2.0.1) with the real ProductVariantChanged contract, proving admin-plane topology creation + the AMQP round-trip nightly in cross-service-tests.yml (new job, same cross-service-freshness deploy gate). Closes the local-RabbitMQ vs prod-Service-Bus red flag with automation instead of documentation; §33 I8→9 candidacy recorded for the next re-score. Candidacy NOT granted on the 2026-07-28 re-score: the tier still exists but is nightly and non-gating, and its project (Tests/Integration/MMCA.Store.ServiceBusEmulator.IntegrationTests) is in neither solution filter, reaching the deploy chain only through the recency check (re-anchored 2026-09-04: the cross-service-freshness job at deploy.yml:770, in deploy needs at :999). §33 holds M4/I8 and stays in the implementation band. Tier PROMOTED TO GATING 2026-09-01 (drift plan D1), so the "nightly and non-gating" half of that refusal no longer applies: continue-on-error is off the servicebus-emulator-smoke job in cross-service-tests.yml:151, with an explicit do-not-re-add note at :143; its fixture now starts the bus once on the collection fixture with wall-clock-bounded startup phases (the shape ADC uses), and cross-service-freshness in deploy.yml requires BOTH cross-service and servicebus-emulator-smoke to have concluded success in the same nightly run (the both-jobs jq filter at deploy.yml:824, success message :827, failure message :835; re-anchored 2026-09-04), so a Service-Bus-only regression blocks the next deploy. Mirrors ADC's 2026-08-31 TD-17 promotion, and ADR-066 is refreshed to record the gated end state in both consumers. Honest residual: the tier needs Docker, so it stays out of both solution filters and gates through recency rather than per PR. §33 I8→9 candidacy re-recorded on this basis; this entry records the mechanism, not a score move, which the next /update-scorecard run owns.
  • ADR-044 adoption (native push, third notification channel): recorded DEFERRED (2026-07-16 drift fold). Store has no user-notification pipeline at all (no ADR-024 inbox, no SignalR channel), so ADR-044 adoption means adopting the whole notification stack first: a product decision, not remediation. Record here so the gap is conscious; schedule only if Store wants user notifications.
  • Chromium-only deploy E2E gate: accepted CI-cost trade-off (recorded 2026-07-28). The 2026-07-18 Actions-minute reduction cut the deploy-gating e2e-gate to a single engine (re-anchored 2026-09-04: job at deploy.yml:634, rationale :636-644, browsers: '["chromium"]' at :648) and left firefox/webkit on the Mon/Thu advisory matrix (re-anchored 2026-09-01: the browser matrix expression at e2e.yml:133-136, whose alternating branches are :134 firefox and :135 webkit, with the schedule-scoped continue-on-error at :143; the entry's former :133-135 engine-selection anchor is superseded by that range, not dropped). The saving is real and the decision stands; what was missing was the record, so the ledger predicted a reopen at line 26 and in the protect list without anyone having decided anything. Recording the trade-off does not restore the score: the rubric's maturity 4 is "enforced automatically", and a convention-enforced check is a 3, so §22 is scored M3 and sits in the maturity band with a named lever. The two are complementary: the score reflects what CI enforces, this entry reflects why. Revisit if a webkit-only or firefox-only defect ever reaches production, which is the risk being priced. Cadence update (verified 2026-08-14, re-confirmed 2026-08-23, 2026-09-01 and 2026-09-04): since 2026-07-29 the scheduled matrix runs ONE alternating engine per week (Mon firefox, Thu webkit; crons e2e.yml:46-47, engine selection :134-135), not both engines twice weekly, so the priced blind window per engine is now 7 days, wider than originally recorded. Content gap noted 2026-08-23: this entry prices the ENGINE dimension only. The gate is also UI-SCOPED (re-anchored 2026-09-04: deploy.yml:645) and a skipped e2e-gate does not block the deploy (deploy.yml:1038, gate block :1026-1039, with backend-test-gate now the complement at :1039), so on a backend-only merge the priced blind window is 100% for all three engines; that second, unrecorded hole is tracked as the TD under #21 in the maturity-band section, pending a decision to either enforce or accept it.
  • 2026-07-28 FLAG carry-forward (#19, #30). Both categories came back FLAG on the full re-score: first-pass proposals (#19 Implementation 8→9; #30 M4/I8→M3/I7) that the adversarial verify pass rejected against evidence re-read at HEAD 8d4af68c. Both hold their prior M4/I8 and their implementation-band rows are unchanged, still with no named lever. #19's rejection was specific: no §19 substance landed since the prior pin (the UI diff is culture-invariant string mechanics, payment-poll cadence tuning, and batched-lookup round-trip cuts, all §12/§23 work), and a minor red flag persists in a publicly settable IsDrawerOpen on the shared scoped state service, mutated directly by the component outside the notify path.
  • 2026-08-14 FLAG carry-forward (#5, #15, #17, #19, #20, #21). Six categories came back FLAG on the full re-score, every one an adversarial rejection of a proposed first-pass uplift against evidence re-read at HEAD 9571a963, none a found regression: #5 held I8 (horizontal folders inside module Application layers; generic-CRUD slices on shared framework handlers; only three bespoke query types), #17 held I9 (no pre-prod Bicep validation, SQL public network access, prod-only environment), #19 held I8 for the second consecutive cycle (IsDrawerOpen, now a named lever in its band row), #20 held I7 (the ProductList conversion covered ~3 of 34 occurrences), #21 held M3/I8 (placeholder SR log; one added dark-mode scan is a Strong-band increment). #15 is the one adjudicated case: the verify pass proposed a correction to I7 on three suppression-hygiene gaps (expired GHSA-2m69-gcr7-jv3q suppression, undocumented NoWarn codes, MAUI head outside CI); the user adjudicated a hold at the prior I8, and the three gaps are recorded as #15's named lever in the band table above.
  • 2026-08-23 FLAG carry-forward (#5, #6, #7, #9, #12, #20, #31). Seven categories returned FLAG on this re-score; none is a found regression and every one keeps its prior score under the merged-prior rule. #9 holds M4/I9 and stays on the protect list (a proposed downgrade was rejected: the contract-guard evidence base grew to seven files plus the frozen gRPC proto contract; only the scorecard row's narration was stale, now corrected). #12 holds M3/I8 and #20 holds M4/I7, both with their band rows and levers unchanged (re-verified this run: deploy.yml:613/:626-643 for #12, the 30 residual Style=/CellStyle= occurrences for #20). #6, #7, and #31 hold I8 with no lever-bearing evidence surfaced, so their band rows keep "not yet identified". #5 is the one special case: the scorer returned NO numbers at all (maturity null / implementation null), so its M4/I8 is carried forward unverified this cycle rather than re-established; treat #5 as owing a fresh read at the next re-score, not as re-confirmed. Discharged 2026-09-01: #5 returned M4/I8 CONFIRMED on a fresh read, so that debt is paid.
  • 2026-09-01 FLAG carry-forward (#7, #12, #19, #20, #21, #27). Six categories returned FLAG on this re-score; none is a found regression, every one is an adversarial rejection of a proposed first-pass uplift, and each keeps its prior score under the merged-prior rule (evidence re-read at HEAD 44d9304c, clean tree, pin v1.179.0). #7 holds M4/I8 on a third consecutive rejection of 8→9: the build half of independent deployability is now per-image incremental (deploy.yml:138-161 classifier, :848-873 matrix), but the deploy half is still one azure/arm-deploy of the whole main.bicep (:1115-1121) with all five images on one commit sha (:1025-1029) and a whole-fleet rollback (:1147, :1191), so its band row keeps "not yet identified". #12 holds M3/I8 on a fifth rejection of M3→4, and this is a VERIFIED hold, not an unread carry-forward: the verifier confirmed BOTH axes on byte-identical evidence (load-test.yml unchanged since 063c90dd, monthly cron :18 plus dispatch :9; load-freshness push-only at deploy.yml:663 with its break-glass skip at :673-690; deploy.needs at :945 still carrying no perf job) and re-confirmed Implementation 8 on the hot-path anchor OrdersController.cs:110. #19 holds M4/I8 on a third rejection of 8→9: IsDrawerOpen is still publicly settable (ICartStateService.cs:41) and set outside the notify path (CartDrawer.razor.cs:153), the new M88 superseded-load guard reaches only 4 of the 7 detail pages plus CatalogBrowse, and the IUiReadCache client cache was withdrawn 2026-08-31 (commit e60bb8be) pending a per-service invalidation story. #20 holds M4/I7 on a third rejection of 7→8: still exactly 30 Style=/CellStyle= occurrences across 14 razor files, zero .css files changed, and the shared semantic classes consumed by 1 of the 6 MudDataGrid list pages. #21 holds M3/I8 on the placeholder-only screen-reader results log (store-ACCESSIBILITY-SCREENREADER-PASS.md:68), with the 23-scan axe suite and its chromium e2e-gate (deploy.yml:584, :594, :945, :976) re-verified. #27 holds M4/I8: no .resx or localization file has changed since 9571a963 (2026-08-14), and both unmet criteria are live (MoneyExtensions.cs:69-70 formats with CultureInfo.InvariantCulture; the "(s)" pluralization workaround at CartDrawer.resx:20 and ShoppingCartList.es.resx:11).
  • 2026-09-04 FLAG carry-forward (#15, #20, #25, #31). Four categories returned FLAG on this re-score (the first against rubric v2, evidence re-read at HEAD 04afa39d, clean tree, pin v1.185.0); none is a found regression, every one is an adversarial rejection of a first-pass proposal, and each keeps its prior score under the merged-prior rule. #15 holds M4/I8 on a rejected 8→9: the MAUI head is still in neither solution filter (MMCA.Store.CI.slnf:24-25 lists only the two web hosts) and the three NoWarn codes at Directory.Build.props:26 still carry no rationale; the weekly maui-audit.yml closes only the vulnerability-audit half and is §32 substance by its own header. #20 holds M4/I7 on a fourth rejection of 7→8: zero .css files changed since 44d9304c (the razor diff is the §5 folder renames) and still exactly 30 Style=/CellStyle= occurrences across 14 razor files. #25 holds M4/I8 against a proposed DOWNGRADE to M3 (all three maturity legs verified live; doc drift scores on the implementation axis, the same ruling ADC received on 2026-09-01), and its band row gains a named lever for the first time (the store-NavigationFlow.md:166 menu claim versus the framework shell's /profile/sessions and /notifications/send routes). #31 holds M4/I8 on a rejected 8→9 and is a VERIFIED hold, not an unread carry-forward: the verifier's structured return lost its implementation number to a schema retry cap, but its text confirmed Maturity 4 (cost-guard in deploy.needs at deploy.yml:999, required success at :1032) and rejected the 9 on unchanged mechanism (cost-guard.yml:7 never mutates, :94 prints a manual reset; one uniform tag set at infra/main.bicep:113-119), while crediting the 2026-09-02 cost pass (0.25 vCPU right-sizing :1006, sampling :184, 300s metric export :221-222, the legacy database dropped :620-624, foundation.bicep:36-42 retention cap and :88-98 ACR purge) as refinement inside the 8. §31's scorecard anchors were also corrected: the former infra/main.bicep:80-86,271 pointed at unrelated parameters.

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

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

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

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

  • [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 app references. Every Store drill on record, scheduled and dispatched alike, hit it, so Store_Catalog / Store_Sales / Store_Identity had never had a restore proven while the scorecard credited §29 M4/I9 on "a drilled restore" and the rubric's #1 red flag is backups that have never been restored. Fixed: ISO-week rotation across the three live DBs (MMCAStore is absent from the rotation array and can only be dispatched), drill timeout 30 -> 60 min (a run was killed at 30m16s on 2026-07-20 and recorded no proof), and ADC's break-glass (skip_freshness_gates + mandatory justification) ported to all three freshness gates, since Store had no escape hatch at all. All three live DBs dispatch-drilled before landing: Catalog 3.6 min, Sales 2.1 min, Identity 1.3 min, all PASS.
  • [x] #15/#17/#34 Http2-only services had no readiness probe. Catalog and Identity run h2c-only for cross-service gRPC, which rejects HTTP/1.1 probes, so both fell back to tcpSocket with no readiness probe: a replica that could not reach its database stayed in rotation serving 500s. Ported ADC's dedicated Http1 probe listener on HealthProbe:Port 8081 plus the three-probe httpGet form. Verified on the deployed revisions.
  • [x] #31 SLO and budget alerts were notifying NOBODY. alertEmailAddress defaulted to '' with empty() guards on every consumer, and the ALERT_EMAIL repo variable was unset, so every alert routed to an action group with zero receivers. The parameter is now required and deploy.yml fails fast naming the missing variable.
  • [x] #33 README expanded from a two-line stub into a getting-started path.
  • [x] ADR-055 raw-queryable rule adopted with an EMPTY AllowedFiles: Store's Application layer had zero raw-queryable uses, so it starts with no exemptions to ratchet down. Plus DecoratorPipelineOrderTests and HandlerResultConventionTests.
  • [x] Password-spray protection (D3) closed with no Store code change. MMCA.Common v1.130.0 put the per-IP anti-spray throttle on AuthControllerBase.LoginAsync/RegisterAsync by default; Store inherits both actions and had never attached the attribute, so it had none. Watch the 429 rate: Blazor Server issues the login call server-side, so every Server-circuit user shares the UI host's IP against a 30/min window. Tune with AddCommonRateLimiting(authIpPermitLimit: N).
  • [x] E2/E4/E7/E8 adopted: PaymentReconciliationService on PeriodicBackgroundService (its first adopter), the shared SQL readiness check, and the observability and graceful-shutdown test bases (118-line and factory+test files collapsed to subclasses).

🟢 Resolved this cycle (2026-07-25, performance program 2)

Second evidence-led performance pass over Common/ADC/Store. Store's share shipped as two PRs plus the v1.127.0 framework sweep.

  • Output cache shared across replicas. Catalog registered AddOutputCache with no store while running maxReplicas: 2, so every EvictByTagAsync reached only the replica that handled the mutation: an admin repricing a product left roughly half of storefront reads serving the old value for the full 5-minute TTL, each replica filled its own copy, and a few multi-megabyte image entries per replica crowded out the product/category JSON. Redis was already provisioned and wired as IDistributedCache, so the fix was AddStackExchangeRedisOutputCache. ADR-040's original trade-off (per-replica in-memory accepted) is superseded; see its 2026-07-25 amendment.
  • Order hot predicates indexed. The payment-reconciliation sweep runs every 10 minutes over Status = PaymentInitiated AND COALESCE(LastModifiedOn, CreatedOn) < cutoff with nothing to seek, scanning and sorting a forever-growing Order table to find a normally-empty set; and both order grids sort CreatedOn DESC with OFFSET/FETCH. Added a filtered (Status, LastModifiedOn, CreatedOn) index and widened CustomerId to (CustomerId, CreatedOn).
  • Admin order search was returning an error, not results. OrderList sent ("contains", …) on an int Id; IntFilterStrategy has no CONTAINS, so Filter.Operator.NotSupported failed the whole query. Now an equality match when the input parses, ignored otherwise.
  • Warm-up tasks warmed nothing. Catalog primed a key the storefront never requests (pageSize=10 / includeChildren=False plus a Categories/paged call, versus the real pageSize=12 / includeChildren=True / Categories/lookup), and the policy varies by every query parameter. Sales and Identity GET [Authorize] endpoints unauthenticated, so they 401 by design and GetStringAsync threw: every startup logged a warm-up failure, and neither service registers an output-cache policy at all.
  • Customer enrichment N+1 over HTTP. GetByIdsAsync fanned out one request per id, so a 25-customer admin grid page issued 25 concurrent Gateway-to-Identity round trips on every page change. Now one request via the Id IN (...) operator.
  • Cart variant lookup projected. GetVariantCartInfoHandler loaded Product aggregates with an unfilterable ProductVariants include and discarded the surplus in memory, so a 30-variant product shipped 30 rows to render one, on the path that fires after every cart change.
  • Post-checkout Stripe polling backed off (banded 2s/3s/4s/6s, Stripe not called before attempt 5). A flat 3s x 20 made this page the dominant Stripe consumer under load, risking the circuit breaker for genuine session creations.
  • Load and CWV coverage. catalog-read-load.js measured only guaranteed cache hits on keys the storefront never sends; it now also exercises the real storefront key and two deliberate cache-miss shapes, with hit/miss thresholds split by tag so DB-path latency has its own budget. Product detail gained a CWV budget.

Deferred from that program (record the choice)

  • TD · Batch the bulk-inventory existence check. DONE (verified 2026-09-01). BulkSetInventoryHandler used to validate each variant with its own sequential cross-service gRPC call; it now makes ONE round trip via productVariantService.GetExistingIdsAsync(...) and derives the missing set by difference (Source/Modules/Sales/MMCA.Store.Sales.Application/Inventory/UseCases/BulkSet/BulkSetInventoryHandler.cs:36-41, rationale comment :30-34). Path correction: this ledger and the #12 entry cited Sales.Application/InventoryItems/Commands/BulkSetInventory/, which does not exist. The whole contract path the TD said was required shipped with it: Catalog.Contracts/Protos/product_variants.proto, Catalog.Contracts/ProductVariantServiceGrpcAdapter.cs, Catalog.Service/Grpc/ProductVariantsGrpcService.cs, Catalog.Shared/Products/IProductVariantService.cs, Catalog.Shared/Products/DisabledProductVariantService.cs, Catalog.Application/Products/ProductVariantService.cs. The reuse hazard the TD named is now recorded in the code itself: GetUnitPricesAsync could not be reused, because it drops variants whose Price is null, so an existing-but-unpriced variant would be reported missing and fail the request.
  • TD · Projected order-line count. DONE (verified 2026-09-04). The persisted denormalized column the TD called the only proper fix shipped: Order.LineCount (Source/Modules/Sales/MMCA.Store.Sales.Domain/Orders/Order.cs:53, private set, maintained by the domain at :129 and documented as the count at creation, since Create is the only path that adds lines), mapped in OrderConfiguration.cs:35, exposed on OrderDTO.cs:34, migrated by MMCA.Store.Migrations.SqlServer.Sales/Migrations/20260831011924_AddOrderLineCount.cs, and read by the admin grid as a real PropertyColumn (Sales.UI/Pages/Orders/OrderList.razor:109, rationale :106). No Orders list or paged query includes OrderLines any more; the remaining includes are the money and saga paths only. Original note, for the record: the admin order grid loaded every order line only to render OrderLines.Count, and a DTO field alone could not help because the generic query pipeline materializes entities before mapping.
  • TD · Product-image derivatives. Images are full-size DB blobs streamed as-is and rendered as card thumbnails with no srcset/dimensions, so a 12-card browse grid can pull 12 full-size assets. Fixing it is a storage-design decision, not a local change.
  • TD · Port ADC's expand/contract migration guard. DONE (verified 2026-07-28; anchors refreshed 2026-08-23, substance confirmed unchanged). The "Expand/contract migration guard (schema rollback safety)" step runs inside the required build-and-test job (re-anchored 2026-09-01 by a fresh grep of deploy.yml: the step at :279-325, the job at :173): it fails any PR whose newly added migration Up() body contains DropColumn/DropTable/DropIndex without an EXPAND-CONTRACT-OVERRIDE marker (:317-319, the marker documented at :288), scope and rationale at :281-292, and fails closed when the base diff is unresolvable (:301-305) rather than passing vacuously. The companion model-drift gate is the step above it at :263-277. The policy is documented alongside the code at CONTRIBUTING.md:55. This is part of what lifted §8 to Implementation 9.

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

  • #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 GracefulShutdownTests (in MMCA.Store.Gateway.Tests, MMCA.Store.CI.slnf) is CI-gated, so scorecard §29 legitimately holds M4 and lifts Implementation 8 → 9. The former honest residual (stale 2026-06-22 drill) is resolved: the weekly dr-drill.yml cron runs green, most recently 2026-07-13, so dr-freshness is satisfied.
  • #31 cost-guard deploy gate (drift plan D4). cost-guard.yml gained a workflow_call trigger and is invoked as a cost-guard job in deploy.needs, so an un-reverted surge blocks the deploy (previously weekly-cron-only). Prerequisite for a clean first gated deploy: a green latest scheduled cost-guard run.
  • #1 SOLID, impl 8 → 9 (drift plan D9). Scorecard §1 refreshed to cite the CI-gated ConstructorDependencyCountTests (ceiling 7, the 8+-dependency red flag cleared) + TimeProvider injection.
  • #17 DevOps, MI-SQL ACTIVATED in prod (2026-07-12; wiring landed inert 2026-07-11 as drift plan D2). The full infra/SQL-MANAGED-IDENTITY.md sequence is complete: Stage 1 Entra admin live on the server (SQL_AAD_ADMIN_* repo vars), Stage 2 grants verified in all three DBs (mmca-prod-apps-identity as EXTERNAL_USER + db_owner in Store_Catalog/Store_Sales/Store_Identity), Stage 3 USE_MANAGED_IDENTITY_SQL=true deployed green (run 29192048197, full gate chain + post-deploy smoke). All three services now run passwordless Authentication=Active Directory Managed Identity; the SQL password path remains only as the documented dual-auth rollback. Mirrors ADC's 2026-06-28 activation; §17 Implementation 8→9 BANKED on the 2026-07-16 re-score (repo variable USE_MANAGED_IDENTITY_SQL=true + green run 29192048197 re-verified directly; the scorecard's stale inert claim corrected). The SQL public-network-access acceptance (no VNet) stands as documented.

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

Both axes satisfied (maturity 4 AND implementation >= 9), the true protect list: SOLID (#1), Design Patterns (#2), Clean Architecture (#3), DDD (#4), Data (#8), API (#9), Observability (#13), Testability (#14), DevOps (#17), Front-End Security (#26), Resilience (#29), Dependency & Supply-Chain (#32), Architecture Governance (#34). #8 joined on the 2026-07-28 re-score (Implementation 8→9): the atomic conditional-UPDATE stock decrement with deterministic lock ordering (InventoryAllocationService.cs:71), its CK_InventoryItem_AvailableQuantity_NonNegative schema backstop (InventoryItemConfiguration.cs:27), the single-transaction checkout write phase (CheckOutHandler.cs:91), the fail-closed expand/contract migration guard in the required build-and-test check (deploy.yml:279, re-anchored 2026-09-04), and the raw-IQueryable ban with an empty allowlist (RawQueryableConventionTests.cs:14 in MMCA.Store.CI.slnf:53). Protecting it means keeping the decrement atomic and the CHECK constraint in place; the honest residual is that Identity has no concurrency round-trip test.

Maturity 4 but implementation <= 8, so still ranked in the implementation band above: Vertical Slice (#5), CQRS (#6), Microservices (#7), Messaging & Integration (#10, carried at prior under rubric v2), Security (#11), Code Quality (#15), UI Architecture (#18), State Management (#19), Design System (#20), Front-End Performance (#23), Forms (#24), Navigation (#25), i18n (#27), Front-End Testing (#28), Compliance/Privacy (#30), FinOps (#31), DevEx (#33). Closing on maturity alone is exactly what let the two indices drift apart, so these stay visible rather than disappearing into the protect list. (#16 became AI-Native Application Architecture, N/A, on the 2026-09-04 rubric v2 rebase.)

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

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

#34 caveat realized (2026-07-02): both governance ledgers are committed and kept current per release (§34 impl re-scored 7 → 9), and the arch fitness suite has grown to 22 test classes. Record future tactical deferrals here as TD-NN sub-items under their category rather than in a separate file (the single-ledger model ADC adopted 2026-06-26).