Architecture governance
MMCA.Common — Architecture Scorecard
Canonical, version-controlled scorecard for this repo — the single source of truth for MMCA.Common's architecture scores (replaces the former single-axis snapshot; see git history). Scored against the rubric at
ArchitectureEvaluationCriteria.md; framework-wide facts (version, packages, ADR range, fitness counts) live inFACTS.md. The cross-repo Common-vs-ADC-vs-Store narrative lives once in../Docs/Architecture/CrossRepoComparison.md.
Rubric: ArchitectureEvaluationCriteria.md • Date: 2026-07-23 (full 34-category, two-pass evidence re-score at framework v1.123.0, git HEAD c911480, clean working tree; fifteen published packages, 91 fitness methods across 30 bases per FACTS.md) • No score moves this cycle: all 34 categories re-confirmed at their twenty-first-wave values against current source. Five first-pass proposals were adversarially refuted and held at prior: three lifts (§9 Maturity 3→4: the contract-drift gate stays deliberately consumer-owned, no in-repo CI enforcement; §10 Implementation 8→9: the three documented hold-reasons are still in source; §30 Maturity 3→4: only the erasure-mechanism gate is automatic, the governing process stays consumer-resident) and two downgrades (§23 and §24, each Implementation 8→7: refuted on a fresh re-read of every cited file, no regression exists). Indices unchanged: Maturity 96.9% (314/324), Implementation 84.6% (685/810). The v1.122.0-v1.123.0 capability train (typed filter DSL operators, the EntityQueryPipeline page-size clamp, cache-observability warnings, the IIntegrationEventPublisher removal with callers moved to IEventBus) lands as evidence enrichment in categories already scored 8-9, with no band move. • Two axes per category: Maturity (0-4, process/governance) and Implementation (0-10, substance/execution). Indices computed deterministically from the scores below. Historical wave-by-wave detail lives in the executive summary below and in git history.
Update 2026-06-30 (tenth wave: focused in-repo remediation). Four scores moved up on shipped, tested evidence and both indices rose: Maturity 91.7% → 92.9% (301/324) and Implementation 84.1% → 84.9% (688/810). The moves: §27 i18n Maturity 2→3 / Implementation 6→7 (a ResourceTranslationsAreComplete translation-coverage fitness gate plus MA0076 raised to a build error close the two ADR-027 §7 follow-ups; 33 culture-less formatting sites fixed); §29 Resilience Maturity 3→4 (the in-repo DatabaseRestoreDrillTests is a build gate run on every CI build, now with a recorded measured RTO/RPO baseline in RESILIENCE.md; a build gate, not a non-gating scheduled cron, is the M4 bar); §28 Front-End Testing Implementation 8→9 (a deterministic render-snapshot regression tier, MarkupSnapshot + PrimitivesSnapshotTests, shipped in Testing.UI for reuse); §21 Accessibility Implementation 8→9 (axe coverage broadened to the loading/error states plus a documented manual screen-reader pass in ACCESSIBILITY.md, which also found and fixed a real WCAG 4.1.2 defect in PageLoadingState). One honest finding logged but not gated: a dark-mode palette contrast gap (filled-primary button label + error-alert text), tracked under §20. Fitness counts advanced to 72 methods / 19 bases (Common runs 39) per the regenerated FACTS.md. The dated note above records the prior ninth-wave (v1.92.0) re-score; the figures in the executive summary and closing analysis below predate this wave (the Indices section is authoritative).
Update 2026-06-30 (eleventh wave: ADR governance for two pre-existing mechanisms). No score moves this cycle. A full 34-category evidence re-score at framework v1.93.0 (git HEAD 3e72bfa, working tree dirty, see the caveat below) re-confirmed every category at its tenth-wave value, so the indices hold at Maturity 92.9% (301/324) / Implementation 84.9% (688/810) (the Indices section remains authoritative). The wave is governance and docs only: two new ADRs record mechanisms that already ship in framework code. ADR-033 (resource-ownership authorization) documents the OwnerOrAdminFilter plus OwnershipHelper axis (a single-resource 403 filter keyed on the customer_id claim, plus an ownership Specification that row-scopes collection queries, with one admin bypass; opt-in per controller, claim-trusting, not ABAC, adopted in MMCA.Store). ADR-034 (generic entity controllers + dynamic query contract) documents the EntityControllerBase / AggregateRootEntityControllerBase generic REST surface and OData-lite query contract (sparse fields, per-type IFilterStrategy filtering, sort, pagination with X-Pagination, the MaxUnboundedResultLimit ceiling, two-path includes). Both enrich evidence (§11, §9/§2/§12, §34) without moving a score: ADR-033 is the resource-ownership criterion §11's Implementation-8 cap named, but it stays ownership-by-claim, opt-in, not-ABAC, and Store-adopted, so §11 correctly holds at Implementation 8. The source-generated, CI-gated FACTS.md advanced to ADR set 001-034 and 72 fitness methods across 19 bases (Common runs 39); this scorecard's prose is synced to match. Honest caveat: the tree is dirty (ADRs 033/034 added; ADRs 015/026/030 plus ADRs/README.md and FACTS.md modified) until this docs pass is committed, the recurring per-cycle §34 hygiene nit, tracked in the backlog.
Update 2026-06-30 (twelfth wave: under-8 Implementation lift, v1.94.0 pending release). Two Implementation scores move up on shipped, tested in-repo evidence; both indices' Implementation axis rises while Maturity holds: Maturity 92.9% (301/324) unchanged, Implementation 84.9% → 85.3% (691/810). The moves: §22 Responsive & Cross-Browser Implementation 7→8 — the two execution gaps the prior 7 named are closed with real framework code: grid density options now ship on DataGridListPageBase (DenseGrid + ToggleDensity(), persisted through ListPageState/URL/sessionStorage, unit-tested) and the 48px touch target is generalized from a cart-drawer-only rule into a shared .mmca-touch-target affordance (cart drawer + mobile cards + pager, enforced by a phone-viewport Playwright bounding-box test that passes locally), plus a documented RESPONSIVE.md device/browser matrix; and §27 Internationalization Implementation 7→8 — a real pseudo-localization pass ships (PseudoLocalizer transform + an IStringLocalizerFactory decorator activated by a Development-only qps-Ploc culture, inert in production, unit-tested), closing the pseudo-localization gap the prior 7 named. §31 FinOps stays at Implementation 7 by deliberate, documented acceptance (a structural cap, not an open gap): its two unmet criteria — right-sizing and reversible scale-events — are consumer/IaC execution a NuGet library provisions nothing to perform, and per-service cost attribution via Aspire annotations is inert for the hand-written-Bicep consumers (ADC/Store); the in-repo levers (Telemetry:TracesSampleRatio, outbox-log trim, COST.md) are already shipped. Full Common Release build clean, 1685 tests pass; the touch-target + pseudo-loc tests are green. The version is v1.94.0 pending the release tag + consumer sweep (held for review at this writing, so the working tree is dirty against the v1.93.0 tag).
Update 2026-07-02 (thirteenth wave: performance-smoke CI gate + rubric-band recalibration). A full 34-category evidence re-score at framework v1.99.0 (git HEAD 4b83891, working tree dirty, see the caveat below) moves five scores: one maturity up, four implementation recalibrated down to their rubric bands. §12 Performance & Scalability Maturity 3 to 4: the BenchmarkDotNet hot-path harness is now a committed CI gate (a performance-smoke job runs the benchmarks --filter "*" --job Dry on every push/PR, .github/workflows/ci.yml:149), so the §12 hot path (Specification compiled-expression cache + criteria composition) is automatically enforced, meeting the rubric's maturity-4 "enforced automatically (CI), documented, evolved deliberately" bar. Implementation holds at 8 because that CI benchmark is a runs-clean smoke, not a latency-regression gate (ci.yml:145), and load/stress timing data still lives in the consumer apps. §14 Testability 10 to 9, §19 State Management 9 to 8, §26 Front-End Security 9 to 8, §34 Governance & Docs 9 to 8: four recalibrations to the Strong band on the same evidence the prior scores hedged as "just shy of 10". The 53% coverage floor is modest and there is no mutation testing (§14), server-vs-client-state staleness is only partially deliberate (§19), the default static CSP omits script-src/style-src (§26), and the governance docs' own uncommitted-ADR pass keeps traceability momentarily inconsistent with the CI-gated FACTS.md (§34). This is honest calibration to the rubric bands, not regressed work. Net indices: Maturity 92.9% to 93.5% (301 to 303 / 324, +2 from §12), Implementation 85.3% to 84.0% (691 to 680 / 810, minus 11 from the four Implementation recalibrations: §14/§19/§26 each minus 3 on weight 3, §34 minus 2 on weight 2). Implementation stays the weaker axis and the gap widens to ~9.5 points (Maturity up, Implementation recalibrated down); no category is N/A. The source-generated, CI-gated FACTS.md advanced to fourteen packages / ADR set 001-036 / 77 fitness methods across 24 bases (Common runs 39) and this scorecard's prose is synced to match. Honest caveat: the tree is dirty (a docs pass is uncommitted: ADRs 035/036 added and untracked; ADRs 015/023/027/029/030/032/033 + ADRs/README.md + FACTS.md modified), the recurring per-cycle §34 hygiene nit, tracked in the backlog. The Indices section below is authoritative; the executive summary and closing analysis predate this wave.
Update 2026-07-03 (fourteenth wave: clean-tree evidence re-score at v1.101.0). A full 34-category, two-pass evidence re-score (scorer plus adversarial verifier per category) at framework v1.101.0 (git HEAD 5e55be2, working tree clean: the recurring uncommitted-docs-pass caveat is closed this cycle) moves two scores. §24 Forms, Validation & UX Safety Maturity 3 to 4: the shared auth-form convention is enforced automatically in the CI-gated .slnx unit tier (AuthModelValidationTests, 8 facts covering required/email/complexity/password-match, plus RegisterFormTests asserting the per-field ValidationMessage renders on an empty submit with the auth service never invoked), meeting the rubric maturity-4 "enforced automatically by tests/CI" bar, consistent with how §19 holds M4 on the same CI-gated guard tests; the shared FormsConventionTestsBase fitness base remains consumer-scoped (it enumerates Source/Modules/**/*Create.razor, which Common does not contain, and Common has no subclass), so the in-repo enforcement rests on those bUnit/model tests. §14 Testability Implementation 9 to 8: an internal-consistency band recalibration, not regressed work; the row's own stated reasoning (a modest 53% gated coverage floor, no mutation testing) describes the rubric's Strong band (7-8) while 9 sits in Exemplary, so 8 is the number that matches the reasoning. Four further first-pass downgrade proposals were adversarially reviewed and not applied: §25 (Implementation 8 to 7) and §34 (Maturity 4 / Implementation 8 to 3/7) were refuted on evidence, and §7/§9/§13 (each Implementation 9 to 8, a band recalibration) were declined on review, so all five hold at their prior scores; their rows receive evidence corrections only (drifted Aspire/Extensions.cs and Directory.Packages.props line anchors, the extraction rule body relocated to the shared Testing.Architecture package, and one stale §13 hold-reason: the warm-up subsystem HAS been unit-tested since the eighth wave, so the §13 hold at 9 rests solely on deployer-owned SLO alerting/dashboards/runbooks). The stale §34 dirty-tree caveat is removed: ADRs 035/036 and the docs pass are committed, so governance traceability is consistent with the CI-gated FACTS.md again. Net indices: Maturity 93.5% to 94.1% (303 to 305 / 324, +2 from §24), Implementation 84.0% to 83.6% (680 to 677 / 810, minus 3 from §14 on weight 3). Implementation stays the weaker axis (gap ~10.6 points); no category is N/A. The Indices section below is authoritative; the executive summary and closing analysis predate this wave.
Update 2026-07-03 (fifteenth wave: i18n completion train, §27 Maturity 3 to 4 + Implementation 8 to 9). The ADR-027 amendment (Decision 9) closes every stated §27 hold. Maturity 3 to 4: the pseudo-localization pass is no longer a developer diagnostic: PseudoLocalizationE2ETests runs in the REQUIRED chromium ui-e2e job (the gallery host enables qps-Ploc unconditionally as unpackaged test infrastructure) and fails the merge on a missing [!! sentinel (resource round-trip) or horizontal overflow under the ~40% expansion (the rubric's layout-tolerance criterion, previously unevidenced), alongside an en-US leak guard; a second fitness gate (LocalizedTextConventionTestsBase, subclassed in-repo as LocalizedTextConventionTests) fails the build on NEW hard-coded snackbar/title/<PageTitle>/breadcrumb/NavItem literals, so the localized state is machine-enforced end to end, not review-enforced. Implementation 8 to 9: MudBlazor's built-in chrome now localizes (ResxMudLocalizer over MudTranslations.{resx,es.resx}, all 145 built-in keys en + es, DI-resolution-tested), the framework's own UI chrome is fully externalized (NavMenu, auth pages, error/empty/loading states, ReconnectModal, notification pages, the UI.Web SSR Error page: SharedResource grew 22 to 136 keys), the ErrorMessages.Success fragment-concatenation red flag is [Obsolete] (whole-sentence page keys replace it) and the Common.Error.* templates no longer surface raw ex.Message, and NavItem.TitleResource makes module nav menus culture-aware. Held below 10: two locales, no RTL. LocalizationResourceTests also gains a non-vacuous floor (MinimumBaseResources = 3). Net indices: Maturity 94.1% to 94.4% (305 to 306 / 324), Implementation 83.6% to 83.7% (677 to 678 / 810), both +1 on §27's weight 1.
Update 2026-07-06 (sixteenth wave: clean-tree re-score at v1.106.0, one score moves). A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.106.0 (git HEAD 6f8b917, one commit past the v1.106.0 tag, working tree clean) moves one score. §14 Testability & Test Strategy Implementation 8 to 9: the specific reason the fourteenth wave recalibrated §14 down to 8 (a "modest 53% gated coverage floor") is resolved. The unit-tier coverage floor was ratcheted 53.0 to 68.3 (.github/workflows/ci.yml:226, commit b75fa8f, measured ~70.3% after the C1-C4 coverage program), and the suite grew from 1586 to 1880 [Fact]/[Theory] across 262 files via a coverage-driven program that found and fixed seven real defects (C-1..C-7, 55f3cab), coverage tracked on meaningful paths rather than as a vanity number, so the top band is now supported. Maturity holds at 4 (CI-gated fitness tests plus the blocking coverage-floor gate plus the zero-discovery min-tests guard); it is held below 10 only by the one remaining Exemplary gap, no mutation testing on the Core tier. All 33 other categories re-confirmed at their prior scores against current source: the defect-fix wave C-1..C-7 (§11 lockout-backoff-overflow clamp, §13 LoggingQueryDecorator query-failure outcome=failed RED-metric parity, the OAuth returnUrl safe-lookup, ChildEntityServiceBase token attach) and ADRs 037 (field-level encryption at rest) plus 038 (supply-chain provenance) are all evidence enrichment in categories already scored 8-9. ADR-037's EncryptedStringConverter is explicitly latent/unadopted, and ADR-038 records the SBOM as "generated, not yet signed or attested", so none moves a score. §34 Governance & Docs holds at M4/I8: the adversarial pass proposed 8 to 7 because the scorecard's own prose was stale against the CI-gated FACTS.md (it self-dated v1.101.0 / ADRs 001-036 while FACTS.md reports v1.106.0 / 001-038), but this refresh cures that staleness (the rewritten scorecard is current at commit time), and the durable Strong-8 cap remains the uncommittable ArchitecturalAnalysis.md in the workspace root. The source-generated, CI-gated FACTS.md is now ADR set 001-038 / 78 fitness methods across 25 bases (Common runs 40) and this scorecard's prose is synced to match. Net indices: Maturity 94.4% (306/324) unchanged, Implementation 83.7% to 84.1% (678 to 681 / 810, +3 from §14 on weight 3). Implementation stays the weaker axis (gap ~10.3 points); no category is N/A. The Indices section below is authoritative; the executive summary and closing analysis predate this wave.
Update 2026-07-09 (seventeenth wave: evidence re-score at v1.108.0, one score recalibrated). A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.108.0 (git HEAD 6c3b3bc, working tree clean, one commit ahead of origin: the ADR-012 mixed-endpoint transport amendment awaiting push) moves one score. §21 Accessibility Implementation 9 to 8: a band recalibration on evidence already on the record, not regressed work. The shipped, user-toggleable dark theme (ADR-028) carries two documented, deliberately un-gated WCAG AA contrast failures (the filled-primary button label and the error-alert message text, ACCESSIBILITY.md known limitations), a directly unmet §21 color-and-contrast criterion; the prior 9 parked that gap under §20 while sitting in the rubric's Exemplary band ("all criteria met to a high standard"), which does not hold when a §21 criterion fails in a default-available feature. Alongside the two standing smaller gaps (no automated focus-trap/reading-order assertion, manual pass only; axe breadth scoped to the gallery's representative states), the category recalibrates to the top of Strong. Held at 8 rather than 7 (reviewed): the gaps read as one minor cluster whose contrast half is a single palette-tuning item already tracked as the open §20 backlog entry, and everything else §21 claims is intact (the required chromium axe merge gate over the WCAG 2.1 AA tag set including the loading/error states, semantic landmarks and named controls, the documented manual screen-reader pass, the shipped E2E a11y bases). Maturity holds at 4. Two adversarially-refuted first-pass proposals are recorded, not applied: §8 Implementation 8 to 9 refuted (the rubric's "migrations run in CI/CD" criterion is structurally unmet in-repo: ci.yml has no migration-apply step, and ADR-030's boot-time self-migration is a runtime mechanism, not that gate) and §22 Implementation 8 to 9 refuted (no repo change since the twelfth-wave lift; firefox/webkit remain advisory continue-on-error, so the chromium-only gate stays the standing §22 gap). All 33 other categories re-confirmed at their prior scores against current source. The ADR set grew to 001-039 (ADR-039 live channel push: ephemeral events over the notification hub via ILiveChannelPublisher, plus the ADR-012 mixed-endpoint transport-profile amendment), both evidence enrichment in categories already scored 8-9 with no score move. The source-generated, CI-gated FACTS.md reports 78 fitness methods across 25 bases (Common runs 41) and this scorecard's prose is synced to match. Net indices: Maturity 94.4% (306/324) unchanged, Implementation 84.1% to 83.7% (681 to 678 / 810, minus 3 from §21 on weight 3). Implementation stays the weaker axis (gap ~10.7 points); no category is N/A. The Indices section below is authoritative; the executive summary and closing analysis predate this wave.
Update 2026-07-12 (eighteenth wave: evidence re-score at v1.115.0, three scores move). A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.115.0 (git HEAD 37d0a3b, working tree clean, at the release tag) moves three scores, all front-end. §20 Design System Implementation 8→9 and §21 Accessibility Implementation 8→9: the two documented dark-theme WCAG AA contrast failures that capped both categories (filled-primary button label + error-alert message text) are fixed in the palette (MMCATheme.cs:60,73, dark PrimaryContrastText/ErrorContrastText = rgba(0,0,0,0.87)) and locked by a blocking dark-mode axe gate (DarkModeE2ETests in the required chromium ui-e2e job, ci.yml:114); this is exactly the "tune the dark palette and gate the dark-mode axe scan" path back to 9 the seventeenth wave named. §20 stays below 10 on the residual raw-hex/!important in app.css and the Bootstrap-chrome coexistence; §21 stays below 10 on the manual-only focus-trap/reading-order pass. §23 Front-End Performance Maturity 3→4: the front-end performance conventions the thirteenth wave recalibrated to review-enforced are now measured AND automatically enforced: WebVitalsE2ETests asserts LCP/TTFB/CLS budgets on the gallery Login + Components pages inside the same blocking chromium ui-e2e gate (WebVitalsE2ETests.cs:43, ci.yml:114,145), meeting the rubric's maturity-4 "enforced automatically (CI)" bar; Implementation holds at 8 on the retained desktop-grid render-efficiency gap. Five adversarially-refuted first-pass proposals are recorded, not applied: §7 Implementation 9→8 (an unforced band recalibration re-litigating a fourteenth-wave decline), §10 Implementation 8→9 (the three documented hold-reasons still in source), §25 Maturity/Implementation downgrade (every §25 mechanism present on a clean tree), §26 Implementation 8→9 (the CSP script-src/style-src gap is unclosed), and §34 Implementation 8→7 (a transient stale-prose basis this refresh cures) all hold at their prior scores. All 26 other categories re-confirmed at their prior scores. Six capabilities landed since v1.108.0 as evidence enrichment with no band move: ADR-040 authenticated output caching, ADR-041 observability/telemetry, ADR-042 MAUI device-capability abstraction (the fifteenth package MMCA.Common.UI.Maui), ADR-043 mobile deep links + native OAuth callback, ADR-044 native push delivery, ADR-045 managed file storage + avatars, all in categories already scored 8-9 (§18/§6/§8/§11/§30). The source-generated, CI-gated FACTS.md reports 15 packages / ADR set 001-045 / 85 fitness methods across 28 bases (Common runs 46) and this scorecard's rows are synced to match. Net indices: Maturity 94.4% → 95.1% (306 → 308 / 324, +2 from §23 on weight 2), Implementation 83.7% → 84.3% (678 → 683 / 810, +2 from §20 on weight 2, +3 from §21 on weight 3). Implementation stays the weaker axis (gap ~10.8 points); no category is N/A. The Indices section below is authoritative; the executive summary and closing analysis predate this wave.
Update 2026-07-15 (nineteenth wave: evidence re-score at v1.115.0, three table moves, one honest downgrade). A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.115.0 (git HEAD c7b3cb9, working tree dirty: an ADR-adding pass is in flight, ADRs 046-048 untracked plus ten modified files including FACTS.md) moves three table scores. §22 Responsive & Cross-Browser Maturity 3→4: firefox was promoted to a required merge gate alongside chromium on 2026-07-12 (ci.yml:112, with continue-on-error now scoped to webkit only, ci.yml:115), exactly the promotion path the prior risk list named for +1 maturity; two real engines now block merges, clearing the chromium-only red flag (Implementation holds at 8: the promotion is an enforcement move, not new responsive execution breadth, and webkit stays advisory). §12 Performance & Scalability Maturity 4→3 (downgrade): the sole basis for the thirteenth-wave M4 was the performance-smoke CI job as automatic enforcement, but that job is a runs-clean smoke (--job Dry, fails only if a benchmarked path throws or no longer compiles, ci.yml:172,193), not a latency-regression gate, so it does not automatically enforce the performance property the rubric's maturity-4 bar requires; convention plus review plus a compile/run smoke is Maturity 3 (Implementation holds at 8; no work was lost, the recalibration rests entirely on the smoke-vs-regression distinction). §23 Front-End Performance Maturity 3→4 applied to the table: the eighteenth-wave prose recorded this move but the table row and the Indices block were never updated (a partial-application inconsistency this refresh cures); the WebVitals budget gate (WebVitalsE2ETests.cs:43, blocking chromium ui-e2e job) is re-confirmed against source. Three adversarially-refuted first-pass proposals are recorded, not applied: §9 Maturity 3→4 (the in-repo OpenAPI drift gate is deliberately consumer-owned, OpenApiEndpointExtensions.cs:12), §25 Maturity 3→4 (NavigationFlow.md has no drift gate and the Routes.razor route-auth wiring has no test; only leaf behaviors are auto-tested), and §33 Implementation 8→9 (the new consumer-source-build cross-repo canary, ci.yml:262, is real progress but advisory (continue-on-error: true) and does not remove the two cited caps: the Aspire AppHost headless hang and Common not being independently runnable). All 28 other categories re-confirmed at their prior scores. ADRs 046-048 (HTTP API versioning, soft-deleted user session revocation, primitive identifier type aliases) land as evidence enrichment in categories already scored 8-9 with no move. The source-generated, CI-gated FACTS.md reports 15 packages / ADR set 001-048 / 85 fitness methods across 28 bases (Common runs 46) and this scorecard's rows are synced to match. Net indices: Maturity 94.4% → 95.1% (306 → 308 / 324: §22 +2, §23 +2, §12 -2), Implementation 84.3% (683/810) unchanged (the prior Indices block read 678/83.7%, stale against its own table rows after the eighteenth-wave §20/§21 lifts; also cured here). Implementation stays the weaker axis (gap ~10.8 points); no category is N/A. The Indices section below is authoritative; the executive summary and closing analysis predate this wave.
Update 2026-07-17 (twentieth wave: evidence re-score at v1.117.0, four scores move). A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.117.0 (git HEAD 76d70cf, working tree clean) moves four scores. §25 Navigation & IA Maturity 3→4: the navigation-contract drift gate the prior risk list prescribed now ships and gates merges: NavigationContractTests reflects over every routable MMCA.Common.UI page and asserts set-equality plus auth-posture agreement against the embedded NavigationFlow.md routes table (with a non-vacuous 8-route floor), and the test project sits in MMCA.Common.slnx, so the gate runs in the CI-gated unit tier (NavigationContractTests.cs:29,44, MMCA.Common.slnx:45), meeting the rubric maturity-4 "enforced automatically (CI)" bar and closing the §25 red flag (navigation drifting from the documented per-role design). Implementation holds at 8: the gate is an enforcement move, not new execution breadth, and the "deep-link param typing beyond list-state is still light" cap is unchanged (framework routes are plain string templates with only the sanitized ?returnUrl= query state), the same calibration §22/§23 applied to their own gate promotions. §33 Developer Experience Maturity 3→4 + Implementation 8→9: the consumer-source-build canary (builds MMCA.Helpdesk against the PR's framework source via UseLocalMMCA) was promoted from advisory to a required merge gate on 2026-07-16 (continue-on-error removed and the "Consumer source build (Helpdesk)" context added to branch protection after 9 consecutive green runs, ci.yml:267-268, confirmed live via the branch-protection API), so a framework change that breaks a source-mode consumer now blocks the merge automatically: that clears both the maturity-4 automatic-enforcement bar and the sole cap the nineteenth wave placed on Exemplary implementation. Held below 10 by the residual friction items, now implementation notes only (the Aspire AppHost hangs when launched headless, and Common itself is a library, not independently runnable). §22 Responsive & Cross-Browser Implementation 8→9: webkit was promoted to a required merge gate on 2026-07-16 after 11 consecutive green main runs (ci.yml:112-114, no continue-on-error remains anywhere in the job, confirmed live via the branch-protection API), so all three engines now block merges: this closes the one stated reason the row held at 8 ("webkit remains advisory") and completes the full 3-engine matrix as an enforced, not just exercised, capability. Held below 10: the matrix runs against the backend-less gallery's representative states, and RESPONSIVE.md's device rows remain a documented matrix, not device-lab execution. (Doc drift cured with this refresh: RESPONSIVE.md still listed webkit as advisory.) §13 Observability Implementation 9→8 (band recalibration, no work lost): the row's own text names two of the six §13 criteria as deployer-owned (SLO alerting/dashboards and runbooks), which is the rubric's Strong band ("nearly all criteria met, minor isolated gaps"), not the Exemplary "all criteria met to a high standard" that 9 claims: the same calibration this scorecard already applies to §17 and §29 for structurally identical deferred-to-consumer reasoning. The in-repo substance is re-confirmed and unchanged (warm-up readiness gate wired unconditionally into AddServiceDefaults with 9 unit tests, RED-metric command/query parity, poll-span noise filtering; stale row anchors corrected to Extensions.cs:38,173,188,302-315,344-352). Three first-pass lift proposals were adversarially refuted and are recorded, not applied: §9 Maturity 3→4 (the OpenAPI/contract drift gate is deliberately consumer-owned per OpenApiEndpointExtensions.cs:13, and no §9-specific CI gate exists in-repo, so enforcement stays review/convention), §17 Maturity 3→4 (the new sample-deployment-validate job compile-checks the reference Bicep on every push, ci.yml:311-322, but a real what-if/deploy stays consumer-side by the workflow's own comment, so the CD axis is not automatically enforced in-repo), and §31 Maturity 2→3 / Implementation 7→8 (the proposal re-cited the identical evidence already grounding M2/I7; no new §31 work exists). One recorded candidacy is declined on live branch-protection evidence: §12 stays Maturity 3. The backlog's latency-regression gate shipped 2026-07-16 (the performance-smoke job now runs --job Short --exporters json and build/perfgate fails on committed perf-baseline.json ceiling/ratio violations, ci.yml:174-200), a real qualitative upgrade over the runs-clean smoke, but the job's context is absent from the live required_status_checks list (queried via the branch-protection API; CONTRIBUTING.md:57-62 agrees), so a red perf gate does not block a merge and the rubric's merge-gate bar is unmet; adding the context to branch protection is now the whole path to M4. All 28 other categories re-confirmed at their prior scores. The source-generated, CI-gated FACTS.md reports 15 packages / ADR set 001-048 / 85 fitness methods across 28 bases (Common runs 49) and this scorecard's rows are synced to match (the prior "Common runs 46" fitness-subclass count was stale against FACTS.md). Net indices: Maturity 95.1% → 96.3% (308 → 312 / 324: §25 +2, §33 +2), Implementation 84.3% → 84.6% (683 → 685 / 810: §22 +2, §33 +2, §13 -2). Implementation stays the weaker axis (gap ~11.7 points); no category is N/A. The Indices section below is authoritative; the executive summary and closing analysis predate this wave.
Update 2026-07-21 (twenty-first wave: evidence re-score at v1.121.0, one score moves). Recorded in full in the executive summary below: §12 Performance & Scalability Maturity 3→4 (the Performance gate (BenchmarkDotNet Short + baseline verify) context is present in live required_status_checks on main, 8 required contexts, refuting the twentieth wave's sole stated basis for holding it at 3). Maturity 96.3%→96.9% (312→314/324); Implementation unchanged at 84.6% (685/810). Seven proposed lifts (§4, §9, §11, §19, §20, §26, §29) were refuted on an adversarial second pass and held at prior; the remaining 26 categories re-confirmed with no move. One documentation drift (the CONTRIBUTING.md gate list vs live protection) was routed to the backlog under #34 and fixed the same day (MMCA.Common PR #100).
MMCA.Common is a .NET 10.0 (LangVersion: preview) framework shipped as fifteen NuGet packages to GitHub Packages (Shared, Domain, Application, Infrastructure, API, Grpc, UI, UI.Maui, UI.Web, Aspire, Aspire.Hosting, Testing, Testing.E2E, Testing.UI, Testing.Architecture) (a library/SDK, not a runnable app) that supplies the cross-cutting foundation for modular-monolith applications built on DDD, Clean Architecture, and CQRS. Its substance is dense and consistent: a Result<T> error-flow pattern, soft-delete/audit-stamping aggregate base classes, an auto-discovered IModule system with topological (Kahn) registration, a Scrutor decorator pipeline, EF Core with a database-per-microservice resolver, and an outbox for cross-source consistency, all layered across Core/Presentation/Hosting tiers. The headline finding is that architecture rules are not aspirational here: the dependency rule is enforced twice over (compile-time MSBuild targets in §3 plus NetArchTest fitness functions, now factored into a define-once shared rule library — the 13th package Testing.Architecture — consumed identically by all three repos per ADR-015), and the same governance rigor extends to microservice-extraction seams (§7), supply chain (§32 — the MassTransit-v8 license pin is a build gate, now also ADR-016), security (§11), and docs (§34's now-sixteen dated ADRs, 001-016). This is a reference-quality governance posture. The ADR set has since grown to twenty-two (001-022), a polyglot-persistence capability (three storage engines behind one entity model) ships as tested framework plumbing (ADR-018), v1.80.0 adds an opt-in permission-based authorization layer plus TimeProvider adoption (fourth wave), and v1.81.0-v1.82.0 add an opt-in Scalar OpenAPI UI, a FinOps COST.md, an RS256 pin on the JWKS-forwarded auth path, and three newly-committed ADRs (020-022) that clear the prior cycle's governance nits (see the fifth wave below).
This re-verification (2026-06-20) found the prior report materially stale. A remediation wave landed on 2026-06-19/06-20 (sixteen commits between v1.68.0 and v1.72.0) that closes four of the previously-flagged top risks, each verified against source: (1) event-schema versioning now exists — BaseIntegrationEvent.SchemaVersion => 1 (ADR-010) plus an EventVersioningConventionTests fitness gate, removing the §6 cross-service contract-break risk; (2) OpenAPI generation now ships — AddCommonOpenApi()/MapCommonOpenApi() over the built-in Microsoft.AspNetCore.OpenApi 10.0.8, removing the §9 "undocumented specs" red flag; (3) the CI coverage gate is now enforced — ci.yml fails the build below a 53% line-coverage floor (was report-only), closing the last §14 gap; (4) the chaos/fault-injection test is now real — ResilienceCircuitBreakerFaultInjectionTests trips a breaker and asserts short-circuiting (was prose-only TODO), lifting §29. Two further hardening moves landed: the CycloneDX SBOM step is now a blocking release gate (was continue-on-error), and the ADR set grew 9→11 (ADR-010 event versioning, ADR-011 single-locale i18n), each with a matching fitness function.
A second wave landed 2026-06-21/06-22 (v1.73.0→v1.76.0), folded into this re-verification. It is governance-and-consolidation rather than score-moving substance, so the scores below are unchanged, but it reinforces the already-high §14/§15/§32/§34 ratings: (1) the architecture fitness functions were extracted into a define-once shared package — MMCA.Common.Testing.Architecture (the 13th package, v1.73.0) holding one IArchitectureMap-parameterized NetArchTest rule library + abstract test bases — so Common, ADC, and Store now run literally the same rules (ADR-015) instead of parallel copies; (2) shared cross-cutting infrastructure was promoted up into Common (v1.74.0, drift-reduction P4); (3) the ADR set grew 11→16 — ADR-012 (gRPC-host transport), ADR-013 (Result pattern), ADR-014 (CQRS decorator pipeline), ADR-015 (architecture fitness functions), ADR-016 (lockstep versioning + MassTransit-v8 pin) — documenting patterns that previously lived only in code; v1.76.0 itself was a docs-only release. The consumer apps were swept to v1.76.0 in lockstep (and have since moved with the framework to v1.79.0 — see the third wave below), so the framework and both consumers remain fully converged with no version lead.
A third wave landed 2026-06-23/06-24 (v1.77.0→v1.79.0), folded into this re-verification. It adds one capability and several governance refinements: (1) polyglot persistence (ADR-018, v1.79.0) — three storage engines (SQL Server / Cosmos / SQLite) behind one entity model, selected per entity by a [UseDataSource] attribute on an engine shim base (EntityTypeConfigurationSQLServer/Cosmos/Sqlite over the engine-aware EntityTypeConfiguration<TEntity,TId>), with WithSQLServerDataSource/WithCosmosDataSource/WithSqliteDataSource AppHost helpers (Aspire.Hosting/Extensions.cs:132/166/198), a CrossSourceSpecification.BuildAsync helper for engine-portable cross-source filters, and a new opt-in SpecificationsDoNotNavigateToOtherEntities fitness rule (ArchitectureRules.Specifications.cs:24) that fails the build on a latent cross-engine navigation hazard. The plumbing is complete, unit/integration-tested, and shipped, but no production entity routes to a non-SQL engine yet (ADR-018 records the capability as deliberately latent — even ADC's Conference Session→Cosmos / Room→SQLite trial was built, tested, then reverted to all-SQL-Server with the seams kept). (2) ADR-017 (request idempotency) now formally documents the long-standing [Idempotent] HTTP-edge filter (Source/Presentation/MMCA.Common.API/Idempotency/, present since v1.14.0 — Idempotency-Key header → distributed-cache replay with X-Idempotent-Replay, per-key SemaphoreSlim double-check), the inbound counterpart to ADR-003's consumer idempotency. (3) Supply-chain governance deepened — a committed .github/dependabot.yml adds weekly NuGet/Actions update PRs with semver-major ignores for the held packages (MassTransit, VS.Threading.Analyzers, StackExchange.Redis, MessagePack), reinforcing the §32 pin discipline in config (not just the fitness test), plus a sweep of dependencies to latest stable. (4) A 985-line, 9-phase GETTING-STARTED.md adoption guide shipped (worked companion to MMCA.Helpdesk), materially strengthening onboarding (§33). Two minor §34 hygiene regressions accompany the wave: the CHANGELOG skips 1.72-1.78 (jumps 1.79.0 → 1.71.0), and ADR-018 is on disk but uncommitted at review time. Net score effect: §33 rises (M2→M3, I7→I8); all other categories hold, with enriched evidence.
A fourth wave landed 2026-06-25/06-26 (v1.80.0), folded into this re-verification. It is capability-and-governance enrichment that lands in categories already scored 9-10, so no numeric score moves this cycle — but the evidence under §11/§1/§4/§14/§34 is materially enriched, with three honest caveats recorded rather than papered over: (1) permission-based authorization now ships (commit bc6c5d7) — IPermissionRegistry + PermissionRegistryBuilder (Shared) declare role→permission grants, [HasPermission("x")] resolves an on-demand perm:x policy via PermissionPolicyProvider, and PermissionAuthorizationHandler checks the caller's roles/claims against the registry; it is opt-in and backward-compatible (the four named role policies are untouched, the mechanism is inert until a host calls AddPermissions(...)), well-factored (immutable FrozenDictionary/FrozenSet registry, additive cross-module grants), and backed by 13 new unit tests. Caveat: this is RBAC with a role→permission capability indirection, not ABAC/resource-based authorization, so it is a real §11 "policy-based" win but should not be over-credited as resource-based. (2) TimeProvider is now injected into TokenService (iat/nbf/exp), the notification read handlers, and UserNotification.MarkAsRead(DateTime readOnUtc) (which now takes an explicit UTC timestamp, keeping that aggregate free of ambient clock) — a genuine §14-testability/§1-DIP improvement. The one remaining domain DateTime.UtcNow — BaseDomainEvent.DateOccurred — was reviewed 2026-06-26 and accepted as a deliberate choice (occurrence-time = when the aggregate raises the event is the correct event-sourcing/audit semantic, enforced by four domain tests; relocating it to the persistence boundary regresses the semantic), and is now documented as such in BaseDomainEvent's <remarks> rather than treated as a defect. (3) ADR-019 (rate-limiting) now documents the authenticated-only global limiter — the limiter itself is pre-existing code (shipped 2026-06-12, commit 285a169), already scored under §11; v1.80.0 added only the ADR. Post-review hardening (2026-06-26): the limiter's load-bearing partition/exemption logic is now unit-tested (RateLimitPartitionTests), and the two notification handler tests now assert the stamped read-time against a fixed TimeProvider — closing the two test-coverage caveats above. Governance is mixed: ADRs 017/018 are now committed (a real fix), but ADR-019 + the README/CLAUDE index edits are uncommitted, the CHANGELOG still skips 1.72-1.78 and now also lacks a 1.80.0 entry (empty [Unreleased]), and the framework's own scorecard/backlog were not yet refreshed for this wave. (All since resolved in the v1.81.0/v1.82.0 governance pass — CHANGELOG completed, ADRs committed, and this scorecard migrated in-repo as the canonical two-axis evaluation; see the fifth wave.)
A fifth wave landed 2026-06-26 (v1.81.0→v1.82.0, HEAD 0dcac4d, clean tree at the v1.82.0 tag). Like the fourth, it lands in categories already scored 9-10, so no numeric score moves — but it converts three previously-anticipated or working-tree items into shipped, committed fact and clears the prior cycle's §34 governance nits: (1) the Scalar OpenAPI UI helper is now released (v1.81.0, not "committed-but-unreleased") — MapCommonScalarUi() renders /scalar/{doc} from the generated document, non-Production only, via the bundled Scalar.AspNetCore 2.16.6 (no CDN), so §9's interactive-UI gap is closed in a tagged release; (2) COST.md (FinOps notes, §31) is released (v1.81.0), documenting the framework's cost levers (telemetry-span filtering, outbox poll/retention tuning) and the right-sizing/attribution/surge-revert defaults consumers set; (3) RS256 is now pinned on the JWKS-forwarded auth path (v1.82.0, §11/§26) — ValidAlgorithms = [RsaSha256] on the forwarded-JWT (JWKS-discovery) validation path in MMCA.Common.API, defense-in-depth against an algorithm-confusion swap, matching the pre-existing in-process pin; (4) ADRs 020-022 are added and committed — 020 (permission-based authorization, documenting the v1.80.0 layer), 021 (consumer-inbox idempotency, documenting the pre-existing EfInboxStore), 022 (browser session-cookie auth, documenting the SSR BFF token-storage scheme) — bringing the tracked set to 001-022; (5) the §34 hygiene nits are resolved — ADR-019 is committed, the CHANGELOG is complete (1.69.0→1.82.0, no gaps), and the working tree is clean. Two test-hardening and one stability item also landed: the rate-limiter partition/exemption logic and the notification read-time clock are now unit-tested (§14), and the E2E register/login success-detection got a grace window to de-flake it (R11, §28). (The prior cycle's minor README.md:42 stale "001-016" parenthetical has since been corrected to 001-022.)
A sixth, governance-focused pass is in flight at this re-score (uncommitted working tree) and is the immediate basis for this re-verification. It moves no score (every change lands in a category already at 9-10) but adds durable governance substance: (1) FACTS.md is now generated from source by a dependency-free build/facts tool and gated in CI (ci.yml:27-28 runs dotnet run --project build/facts -- . --check), so the framework-wide facts (version, package count, ADR range, fitness-method/base counts) can no longer drift from source: a fitness function for the docs themselves (§34/§16). (2) The rubric (ArchitectureEvaluationCriteria.md) and FACTS.md are now version-controlled in this repo (previously workspace-only), completing the §34 governance pattern already applied to the ADRs and this scorecard. (3) Security-response headers were centralized into one pluggable middleware in MMCA.Common.Aspire.Security (AddCommonSecurityHeaders + ICspPolicyProvider + SecurityHeadersMiddleware, unit-tested), removing the per-host drift the previously hand-rolled headers allowed, and documented in the new ADR-023 (bringing the set to 001-023, §11/§26/§34). The one honest caveat: the tree is dirty and the CHANGELOG [Unreleased] does not yet carry ADR-023, so traceability is momentarily inconsistent until this pass is committed.
A seventh wave landed 2026-06-26/06-27 (v1.83.0→v1.84.0, HEAD b2b0aae), folded into this re-verification — and it moves one score. (1) The sixth-pass governance items are now committed and tagged — v1.83.0 (b9a6a28) shipped ADR-023 (security-response headers), the source-generated CI-drift-gated FACTS.md, and this two-axis scorecard, resolving the prior cycle's "ADR-023 uncommitted" §34 caveat. (2) PiiRedactor ships and moves §30 from Implementation 7 to 8 (v1.84.0, b2b0aae) — Domain/Privacy/PiiRedactor.cs masks every [Pii]-marked member (shallow, value-erasing) before an entity carrying personal data reaches a structured log or telemetry attribute, with 7 PiiRedactorTests (incl. a "never emits the clear-text PII values" assertion). This closes the §30 red flag the rubric names verbatim ("PII in logs/telemetry"), which the prior cycle recorded as documented-but-missing; §30 maturity holds at 3 because DSAR/export endpoints, consent capture, the personal-data inventory, residency verification, and retention execution remain consumer-owned and PiiConventionTests still passes vacuously in-repo. (3) A warm-up / readiness subsystem ships (Aspire/Warmup/ — WarmupHostedService, WarmupReadinessGate, WarmupReadinessHealthCheck, OpenIdConnectMetadataWarmupTask), wired into AddServiceDefaults so every host gates /health/ready on warm-up (gate starts closed, opens even on task failure = availability over warmth, with lazy-retry fallback under ADR-009; the built-in task pre-fetches OIDC discovery to kill ACA cold-start), now documented in ADR-025. It is genuine §29/§13 substance but was scored at v1.84.0 as enrichment, not a lift: it shipped with zero unit tests (since added in the eighth wave below, which lifts §29 Impl 7→8) and the §29-capping recovery gaps (no in-repo restore drill, RTO/RPO, or SLO/error-budget) are unchanged, so at v1.84.0 §29 held 3/7 and §13 holds 4/9. (4) ADR-024 (two-channel notifications) documents the pre-existing SignalR-push + durable UserNotification-inbox seams (IPushNotificationSender/INotificationRecipientProvider with no-op defaults), enriching §6 evidence with no move. The honest caveat: at this re-score ADR-024/025 are untracked and the CHANGELOG [Unreleased] is empty while the repo is tagged through v1.84.0 (a transient §34 hygiene nit, tracked in the backlog).
An eighth wave — the under-8 Implementation remediation — shipped in v1.85.0 (commit 78e5312, tag v1.85.0) and is verified against source in this re-score. Every category whose Implementation sat below 8 was lifted with shipped, tested in-repo evidence rather than narrative: §5 (a SliceCohesionTestsBase fitness function enforcing slice co-location), §12 (a BenchmarkDotNet smoke harness + the framework page-size guard), §17 (a reference samples/deployment Bicep/OIDC sample + DEPLOYMENT.md), §24 (Register/Login converted to EditForm + DataAnnotations field-level validation), §25 (an in-shell Forbidden 403 page + NavigationFlow.md), §29 (warm-up subsystem unit tests + a RESILIENCE.md SLO/error-budget + restore-drill reference), and §31 (a built-in OTel Telemetry:TracesSampleRatio sampler knob + a per-message log trim). §5/§12/§17/§24/§25/§29 each reach Implementation 8 and §31 reaches 7 (full Release build clean, 1651 tests pass). The wave also moves one maturity score: §5 Vertical Slice 3→4, because its new slice-cohesion fitness function is automatic CI enforcement of the slice convention (the rubric's maturity-4 bar). A separate docs governance pass remains uncommitted at this writing (ADRs 024/025/026 untracked, the FACTS.md ADR-count bump, ADR cross-links, and a ServiceContractAttribute doc-comment correction that stops it claiming a non-existent [ServiceContract] arch test); ADR-026 documents the two-tier caching substrate (the ICacheService memory-or-distributed swap + the HTTP output-cache edge) that §10 already scored.
A ninth wave landed 2026-06-28/29 (v1.86.0→v1.92.0) and is the basis for this re-score — it moves five scores. The headline is internationalization: multi-locale i18n (en-US + Spanish) now ships in the framework itself (ADR-027, superseding the single-locale ADR-011) — co-located .resx + IStringLocalizer<T>, backend errors localized at the edge keyed by the existing Error.Code, one culture cookie as the source of truth across SSR/Server/WASM (forwarded to services as Accept-Language), and a per-user PreferredCulture. This flips §27 from N/A to a scored Maturity 2 / Implementation 6 (immature: culture-less date/number formatting is guarded only by an advisory analyzer, with no missing-key/coverage CI gate), and removes the last N/A category, so all 34 categories now count toward the indices. Five further capabilities landed without moving a score, each enriching evidence: ADR-028 day/dark theme (the dark palette is now wired through MudThemeProvider with a ThemeService + a ThemeToggle in the shared layout — §20, where the raw-hex/!important deductions still hold); ADR-029 brute-force protection plus ADR-032 password hashing (PBKDF2-HMAC-SHA512, 32-byte salt, 600k iterations, FixedTimeEquals, with a load-bearing legacy-HMAC verify-and-migrate branch — §11, which still recalibrated to 8); ADR-030 startup sole-migrator (each service self-applies its EF migrations at boot — §8/§17, a runtime mechanism, not the CI migration-apply gate those categories' gaps name); and ADR-031 feature-flag management (Microsoft.FeatureManagement on both the controller [FeatureGate] and CQRS IFeatureGated surfaces — §10). The ADR set grew 026→032 and the source-generated FACTS.md advanced the fitness counts (71 methods across 18 bases, Common's own build executes 38). The honest caveat: ADR-032 is untracked and a docs/source pass (seven ADR edits, FACTS.md, one source edit) is uncommitted at this re-score.
The two axes remain deliberately asymmetric: at the ninth wave Maturity 91.7% vs Implementation 84.1% (current: 96.9% vs 84.6%, see the twenty-first wave below), keeping Implementation the weaker axis. Both indices dip slightly this cycle, and the dip is honest re-calibration rather than regressed work: §27 i18n enters both denominators at a low 2/6 (genuinely shipped, but newly so and only advisory-guarded), and §11/§22/§23 were trimmed to better-supported bands on a close re-read (algorithm-confusion defenses and a 3-engine matrix are real, but vault binding, a chromium-only gate, and unmeasured Web Vitals keep them out of the top band). The residual gap is structural rather than a quality defect: the framework consistently ships mature, well-governed mechanisms (ADRs, fitness functions, documented patterns) whose full execution necessarily completes in the consumer apps (ADC/Store). The in-repo half of §17 (a reference Bicep/OIDC sample) and §29 (warm-up tests + an SLO/restore reference) is done; the deeper §17 CD-to-Azure and §29 drilled-restore substance still lives downstream, and Data-migration gating (§8) remains maturity-above-implementation because a verifiable CI migration-apply step lives in the consumer repos (ADR-030's boot-time self-migration is a runtime mechanism, not that gate). Compliance (§30) and FinOps (§31) are the inverse: their in-repo implementation (the PiiRedactor log-masking + IAnonymizable erasure seam, now gated by PiiErasureContractFitnessTests; the documented cost-lever defaults) leads a maturity that stays capped because the governing process — DSAR/consent capture, the personal-data inventory, residency verification, per-service cost attribution, right-sizing — is consumer-resident.
The practical implication: closing the remaining Implementation gap is mostly about adding evidence and verification of execution inside MMCA.Common itself — a committed OpenAPI baseline-drift check (§9), an in-repo restore drill + baseline SLOs (§29), a CI migration-apply step against an ephemeral DB (§8), and a smoke-deploy of the reference Bicep/OIDC sample (§17) — rather than re-architecting. The freshest gap this cycle is the newly-scored §27 i18n: lifting it toward maturity 4 needs a missing-key/translation-coverage CI gate and a documented locale-addition governance step. There are no longer any N/A categories: §27 Internationalization, previously excluded under the single-locale ADR-011, is now an active scored category after ADR-027 shipped en-US + Spanish in the framework.
A twenty-first-wave re-score ran 2026-07-21 at v1.121.0 (HEAD 4a4fc05, clean tree) and moves exactly one score. §12 Performance & Scalability rises Maturity 3→4. The nineteenth wave dropped it to 3 for lack of a real regression gate, the twentieth wave kept it at 3 on the narrower ground that the shipped gate's job context was absent from live branch protection. That ground no longer holds: main now requires 8 status checks including Performance gate (BenchmarkDotNet Short + baseline verify), and the job carries no continue-on-error (.github/workflows/ci.yml:179,196-204), so a latency or allocation regression against the committed baseline blocks the merge. That is automatic enforcement, the rubric's Maturity-4 bar. Implementation holds at 8 because load and stress timing against realistic volumes still lives in the consumer apps. The cycle's more useful finding is what did not move: first-pass scoring proposed lifts in seven categories (§4, §9, §11, §19, §20, §26, §29) and an adversarial second pass refuted every one against source, each for a concrete reason rather than caution: only Notifications is a bounded context in-repo (§4), the three contract-test bases are subclassed downstream and never in Common's own tests (§9), the sample's Key Vault secret binding is still dangling in the bicep template (§11), and no commit has touched resilience, restore-drill, or warm-up files since the twentieth-wave baseline (§29). One documentation drift was surfaced and routed to the backlog under #34 rather than scored: CONTRIBUTING.md still advertises five required gates and calls webkit advisory, while live protection requires eight including webkit, the Helpdesk consumer-source canary, and the perf gate. Because scorecard adjudications cite that file, its drift can mis-ground a future score, which is precisely what happened to §12 this cycle.
A twenty-second-wave re-score ran 2026-07-23 at v1.123.0 (HEAD c911480, clean tree) and moves nothing: all 34 categories re-confirm at their twenty-first-wave values. The cycle's value is again in what it refused to move. Three first-pass lifts were refuted against source: §9 (Maturity 3→4) because OpenApiContractTestsBase is still subclassed only in consumer hosts, never in Common's own tests, and OpenApiEndpointExtensions.cs:13 records that delegation as deliberate, so no in-repo CI gate enforces the contract; §10 (Implementation 8→9) because the three documented hold-reasons are still in code (the distributed cache path is a no-op without a real IConnectionMultiplexer, DependencyInjection.cs:158; the idempotency semaphore is in-memory, not cross-instance-exclusive; resilience config is partly literal); and §30 (Maturity 3→4) because the only automatic gate, PiiErasureContractFitnessTests, proves the erasure mechanism while the structural [Pii] convention scan stays vacuous in-repo and the governing process (inventory, DSAR, consent, residency, retention) is consumer-resident. Two first-pass downgrades were also refuted: §23 and §24 (each Implementation 8→7) failed on a fresh re-read of every cited file: no regression exists, and §23's web-vitals budget gate is now enforced on all three browser engines (ci.yml:107), stronger than when its 8 was set. The v1.122.0-v1.123.0 train (the typed filter DSL operators, the EntityQueryPipeline page-size clamp, cache-observability warnings, the IIntegrationEventPublisher removal with callers moved to IEventBus) lands as evidence enrichment with no band move. Indices hold at Maturity 96.9% (314/324) / Implementation 84.6% (685/810).
Scorecard
| # | Category | Weight | Maturity (0-4) | Impl (0-10) | Weighted (M·w / I·w) | Evidence / Notes |
|---|---|---|---|---|---|---|
| 1 | SOLID Principles | 3 | 4 | 9 | 12/27 | All five principles met and machine-enforced: DIP via Application-owned ports + NetArchTest/MSBuild guards (reinforced in v1.80.0 by TimeProvider injection replacing ambient DateTime.UtcNow in TokenService/notification handlers), OCP via decorator pipeline + the new permission registry as a single change-point for authz grants (no growing switch), ISP via narrow marker interfaces, SRP via single-method handlers/decorators, LSP clean. Held at 9 by a few broader base-controller/UoW surfaces. — Application/Interfaces/Infrastructure/IUnitOfWork.cs; Application/DependencyInjection.cs:94-103 (Scrutor TryDecorate, innermost-first → exec order FeatureGate→Logging→Caching→Validating→Transactional→Handler); Shared/Abstractions/Result.cs:130-163 (Match/Map/BindAsync) |
| 2 | Design Patterns | 2 | 4 | 9 | 8/18 | Idiomatic, problem-driven: Result over exceptions, Repository/UoW, Specification with composition (now incl. InlineSpecification + the polyglot CrossSourceSpecification), Decorator pipeline, entity Factory+Result, Outbox, Strategy — consistently named; no pattern-theater or anemic manager/util classes; vetted libs (Scrutor/MassTransit/FluentValidation). — Shared/Abstractions/Result.cs:130/146/159 (Match/Map/BindAsync); Domain/Specifications/Specification.cs:62/88/114 (And/Or/Not expression-tree composition, EF-translatable) + :45 (InlineSpecification); Application/Specifications/CrossSourceSpecification.cs:39; Application/DependencyInjection.cs:89-106 |
| 3 | Clean Architecture | 3 | 4 | 9 | 12/27 | Dependency rule points strictly inward, enforced twice (MSBuild targets + NetArchTest); domain verified framework-free; Application defines ports, Infrastructure implements adapters. No circular refs / shared-kernel dump. — Source/Build/MMCA.Common.LayerEnforcement.targets:1-90 (compile-time guard, BeforeTargets ResolveProjectReferences, imported Directory.Build.props:87-88); Tests/Architecture/.../LayerDependencyTests.cs:12-58; .../DomainPurityTests.cs:13-26 (Domain forbidden EF/ASP.NET/Serilog/AutoMapper/Newtonsoft/+more) |
| 4 | Domain-Driven Design | 3 | 4 | 8 | 12/24 | Strong tactical DDD: aggregates with enforced invariants + domain events, immutable VOs with Result factories, identifier aliases vs primitive obsession, and an AggregateConventionTests fitness fn pinning Create→Result<T>. v1.80.0 nudges domain purity — UserNotification.MarkAsRead(DateTime readOnUtc) now takes an explicit UTC timestamp instead of reading an ambient clock, with TimeProvider injected at the handler edge. Held at 8 (not lifted): strategic-DDD (bounded contexts/ubiquitous language) is still realized downstream (only Notifications lives here). The one remaining domain DateTime.UtcNow — BaseDomainEvent.DateOccurred — is a deliberate, documented choice, not a defect (reviewed 2026-06-26): a domain event's occurrence instant is the moment the aggregate raises it, which is the correct event-sourcing/audit semantic (four domain tests enforce it); moving the stamp to the persistence boundary would regress occurrence-time → persist-time, and threading a clock through every aggregate is disproportionate. — Domain/Entities/AuditableAggregateRootEntity.cs:13-105; Domain/Notifications/.../UserNotification.cs:53-58 (explicit-timestamp seam); Domain/DomainEvents/BaseDomainEvent.cs (deliberate occurrence-time default, see <remarks>); Shared/ValueObjects/{Money,Address,Email,PhoneNumber,DateRange}.cs; Tests/Architecture/.../AggregateConventionTests.cs:24-48 |
| 5 | Vertical Slice Architecture | 2 | 4 | 8 | 8/16 | Slice cohesion is now machine-enforced: a SliceCohesionTestsBase fitness function (shared MMCA.Common.Testing.Architecture) fails the build if a handler/validator is stranded from its same-assembly command/query contract — automatic CI enforcement of the slice convention, which meets the rubric's maturity-4 "enforced automatically by tests/CI" bar (matching every other fitness-gated category here). Where features exist they are clean vertical slices (Feature/UseCases/{Action}/ co-locating command/query + handler + validator + DTO) with cross-cutting in the pipeline. Impl held at 8 (not 9) because the framework's own primitives are deliberately horizontal (correct for infra) and only Notifications exercises slicing in-repo. — Source/Hosting/MMCA.Common.Testing.Architecture/ArchitectureRules.Slices.cs + Bases/SliceCohesionTestsBase.cs; Tests/Architecture/.../SliceCohesionTests.cs; Application/Notifications/PushNotifications/UseCases/Send/; .../UserNotifications/UseCases/{MarkRead,MarkAllRead,GetInbox,GetUnreadCount}/ |
| 6 | CQRS & Event-Driven | 2 | 4 | 9 | 8/18 | Real command/query separation with an enforced, documented decorator pipeline; atomic outbox persist + dual dispatch; genuinely idempotent inbox consumer (dedup by MessageId via IInboxStore, now formally documented in ADR-021 as the broker-consume sibling of ADR-003's outbox + ADR-017's HTTP-edge idempotency). Event-schema versioning present — BaseIntegrationEvent.SchemaVersion => 1 (ADR-010) + an EventVersioningConventionTests fitness gate — removing the prior unversioned-contract risk. Held below 10 only because SchemaVersion is a default with no upcaster/compat policy yet demonstrated and read models are DTO projections. — Domain/DomainEvents/BaseIntegrationEvent.cs:22; Infrastructure/Persistence/Outbox/OutboxProcessor.cs (drain :221, dead-letter :282-283, retry :305-308); Persistence/Inbox/EfInboxStore.cs:29 |
| 7 | Microservices Readiness | 3 | 4 | 9 | 12/27 | Bounded-context modules extractable without rewrites (ADR-008), real data ownership per service (ADR-006), async events + gRPC only where sync needed, resilience policies, versioned contracts, trace propagation. Extraction seams enforced by fitness functions, not convention. No distributed-monolith/shared-table flags. — ADR-006; Application/Messaging/IMessageBus.cs:28 (InProcess vs Broker impls); Testing.Architecture/ArchitectureRules.Transport.cs:19 (TransportDoesNotLeakIntoCoreLayers, rule body in the shared package) + Bases/MicroserviceExtractionTestsBase.cs:13, subclassed at Tests/Architecture/.../MicroserviceExtractionTests.cs:10; Infrastructure/Services/IntegrationEventConsumer.cs:42 (inbox-idempotent) |
| 8 | Data Architecture | 3 | 4 | 8 | 12/24 | Audit fields stamped centrally via AuditSaveChangesInterceptor (not per-handler), soft-delete + global filters via reflection over IAuditableEntity, optimistic concurrency tokens on every auditable entity, batch-by-IDs eager-loading vs N+1, per-service migrations/isolation. Now broadened by shipped polyglot persistence (ADR-018): three storage engines behind one entity model, engine chosen per-entity by [UseDataSource] on an engine shim base, cross-engine relationships auto-degraded (CrossDataSourceDegradeConvention), an engine-portable CrossSourceSpecification.BuildAsync, and a SpecificationsDoNotNavigateToOtherEntities build gate — proven by unit/integration tests. Held at 8 (not lifted): the rubric's §8 gaps are untouched — cross-source consistency is still best-effort sequential (no 2PC, ADR-006), migration application is policy-driven — each service can self-apply its EF migrations at boot as the sole migrator (ADR-030), but that is a runtime mechanism, not the verifiable CI migration-apply step §8 still lacks in this repo — and ADR-018 itself records the non-SQL engines as deliberately latent (no production entity on them). — Infrastructure/Persistence/DbContexts/ApplicationDbContext.cs:149-163 (soft-delete filters), :176-196 (RowVersion/IsConcurrencyToken), :94-96 (interceptor audit stamping); Infrastructure/Persistence/Configuration/EntityTypeConfiguration/EntityTypeConfiguration.cs + EntityTypeConfiguration{SQLServer,Cosmos,Sqlite}.cs; Infrastructure/Persistence/Conventions/CrossDataSourceDegradeConvention.cs:34-37; Application/Specifications/CrossSourceSpecification.cs:39 |
| 9 | API & Contract Design | 2 | 3 | 9 | 6/18 | Consistent RFC 9457 ProblemDetails with centralized status mapping, header-based versioning with backward-compatible default, uniform pagination/filtering/sorting over the generic EntityControllerBase query contract (now ADR-034-documented), edge validation, DTO decoupling by ADR, OpenAPI generation (AddCommonOpenApi()/MapCommonOpenApi() over built-in Microsoft.AspNetCore.OpenApi, serves /openapi/v1.json outside Production). Request idempotency at the inbound edge is ADR-documented (ADR-017). The prior two hold-reasons are now resolved: (a) an opt-in Scalar interactive UI helper now ships in a tagged release (v1.81.0) — MapCommonScalarUi() renders /scalar/{doc} from the generated document, non-Production only (OpenApiEndpointExtensions.cs:48), via the bundled Scalar.AspNetCore (no CDN; 2.16.7 today); (b) the in-repo baseline-drift gate is deliberately consumer-owned — the API surface lives in the consumer hosts, which own the contract-snapshot test (e.g. ADC's OpenApiContractTests), so duplicating it in the library would be redundant (documented in OpenApiEndpointExtensions). — API/Startup/OpenApiEndpointExtensions.cs:28,46-50 (MapCommonOpenApi + MapCommonScalarUi, both non-Production); API/Startup/WebApplicationBuilderExtensions.cs:162 (AddCommonOpenApi), :71-84 (header api-version, v1.0 default); Directory.Packages.props:24 (Scalar.AspNetCore 2.16.7); API/Idempotency/IdempotencyFilter.cs (ADR-017); API/Middleware/ErrorHttpMapping.cs:20-28 |
| 10 | Cross-Cutting Concerns | 2 | 4 | 8 | 8/16 | Validation, logging, transactions, caching, feature-gating all handled once in the decorator pipeline; feature-flag management is now ADR-documented (ADR-031 — Microsoft.FeatureManagement enforced on both the controller [FeatureGate] surface and the outermost IFeatureGated CQRS decorator, disabled = 404); configuration strongly-typed + ValidateOnStart on seven options types; cache invalidation tied to command success; resilience via one shared Polly mechanism; mapping by ADR; request idempotency centralized in one [Idempotent] filter (ADR-017). The caching substrate is now ADR-documented (ADR-026): application code depends only on ICacheService (Get/Set/Remove/RemoveByPrefix), and AddCaching() binds DistributedCacheService when a real IDistributedCache is present else MemoryCacheService — a startup-time memory-or-distributed swap (the same monolith-now/scale-later seam as the message bus), with a short default TTL backstopping best-effort prefix invalidation. Short of exemplary because the in-memory idempotency semaphore (cross-instance replay is cache-consistent but not cross-instance-mutually-exclusive, per ADR-017) and distributed prefix invalidation are pragmatic but coarse (no-op without an IConnectionMultiplexer, TTL-bounded — ADR-026), and resilience config is partly literal. — Application/UseCases/Decorators/* (10 decorators); Application/Interfaces/ICacheService.cs; Infrastructure/DependencyInjection.cs:60-246 (.ValidateDataAnnotations().ValidateOnStart()) + AddCaching (memory-or-distributed swap); CachingCommandDecorator.cs; API/Idempotency/IdempotencyFilter.cs; ADR-026 |
| 11 | Security | 3 | 4 | 8 | 12/24 | Authn centralized (JWKS/RS256+HS256 with algorithm pinning), authz at policy layer, secrets validated-not-committed, rate limiting + brute-force protection + security headers + CSP shipped, CI fails on vulnerable packages. Now broadened by a shipped permission-based authorization layer (v1.80.0): [HasPermission("x")] → on-demand perm:x policy via PermissionPolicyProvider → PermissionAuthorizationHandler checks the caller's roles/claims against an immutable IPermissionRegistry of role→permission grants — opt-in and backward-compatible with the existing named role policies, 13 unit tests. The authenticated-only global rate limiter is now ADR-documented (ADR-019). RS256 is pinned on the JWKS-forwarded auth path (v1.82.0) — ValidAlgorithms = [RsaSha256] on the forwarded-JWT (JWKS-discovery) validation path, closing the cross-service alg-confusion gap that the in-process pin already covered. Further hardened this cycle: a framework IPasswordHasher (ADR-032) hashes new passwords with PBKDF2-HMAC-SHA512 (32-byte salt, 600k iterations, FixedTimeEquals compare) and keeps a load-bearing legacy-HMAC verify-and-migrate branch (picks the algorithm by salt length so old logins still verify), and the always-available brute-force LoginProtectionService is now ADR-documented (ADR-029). Recalibrated to Implementation 8 ("Strong", not "Exemplary"): two §11 criteria are unmet in-repo — vault/managed-identity secret binding is delegated to the deployer (correct for a library), and authorization is policy-based RBAC with capability indirection. A real resource-ownership axis now ships and is documented (ADR-033: OwnerOrAdminFilter 403s a single-resource route on a customer_id-claim mismatch, OwnershipHelper row-scopes collection queries, one admin bypass), but it is opt-in per controller, claim-trusting, and ownership-not-ABAC, so it is a real resource-scoped check rather than the full resource/attribute-based policy engine the top band wants. — Shared/Auth/IPermissionRegistry.cs + PermissionRegistry.cs:10 (FrozenDictionary/FrozenSet, case-insensitive roles); API/Authorization/{HasPermissionAttribute,PermissionPolicyProvider,PermissionAuthorizationHandler}.cs + AuthorizationExtensions.cs:38-42,68; API/Startup/WebApplicationBuilderExtensions.cs:217 (forwarded-path ValidAlgorithms=[RsaSha256], v1.82.0), :364,380 (in-process ValidAlgorithms pinned per RS256/HS256), :34 (rate-limit bypass health/.well-known/grpc, ADR-019); Infrastructure/Auth/LoginProtectionService.cs:50-56,74-82 (exp-backoff lockout + per-IP registration limit, ADR-029); Infrastructure/Services/PasswordHasher.cs:24,52,58 (PBKDF2 600k / legacy-salt branch / FixedTimeEquals, ADR-032) + Tests/Core/MMCA.Common.Infrastructure.Tests/Services/PasswordHasherTests.cs (11 tests); API/Authorization/{OwnerOrAdminFilter,OwnershipHelper}.cs (resource-ownership axis, ADR-033); Infrastructure/Auth/LoginProtectionService.cs:54-58 (C-1 lockout-backoff-overflow clamp, 2026-07-05); ADR-037 field-level-encryption converter ships but is latent/unadopted |
| 12 | Performance & Scalability | 2 | 4 | 8 | 8/16 | Async-throughout (no sync-over-async), projection/AsNoTracking/split-query/batch-by-IDs primitives + compiled-projection cache that prevent the §12 query red flags, stateless horizontal-scale design, a framework-level max-page-size clamp + unbounded-query ceiling (closing the "unbounded query" red flag), and a repeatable BenchmarkDotNet hot-path harness so hot-path efficiency is measured, not assumed. ↑ Maturity 3→4 (twenty-first wave, restoring the level the nineteenth wave dropped): the latency/allocation regression gate that shipped 2026-07-16 is now a live merge gate. The Performance gate (BenchmarkDotNet Short + baseline verify) job runs the harness with --job Short --exporters json and a second step (build/perfgate) fails against the committed Tests/Performance/perf-baseline.json (deterministic per-benchmark allocation ceilings plus a machine-independent 1000x compiled-cache ratio floor); the job carries no continue-on-error, and its context is present in the live required_status_checks list on main (queried via the branch-protection API this cycle: 8 contexts), so a red perf gate blocks the merge. That is the rubric's automatic-enforcement bar, which the twentieth wave declined only because the context was then absent. A residual doc gap was raised alongside this score and fixed the same day: CONTRIBUTING.md listed five gates and called webkit advisory, and its reproduce snippet omitted the perf gate, webkit, and the Helpdesk canary; all eight contexts are now documented (MMCA.Common PR #100, 658786b). Implementation holds at 8: the baseline gate raises rigor but its scope is one benchmark file (the Specification compiled-cache/composition hot path), the page-size clamp and unbounded-query ceiling are real, and full load/stress timing data still lives in the consumer apps. Evidence: .github/workflows/ci.yml:179 (job name Performance gate (BenchmarkDotNet Short + baseline verify), matching the required context exactly) + .github/workflows/ci.yml:196-204 (--job Short + build/perfgate baseline verify, no continue-on-error); Tests/Performance/perf-baseline.json:3-13 (allocation ceilings + ratio floor); build/perfgate/Program.cs (fails on ceiling/ratio violation); Tests/Performance/MMCA.Common.Benchmarks/SpecificationBenchmarks.cs:14 (spec compiled-cache + composition benchmarks); Application/Settings/ApplicationSettings.cs:15 (MaxPageSize=500) + API/Controllers/EntityControllerBase.cs:127 (clamp) + Application/Services/Query/EntityQueryPipeline.cs:100 (MaxUnboundedResultLimit); Infrastructure/Persistence/Repositories/EFReadRepository.cs:242 (AsNoTracking + split/single-query read primitives) |
| 13 | Observability & Operability | 2 | 4 | 8 | 8/16 | Full OTel (structured logs + RED metrics + distributed tracing) to OTLP and App Insights, liveness/readiness/dependency health checks driving orchestration, correlation IDs, deliberate poll-span noise filtering for cost. Enriched (v1.84.0/ADR-025): a warm-up readiness gate (WarmupReadinessHealthCheck, tagged ready) holds /health/ready unhealthy until startup warm-up has run, so the platform keeps traffic off a warming replica: wired unconditionally into AddServiceDefaults, free for every host, with 9 unit tests. ↓ Implementation 9→8 (twentieth wave, band recalibration, no work lost): two of the six §13 criteria (SLO alerting/dashboards, runbooks) remain the deployer's responsibility, which is the rubric's Strong band ("nearly all criteria met, minor isolated gaps"), not the Exemplary "all criteria met to a high standard": the same deferred-to-consumer calibration §17 and §29 already carry. Maturity holds at 4 (the readiness/health/OTel wiring is baked in, not opt-in, and unit-tested). Evidence: Aspire/Extensions.cs:38 (AddWarmupReadiness unconditional in AddServiceDefaults), :173 (Outbox/Cqrs meters), :188 (OutboxPollFilterProcessor), :302,306,315 (/health, /alive, /health/ready gate), :344,352 (OTLP + Azure Monitor exporters); Aspire/Warmup/WarmupReadinessHealthCheck.cs:9 + Tests/Hosting/MMCA.Common.Aspire.Tests/Warmup/*Tests.cs (9 tests); Application/UseCases/Decorators/LoggingQueryDecorator.cs:39 (C-3: query business-failures record outcome=failed, RED-metric parity with the command decorator, 2026-07-05) |
| 14 | Testability & Test Strategy | 3 | 4 | 9 | 12/27 | Right-shaped pyramid (1880 [Fact]/[Theory] across 262 files, weighted to Core); architecture fitness functions enforce dependency/purity/extraction/versioning/PII/specification/slice-cohesion/localization rules, authored once in the shipped MMCA.Common.Testing.Architecture package as 85 methods across 28 abstract bases (ADR-015) and re-run as thin subclasses (Common's own build executes 49 of them, per FACTS.md); plus shared test infra is itself a shipped package, and a zero-discovery min-tests guard gates every merge. Recently added (2026-06-30): a translation-coverage fitness gate (LocalizationResourceTests) and a render-snapshot regression tier (PrimitivesSnapshotTests, 5 baselines) via the shipped MarkupSnapshot helper. Prior waves added permission-authz, fixed-TimeProvider, and PiiRedactorTests. CI enforces a 68.3% line-coverage floor that fails the build (unit tier, generated code excluded; ratcheted from 53% by the 2026-07 coverage program, measured ~70.3%). ↑ Implementation 8→9 (sixteenth wave): the fourteenth wave capped this at 8 on a modest 53% coverage floor; that floor is now 68.3% and the suite grew ~300 tests via a coverage-driven program that found and fixed seven real defects (C-1..C-7), so the top band is now supported. Held below 10 by the one remaining Exemplary gap, no mutation testing on the Core tier. Maturity holds at 4 (CI-gated fitness tests + blocking coverage floor + zero-discovery min-tests guard). — Common's own Tests/Architecture/MMCA.Common.Architecture.Tests/{LayerDependency,DomainPurity,MicroserviceExtraction,DependencyVersion,PiiConvention,AggregateConvention,EventVersioningConvention,LocalizationResource,FrameworkSanity}Tests.cs + SpecificationFitnessTests.cs; Tests/Presentation/MMCA.Common.UI.Tests/Components/PrimitivesSnapshotTests.cs; Tests/Presentation/MMCA.Common.UI.Web.Tests/ (Wave C4); .github/workflows/ci.yml:226 (68.3% coverage floor, ratcheted b75fa8f), :63 (min-tests guard) |
| 15 | Best Practices & Code Quality | 2 | 4 | 9 | 8/18 | Five analyzers at error + warnings-as-errors + lock files + NuGetAudit=all, consistent Result-based error handling, narrowly-scoped justified suppressions rather than blanket pragmas, ADRs capturing the why. 9 not 10 because a few NoWarn entries plus one dated audit-suppress trim rigor at the edges. — Directory.Build.props:7-12 (TWAE, lock files, NuGetAudit, audit=all), :52-71 (Meziantou/VS.Threading/Roslynator/Sonar/StyleCop at error); one NuGetAuditSuppress (GHSA-2m69-gcr7-jv3q / CVE-2025-6965 SQLite, justified+dated 2026-06-19) |
| 16 | Maintainability & Evolvability | 2 | 4 | 8 | 8/16 | Low-coupling/high-cohesion enforced by dual layer gates + extraction fitness functions, explicit versioned package contracts (15 packages), documented upgrade-all-consumers-together policy (no divergent versions), current ADRs (001-048) and a source-generated, CI-drift-gated FACTS.md. Held at 8 because the architecture map (ArchitecturalAnalysis.md) lives in the un-versionable workspace root (this repo carries the in-repo RemediationBacklog.md tech-debt register, but the map cannot be committed here). — Directory.Build.props:87-88; LayerDependencyTests.cs; release.yml (MinVer git-tag versioning); build/facts/FactsGenerator.cs + .github/workflows/ci.yml:27-28 (FACTS generated + drift-gated); ADRs/README.md kept current with supersession dates |
| 17 | DevOps & Deployment | 2 | 3 | 8 | 6/16 | CI gates build+analyzers+tests+vuln-audit+coverage-floor (PR + push) and release automates pack/SBOM/publish on tags with a least-privilege token. Now ships an in-repo reference deployment sample: samples/deployment/{foundation,main}.bicep (Container Apps + ACR-via-managed-identity + Key Vault + SQL + cost tags + budget) + DEPLOYMENT.md (OIDC federated-credential setup, UAMI bootstrap, post-deploy smoke-gate/auto-rollback), closing the "no .bicep/.tf anywhere" gap with the reference the scorecard's own remediation named. The sample is now kept continuously valid in CI (2026-07-16): a sample-deployment-validate job compile-checks both templates with az bicep build on every push/PR (blocking), replacing the former point-in-time lint-clean claim. Maturity holds at 3 (twentieth wave, lift proposal refuted): the validate job is a compile check, and the workflow's own comment states a real what-if/deploy stays a consumer-side concern, so the CD/IaC-apply axis is not automatically enforced in-repo. Held at 8 (not 9): validation is not new deployment execution; it is a reference sample, not a self-deploy (a library can't deploy itself), and full CD machinery lives in consumer repos. Evidence: samples/deployment/; .github/workflows/ci.yml:309-322 (sample-deployment-validate, compile-only by design); .github/workflows/release.yml:14,39-60 |
| 18 | UI Architecture & Components | 3 | 4 | 9 | 12/27 | Clean container/presentational split: reusable smart DataGridListPageBase<TDto> + dumb parameterized primitives composed via RenderFragment slots + EventCallback, shared across both consumer apps, gated by bUnit + a gallery render-smoke job. No 1000-line blobs / logic in markup. — MMCA.Common.UI/Pages/Common/DataGridListPageBase.cs:18; Components/{EmptyState,PageHeader,PageLoadingState,PageErrorState,DeleteConfirmation}.razor; Components/MobileInfiniteScrollList.razor:19,49-52 (typed [Parameter,EditorRequired], EventCallback, @key) |
| 19 | State Management & Data Flow | 3 | 4 | 8 | 12/24 | One explicit owner per state, all per-circuit scoped (no static mutable cross-user state), unidirectional flow via params/EventCallbacks, StateHasChanged marshalled via InvokeAsync, the wrapped-guard stale-IsDirty red flag remediated via IsDirtyAccessor, and the disposed-CTS race fixed (swap-fresh-CTS-before-dispose + catch (ObjectDisposedException), shipped v1.68.0). ↓ Implementation 9→8 (thirteenth wave): recalibrated to Strong because the server-vs-client-state caching/staleness criterion is only partially deliberate (fresh state is polled/refetched rather than layered behind an explicit client-side staleness policy); all other §19 criteria are met with no red flags. Maturity holds at 4 (the guard and disposed-CTS behaviors are bUnit-tested in the CI-gated .slnx). — MMCA.Common.UI/DependencyInjection.cs:70-79 (TryAddScoped scoped-state registrations); Services/Notifications/NotificationState.cs:8 (per-circuit scoped state); Components/UnsavedChangesGuard.razor:32-34 (IsDirtyAccessor/CurrentIsDirty); DataGridListPageBase.cs:553-570 (disposed-CTS swap-before-dispose); Tests/Presentation/MMCA.Common.UI.Tests/Components/UnsavedChangesGuardTests.cs:17 |
| 20 | Design System & UI Consistency | 2 | 4 | 9 | 8/18 | MudBlazor used consistently with a centralized theme/token system, light+dark variants, CI-enforced drift guard binding CSS tokens to the C# source of truth; library quirks fixed once in shared wrappers (grid paging guardrail). The dark palette is now user-toggleable (ADR-028): MudThemeProvider @bind-IsDarkMode driven by a ThemeService (cookie + localStorage + User.PreferredTheme, OS-default), with a ThemeToggle in the shared MainLayout beside the culture switcher (ADR-028 notes the no-flash SSR theme bootstrap is not yet wired). ↑ Implementation 8→9 (eighteenth wave): one of the two deductions that capped this at 8 is closed: the dark palette's filled-primary button label and error-alert text WCAG AA contrast failures are fixed (dark PrimaryContrastText/ErrorContrastText = rgba(0,0,0,0.87)) and locked by a blocking dark-mode axe gate (DarkModeE2ETests in the required chromium ui-e2e job, resolved 2026-07-11). Held below 10 by the second, still-open deduction: some app.css blocks use !important overrides and raw hex outside the token set, and Bootstrap chrome still coexists with MudBlazor. Light mode was already fully AA-gated. Evidence: Theme/MMCATheme.cs:60,73 (dark PrimaryContrastText/ErrorContrastText), :13,45 (light + dark palette); Theme/BrandColors.cs:10-19 (C# SSOT + BrandColorTokenTests.cs:38); Tests/Presentation/MMCA.Common.UI.E2E.Tests/DarkModeE2ETests.cs:30 + .github/workflows/ci.yml:114 (dark-mode axe gate, chromium blocking); ACCESSIBILITY.md:49 (contrast RESOLVED 2026-07-11); UI/Components/ThemeToggle.razor + MainLayout.razor (ADR-028); wwwroot/app.css raw hex/!important (e.g. :122 ⚠ minor) |
| 21 | Accessibility (a11y) | 3 | 4 | 9 | 12/27 | Semantic landmarks, real keyboard-operable controls, ARIA names, stated WCAG 2.1 AA target enforced by a required chromium axe gate in CI covering the loading (named progressbar) and error (alert) component states + shipped E2E a11y bases + a documented manual screen-reader pass (ACCESSIBILITY.md: landmarks/focus-order/ARIA-names/form-error association). Broadening the scan (tenth wave) found and fixed a real WCAG 4.1.2 defect: PageLoadingState wrapped an anonymous progressbar in a bare <div> carrying a prohibited aria-label; now role="status" + a named spinner. ↑ Implementation 8→9 (eighteenth wave): the two documented, deliberately un-gated dark-theme WCAG AA contrast failures that capped this at 8 in the seventeenth wave (filled-primary button label + error-alert message text) are now fixed (dark PrimaryContrastText/ErrorContrastText = rgba(0,0,0,0.87)) and the dark-mode axe scan is a blocking gate (DarkModeE2ETests in the required chromium ui-e2e job), exactly the path back to 9 the prior row named, shipped 2026-07-11. Held below 10 by the standing smaller gaps: no automated focus-trap/reading-order assertion (manual pass only), and axe breadth scoped to the gallery's representative states (deep consumer states scored downstream). Maturity holds at 4 on the required chromium axe merge gate. Evidence: Layout/MainLayout.razor:22,33,88 (skip-nav + role="main" + role="alert"); Components/PageLoadingState.razor:3 (role=status + named progressbar); Theme/MMCATheme.cs:60,73 (dark contrast fix); Tests/Presentation/MMCA.Common.UI.E2E.Tests/DarkModeE2ETests.cs:30 (dark-mode WCAG 2.1 AA axe gate); ACCESSIBILITY.md:44-57 (light+dark AA pass + known limitations: manual-only focus-trap/reading-order); .github/workflows/ci.yml:103-114 (ui-e2e matrix, chromium blocking); Testing.E2E/Infrastructure/AxeOptions.cs:22 (WCAG 2.1 AA tag set); Tests/Presentation/MMCA.Common.UI.E2E.Tests/ComponentsPageE2ETests.cs:82 (loading/error-state axe coverage) |
| 22 | Responsive & Cross-Browser | 2 | 4 | 9 | 8/18 | Fluid MudBlazor breakpoints, explicit grid→card mobile alternative, a 3-engine cross-browser matrix in CI, density options on DataGridListPageBase (a DenseGrid property + ToggleDensity() round-tripping through ListPageState: URL key d, in-memory, sessionStorage; unit-tested in ListPageStateServiceTests/ListPageQueryStateServiceTests), the shared .mmca-touch-target 48px affordance (cart drawer + mobile cards + data-grid pager, enforced by a phone-viewport Playwright bounding-box test), and a documented RESPONSIVE.md device/breakpoint matrix (all twelfth-wave Implementation-8 evidence, unchanged). ↑ Maturity 3→4 (nineteenth wave): firefox was promoted to a required merge gate alongside chromium (2026-07-12), clearing the chromium-only red flag. ↑ Implementation 8→9 (twentieth wave): webkit was promoted to a required merge gate on 2026-07-16 after 11 consecutive green main runs, so all three engines now block merges (confirmed live via the branch-protection API: the chromium, firefox, and webkit ui-e2e contexts are all in required_status_checks), closing the single stated hold-at-8 reason ("webkit remains advisory") and completing the 3-engine matrix as an enforced capability. Held below 10: the matrix runs against the backend-less gallery's representative states, and RESPONSIVE.md's device rows are a documented matrix, not device-lab execution. (Doc drift cured with this refresh: RESPONSIVE.md still listed webkit as advisory.) Evidence: .github/workflows/ci.yml:111-114 (3-engine matrix, all required merge gates; webkit promotion note 2026-07-16; no continue-on-error in the job); MMCA.Common.UI/Pages/Common/DataGridListPageBase.cs (DenseGrid/ToggleDensity) + Services/ListPageStateService.cs/ListPageQueryStateService.cs (DenseGrid persisted); wwwroot/app.css:148 (.mmca-touch-target shared rule); RESPONSIVE.md (breakpoint + browser matrix); Tests/Presentation/MMCA.Common.UI.E2E.Tests/ComponentsPageE2ETests.cs (touch-target viewport test); Common/BreakpointConstants.cs:16 (<960px mobile) |
| 23 | Front-End Performance | 2 | 4 | 8 | 8/16 | Server-side paging (no load-everything-then-page-in-memory), cancellation of superseded fetches, SSR persist/restore to kill a redundant round-trip, bounded infinite-scroll DOM (MaxRenderedItems cap), @key-stable rendering, skeleton/loading states. ↑ Maturity 3→4 (eighteenth wave, applied to this table on the nineteenth): the conventions the thirteenth wave recalibrated to review-enforced are now measured AND automatically enforced: WebVitalsE2ETests asserts LCP/TTFB/CLS budgets on the gallery Login + Components pages (via the shipped WebVitalsCollector, no longer unwired) inside the required blocking chromium ui-e2e merge gate, meeting the rubric's maturity-4 "enforced automatically (CI)" bar. (The eighteenth-wave prose recorded this move but the table row was never updated; this refresh cures that inconsistency.) Implementation holds at 8: desktop MudDataGrid still uses server paging rather than row virtualization, the one retained render-efficiency gap. Evidence: Tests/Presentation/MMCA.Common.UI.E2E.Tests/WebVitalsE2ETests.cs:43 (LCP/TTFB/CLS budget assertions); .github/workflows/ci.yml:105-115 (blocking chromium ui-e2e job); DataGridListPageBase.cs:403-409 (ServerData + ResetCancellationTokenAsync), :109-138,413-428 (PersistentComponentState); MobileInfiniteScrollList.razor:61,97 (MaxRenderedItems cap, IntersectionObserver) |
| 24 | Forms, Validation & UX Safety | 2 | 4 | 8 | 8/16 | Unsaved-changes guard with reliable current-dirty reads, submit/loading/error/empty states, destructive-action confirmation, genuine client/server password-rule parity — now with field-level error association: Register/Login are EditForm + DataAnnotationsValidator + per-field ValidationMessage over typed models (RegisterModel/LoginModel, PasswordComplexity attribute mirroring the server rule), with the MudAlert kept for form-level/server errors. This closes the prior red flag (a generic summary not tied to the offending input). ↑ Maturity 3→4 (fourteenth wave): the shared auth-form convention is enforced automatically in the CI-gated .slnx unit tier (AuthModelValidationTests, 8 facts covering required/email/complexity/password-match; RegisterFormTests asserting the per-field ValidationMessage renders on an empty submit and the auth service is never invoked), meeting the rubric's maturity-4 "enforced automatically by tests/CI" bar, consistent with §19's M4 on the same CI-gated guard tests. (The shared FormsConventionTestsBase stays consumer-scoped: it enumerates Source/Modules/**/*Create.razor, absent here, and Common has no subclass, so the in-repo enforcement rests on these bUnit/model tests.) Held at 8 (not 9): only the shared auth forms live here; consumer module forms are scored downstream. — Pages/Auth/{Register,Login}.razor (EditForm + For= per-field) + {RegisterModel,LoginModel,PasswordComplexityAttribute}.cs; Tests/.../Pages/Auth/{AuthModelValidationTests,RegisterFormTests}.cs; Components/UnsavedChangesGuard.razor |
| 25 | Navigation & Information Arch | 2 | 4 | 8 | 8/16 | Route-level auth via AuthorizeRouteView (not just UI hiding), role/claim-filtered navigation, focus management on navigate, query-state preservation, and both 404 and 403 handled in the app shell: a dedicated Forbidden (403) page renders for the authenticated-but-unauthorized branch (was a bare MudAlert), alongside the existing NotFound. A NavigationFlow.md documents the Common UI surface's routes + role/claim model (mirroring ADC's). ↑ Maturity 3→4 (twentieth wave): the navigation-contract drift gate the prior risk list prescribed now ships and gates merges: NavigationContractTests reflects over every routable MMCA.Common.UI page (RouteAttribute/AuthorizeAttribute) and asserts route set-equality plus auth-posture agreement against the embedded NavigationFlow.md routes table, with a non-vacuous 8-route floor; the test project is in MMCA.Common.slnx, so the gate runs in the CI-gated unit tier, meeting the rubric maturity-4 "enforced automatically (CI)" bar and closing the §25 red flag (navigation drifting from the documented per-role design). Implementation holds at 8 (not 9): the gate is an enforcement move, not new execution breadth, and deep-link param typing beyond list-state is still light (framework routes are plain string templates, only the sanitized ?returnUrl= query state), the same calibration §22/§23 applied to their gate promotions. Evidence: Tests/Architecture/MMCA.Common.Architecture.Tests/NavigationContractTests.cs:29,44 (set-equality + auth-posture drift gate) + MMCA.Common.Architecture.Tests.csproj:12 (NavigationFlow.md embedded resource); MMCA.Common.slnx:45 (gate in the CI-gated unit tier); Pages/Forbidden.razor + Routes.razor:11 (AuthorizeRouteView: NotAuthorized→<Forbidden/>, NotFoundPage); NavigationFlow.md:14; Components/RedirectToLogin.razor (ReturnUrlProtector); Tests/.../Pages/ForbiddenTests.cs |
| 26 | Front-End Security | 3 | 4 | 8 | 12/24 | No unsanitized HTML/eval (grep MarkupString|innerHTML|eval over MMCA.Common.UI = zero), centralized hardened security-headers middleware with a pluggable ICspPolicyProvider CSP seam (now ADR-023-documented, replacing per-host hand-rolled headers), unit-tested open-redirect protection, explicit "client untrusted / server re-validates" posture, no client-shipped secrets, and access tokens held in-memory with the refresh token in an HttpOnly cookie (no localStorage; the comments in ITokenStorageService explicitly state tokens are NOT stored there — the earlier "localStorage" finding was a stale doc artifact, corrected in commit 363861d). The SSR token-storage scheme (HttpOnly access/refresh cookies seeded at login, an SSR-time non-validating read handler for [Authorize] on fresh GETs, refresh-never-leaves-the-server) is now documented in ADR-022 (a BFF-style design whose security argument rests on the API remaining the validation boundary). ↓ Implementation 9→8 (thirteenth wave): the single documented gap (the default static CSP deliberately omits script-src/style-src, a safe-but-partial baseline until an HTML host registers its own provider) fits the rubric's Strong-band "minor, isolated gap" rather than an Exemplary "all criteria met to a high standard", so Implementation 8 is the more rubric-consistent read of the same evidence the prior 9 hedged. Maturity holds at 4 (the hardened CSP baseline + HSTS/CSP behavior are unit-tested). — Aspire/Security/SecurityHeaders.cs:46-47 (default CSP default-src 'self'/object-src 'none'/frame-ancestors 'none'), :32-35,128 (HSTS), :64-68 (ICspPolicyProvider seam) + :157-177 (AddCommonSecurityHeaders, TryAddSingleton default → first-registered provider wins), Tests/Hosting/MMCA.Common.Aspire.Tests/Security/SecurityHeadersMiddlewareTests.cs, ADR-023; ITokenStorageService.cs:5-6; Services/Navigation/ReturnUrlProtector.cs:18-60 + tests |
| 27 | Internationalization (i18n) | 1 | 4 | 9 | 4/9 | ↑ Maturity 3→4 + Implementation 8→9 (fifteenth wave, i18n completion train, ADR-027 Decision 9). Multi-locale i18n (en-US + Spanish) ships in the framework itself and default-on: co-located .resx + IStringLocalizer<T>, backend errors localized server-side at the edge keyed by the existing Error.Code (English Message as fallback), one culture cookie as the source of truth across SSR/Server/WASM (forwarded to services as Accept-Language), and a User.PreferredCulture persisted per user with a CultureSwitcher in the shared layout. Maturity is now Optimized because ALL of it is machine-enforced: the translation-coverage gate (LocalizationResourceTests, now with a non-vacuous MinimumBaseResources = 3 floor) fails the build on a missing/incomplete es sibling; the NEW literal gate (LocalizedTextConventionTests, a LocalizedTextConventionTestsBase subclass) fails the build on hard-coded snackbar/title/<PageTitle>/breadcrumb/NavItem literals; MA0076 culture-less formatting is a build error; and the pseudo-localization pass is a REQUIRED CI gate, not a diagnostic (PseudoLocalizationE2ETests in the chromium ui-e2e merge gate renders /login, /register, /components under qps-Ploc asserting the [!! sentinel round-trip AND no horizontal overflow under ~40% expansion, the rubric's layout-tolerance criterion, plus an en-US leak guard). Implementation 9: the framework's own chrome is fully externalized (SharedResource 136 keys en+es: NavMenu, auth pages, error/empty/loading states, ReconnectModal, notification pages, UI.Web SSR Error page), MudBlazor built-in chrome localizes via ResxMudLocalizer over MudTranslations.{resx,es.resx} (all 145 MudBlazor 9.6.0 keys, DI-resolution-tested), NavItem.TitleResource makes module nav menus culture-aware, the ErrorMessages.Success fragment-concatenation red flag is [Obsolete] (whole-sentence page keys), and Common.Error.* templates no longer surface raw ex.Message. Held below 10: two locales, no RTL. Evidence: Tests/Presentation/MMCA.Common.UI.E2E.Tests/PseudoLocalizationE2ETests.cs; Tests/Architecture/MMCA.Common.Architecture.Tests/{LocalizedTextConventionTests,LocalizationResourceTests}.cs; Source/Hosting/MMCA.Common.Testing.Architecture/{ArchitectureRules.LocalizedText.cs,Bases/LocalizedTextConventionTestsBase.cs}; Source/Presentation/MMCA.Common.UI/Globalization/ResxMudLocalizer.cs + Resources/MudTranslations.{resx,es.resx} + DependencyInjection.cs (TryAddTransient + Pseudo decorator); Pages/Common/ErrorMessages.cs ([Obsolete] Success, sanitized templates); Common/NavItem.cs (TitleResource); Tests/Presentation/MMCA.Common.UI.Gallery/GalleryHost.cs (test-only qps-Ploc allowlist); ADRs/027-multi-locale-i18n.md §8/§9 |
| 28 | Front-End Testing & Quality | 3 | 4 | 9 | 12/27 | Right-shaped pyramid (many bUnit component + service unit tests, fewer Playwright E2E), a shipped shared E2E package reused across apps, axe WCAG 2.1 AA + render-smoke as a real-browser CI merge gate against a self-hosted gallery, robust role/test-id selectors with auto-wait (no sleep-flakiness), per-test trace artifacts. ↑ Implementation 8→9 (2026-06-30): the prior "no visual-regression layer" gap is closed by a render-snapshot (golden-markup) regression tier (MarkupSnapshot, shipped in Testing.UI for consumer reuse) that normalizes per-render MudBlazor GUIDs and diffs the markup of shared primitives against committed baselines, failing the build on an unintended structural change. It is deterministic and OS-independent (markup, not pixels), so it runs in the in-solution unit tier on every platform with no per-platform golden management. Held below 10: it is a markup-snapshot, not pixel visual-regression, and E2E journeys still center on Identity/auth. — Source/Hosting/MMCA.Common.Testing.UI/Infrastructure/MarkupSnapshot.cs; Tests/Presentation/MMCA.Common.UI.Tests/Components/PrimitivesSnapshotTests.cs + Snapshots/*.html; Testing.E2E/** (PageObjects, abstract *TestsBase); Infrastructure/PageExtensions.cs:113-132,185-210 (FillAndVerify auto-wait + axe); .github/workflows/ci.yml:72-135 (ui-e2e gate, chromium blocking) |
| 29 | Resilience & Business Continuity | 3 | 4 | 8 | 12/24 | Failure-isolation is strong and fitness-enforced (timeouts/retries/circuit breaker on every outbound client + outbox graceful degradation), the chaos/fault-injection test trips a breaker and asserts BrokenCircuit short-circuiting, the warm-up/readiness gate is unit-tested (gate latch/idempotency/thread-safety, opens even on task failure), and OpenIdConnectMetadataWarmupTask pre-warms OIDC to kill ACA cold-start. ↑ Maturity 3→4 (2026-06-30): the in-repo restore drill (DatabaseRestoreDrillTests: seed → backup → catastrophic wipe → restore → verify on ephemeral SQLite) runs on every CI build in the unit tier — a build gate, not a scheduled cron (the distinction that keeps a non-gating drill at M3) — and RESILIENCE.md records the framework's measured restore baseline (~5 ms median RTO over 5 runs, 0-row RPO byte-for-byte asserted) alongside the SLO/error-budget template. The recovery procedure is thus demonstrated, measured, and automatically enforced in-repo, meeting the M4 "enforced automatically by tests/CI" bar. Implementation held at 8: production RTO/RPO against real cloud backups + measured production SLOs remain consumer IaC. — Tests/Core/MMCA.Common.Infrastructure.Tests/Resilience/DatabaseRestoreDrillTests.cs; RESILIENCE.md (measured baseline + SLO template); Aspire/Warmup/ + Tests/Hosting/MMCA.Common.Aspire.Tests/Warmup/*Tests.cs; Tests/.../ResilienceCircuitBreakerFaultInjectionTests.cs:17-61 (ADR-009/025) |
| 30 | Compliance, Privacy & Governance | 2 | 3 | 8 | 6/16 | Directly answers the headline §30 red flag (soft-delete-as-only-deletion-path) with an explicit erasure seam, AES-256-GCM encryption, bounded retention, and a build-gating PII fitness function — now also closing the second §30 red flag ("PII in logs/telemetry"): the shipped, tested PiiRedactor (v1.84.0) masks every [Pii]-marked member (shallow, value-erasing) before an entity carrying personal data is written to a structured log or telemetry attribute, the redaction half of the [Pii] contract the docs previously only advertised. The PII guard is no longer vacuous: a PiiErasureContractFitnessTests build gate now forces a [Pii]-marked DataSubjectSample through PiiRedactor + IAnonymizable end-to-end, closing the prior cycle's "no fitness function forces types through the redactor" blocker. Maturity held at 3 (not lifted to 4) after review: that gate verifies the erasure mechanism rather than gating a repo-wide convention (the structural PiiConventionTests scan is still vacuous — no PII-bearing type lives in Common's Domain), and the bulk of §30 governance — the personal-data inventory, export/access (DSAR) endpoints, residency verification, consent capture, and retention execution — is consumer-resident, so the category as a whole is not automatically enforced (the rubric's M4 bar). Same "deeper substance deferred" calibration as §11. — ADR-005; Domain/Privacy/PiiRedactor.cs:24-142 ([REDACTED] token, per-type reflection cache); Tests/Core/MMCA.Common.Domain.Tests/Privacy/PiiRedactorTests.cs (7 [Fact]s, incl. "never emits the clear-text PII values"); Tests/Architecture/.../PiiErasureContractFitnessTests.cs:19-40 (forces a [Pii] type through redactor + anonymize); Domain/Interfaces/IAnonymizable.cs:22-31 (idempotent Result-based Anonymize); Infrastructure/Persistence/Encryption/EncryptedStringConverter.cs:31,34 (AES-256-GCM, 12B nonce/16B tag, now ADR-037-documented; shipped but unadopted/latent, no production column encrypted) |
| 31 | Cost Efficiency / FinOps | 2 | 2 | 7 | 4/14 | Real design-level cost awareness on the telemetry/poll-interval axis (OutboxPollFilterProcessor + idle-vCPU socket tuning + a released COST.md) — now with two real in-framework execution levers, not just docs: a built-in head-based OTel trace sampler knob (Telemetry:TracesSampleRatio → ParentBasedSampler(TraceIdRatioBasedSampler), unit-tested, the largest trace-ingestion lever) and the outbox per-message "dispatched" log moved Info→Debug (caps the highest-volume log line), plus COST.md cost-attribution-tag + cost-guard-workflow samples. Held at 7 (not 8): right-sizing, reversible scale-events, and per-service cost attribution remain consumer/IaC execution a library cannot perform. — Aspire/Extensions.cs (TracesSampleRatio sampler) + Tests/.../Telemetry/TracesSampleRatioTests.cs; Infrastructure/.../Outbox/OutboxProcessor.cs (LogMessageProcessed=Debug); COST.md; Aspire/Telemetry/OutboxPollFilterProcessor.cs:34-45 |
| 32 | Dependency & Supply-Chain | 3 | 4 | 9 | 12/27 | Hits essentially every §32 criterion for a published framework: central pinned versions, 26 lock files + audit=all gated in CI across feeds, source-mapped trusted feed, SemVer via MinVer, CycloneDX SBOM a hard release gate, Apache-2.0 license (patent grant; committed LICENSE file), and — uniquely — a fitness function (ADR-016) turning the licensing-driven MassTransit-v8 pin into a build gate so the 'blanket update reintroduces known-bad dependency' red flag cannot recur. Now reinforced by a committed .github/dependabot.yml (weekly NuGet + Actions PRs) carrying version-update:semver-major ignores for every held package (MassTransit, VS.Threading.Analyzers, StackExchange.Redis, MessagePack) — so the pin discipline lives in config, not only in the test — plus a periodic sweep to latest stable. Minor gap only: the coordinated consumer sweep is policy, not automated. — Directory.Packages.props:41-43 (MassTransit 8.5.5 pinned + v9 commercial-license-crash note :36-40); Directory.Build.props:8-10; Tests/Architecture/.../DependencyVersionTests.cs + base DependencyVersionTestsBase.cs:24-37 (fails if MassTransit major≥9); .github/dependabot.yml:24-35 (held-package semver-major ignores); release.yml:45-50 (SBOM, blocking); 26 packages.lock.json; ADR-016; ADR-038 (supply-chain provenance) records the layered posture in one decision record, noting the SBOM is generated but not yet signed or attested (a possible follow-up, so I9 holds rather than moving to 10) |
| 33 | Developer Experience & Inner Loop | 2 | 4 | 9 | 8/18 | Strong inner loop: documented, gitignored cross-repo source override (the rubric's explicit local.props example), Aspire one-command run for consumers, one-obvious-command build/test/pack, analyzer/editorconfig parity local-to-CI, and a 1025-line, 9-phase GETTING-STARTED.md adoption guide (Phase 0 prerequisites → Phase 8 extract-a-module) with MMCA.Helpdesk as a worked, runnable companion. ↑ Maturity 3→4 + Implementation 8→9 (twentieth wave): the consumer-source-build canary (builds MMCA.Helpdesk against the PR's framework source via UseLocalMMCA) was promoted from advisory to a required merge gate on 2026-07-16: continue-on-error removed and the "Consumer source build (Helpdesk)" context added to branch protection after 9 consecutive green runs (ci.yml:267-268 promotion note; confirmed live via the branch-protection API and CONTRIBUTING.md:62). A framework change that breaks a source-mode consumer now blocks the merge automatically, clearing both the maturity-4 automatic-enforcement bar and the sole advisory-canary cap the nineteenth wave placed on Exemplary implementation. Held below 10 by residual friction, now implementation notes only: the Aspire AppHost hangs when launched headless (must be run interactively) and Common itself is a library, not independently runnable. (Noted, non-blocking: CONTRIBUTING.md:104-124's branch-protection reproduction snippet is stale, still treating the canary as not-yet-promoted and omitting webkit.) Evidence: .github/workflows/ci.yml:267-303 (required cross-repo canary, no continue-on-error); CONTRIBUTING.md:62 (required-checks list incl. "Consumer source build (Helpdesk)"); MMCA.Common/GETTING-STARTED.md (1025 lines, 9 phases); workspace CLAUDE.md (local.props.template → UseLocalMMCA → ../MMCA.Common/Source, gitignored); MMCA.Common/CLAUDE.md (build/test/pack/single-test/arch-test commands) |
| 34 | Architecture Governance & Docs | 2 | 4 | 8 | 8/16 | Reference-quality governance: forty-eight ADRs (001-048, incl. ADR-015 architecture fitness functions, ADR-016 lockstep versioning + MassTransit pin, ADR-017 request idempotency, ADR-018 polyglot persistence, ADR-019 layered rate-limiting, ADR-020 permission-based authorization, ADR-021 consumer-inbox idempotency, ADR-022 browser session-cookie auth, ADR-023 centralized security-response headers, ADR-024 two-channel notifications, ADR-025 startup warm-up + readiness gating, ADR-026 two-tier caching strategy, ADR-027 multi-locale i18n (supersedes 011), ADR-028 day/dark theme, ADR-029 brute-force protection, ADR-030 startup sole-migrator, ADR-031 feature-flag management, ADR-032 password hashing, ADR-033 resource-ownership authorization, ADR-034 generic entity controllers + dynamic query contract, ADR-035 optimistic concurrency, ADR-036 external OAuth login, ADR-037 field-level encryption at rest, ADR-038 supply-chain provenance, ADR-039 live channel push, ADR-040 authenticated output caching, ADR-041 observability and telemetry, ADR-042 MAUI device-capability abstraction, ADR-043 mobile deep links + native OAuth callback, ADR-044 native push delivery, ADR-045 managed file storage + avatars, ADR-046 HTTP API versioning, ADR-047 soft-deleted user session revocation, ADR-048 primitive identifier type aliases) with index + authoring guide, each non-obvious decision matched to a fitness function; architecture rules enforced automatically twice over (compile-time MSBuild target + a now-shared NetArchTest rule library, ADR-015) plus dependency-version/PII/aggregate/event-version/specification/slice-cohesion fitness functions; a 1025-line GETTING-STARTED.md + current CLAUDE.md + ArchitecturalAnalysis map matching the code. FACTS.md is generated from source by a dependency-free build/facts tool and CI drift-gated (a --check step fails the build when the committed facts drift), so version / package-count / ADR-range / fitness counts are a computed-and-gated artifact rather than hand-maintained prose; the rubric (ArchitectureEvaluationCriteria.md) and FACTS.md are version-controlled in this repo, completing the §34 pattern already applied to the ADRs and this two-axis scorecard. ↓ Implementation 9→8 (thirteenth wave), held at 8 (fourteenth wave, re-confirmed nineteenth): Strong, not Exemplary, held down by the one structural residual: ArchitecturalAnalysis.md lives in the uncommittable workspace root (the root can't be versioned). Transient caveat at the nineteenth-wave re-score: the tree is dirty again (ADRs 046-048 untracked plus a modified FACTS.md, an ADR-adding pass in flight), the recurring per-cycle hygiene nit tracked in the backlog; the sixteenth-wave adversarial precedent stands (a between-refresh prose lag is transient and cured by each refresh, and the durable cap is the uncommittable map, not the lag). Evidence: ADRs/001-048 (Accepted/dated; README index + 'Writing a new ADR' guide); build/facts/FactsGenerator.cs + .github/workflows/ci.yml:27-28 (FACTS generated + --check drift gate) + FACTS.md; ArchitectureEvaluationCriteria.md (rubric in-repo); Source/Build/MMCA.Common.LayerEnforcement.targets AND Tests/Architecture/.../*Tests.cs (subclassing Source/Hosting/MMCA.Common.Testing.Architecture); GETTING-STARTED.md |
Weighted column = Maturity·weight / Implementation·weight per row. Axis-gap findings: §17/§8 are mature-but-execution-deferred (mechanism shipped, deeper proof lives downstream); §13 joined that calibration on the twentieth wave (Implementation 8: alerting/dashboards/runbooks are deployer-owned); §29 closed its maturity gap (the restore drill is now a build gate with a measured baseline) but its Implementation 8 still defers production SLOs downstream; §30/§31 are the inverse (in-repo implementation leads a maturity capped by consumer-owned governance/cost process); §12 left that group this cycle when its perf gate became a required check, and now sits mature-but-execution-deferred like §17/§8 (load and stress timing against realistic volumes is a consumer-app concern). Together these are the structural source of the 96.9% vs 84.6% gap.
Indices
- Maturity index = Σ(maturity×weight) ÷ Σ(weight×4) = 314 ÷ 324 = 96.9% (unchanged on the twenty-second-wave re-score, 2026-07-23: no maturity score moved; two proposed lifts, §9 and §30 Maturity 3→4, were refuted on adversarial re-verification. The latest move remains the twenty-first wave's §12 Maturity 3→4: 312 + 2 = 314.)
- Implementation index = Σ(impl×weight) ÷ Σ(weight×10) = 685 ÷ 810 = 84.6% (unchanged: no Implementation score moved on the twenty-second-wave re-score; one proposed lift (§10 8→9) and two proposed downgrades (§23 and §24, each 8→7) were refuted on adversarial re-verification and held at prior.)
- Weaker axis: Implementation (execution quality), by ~12.3 points
- N/A (excluded from denominators): none (§27 Internationalization is scored: ADR-027 superseded the single-locale ADR-011, so all 34 categories count)
- §32 weight = 3 (raised from the default 2 because MMCA.Common is a published framework)
Top 5 strengths
Dual-enforced Clean Architecture dependency rule (compile-time + fitness functions) — §3 (impl 9):
Source/Build/MMCA.Common.LayerEnforcement.targets:1-90fails the build on layer violation (BeforeTargets ResolveProjectReferences);LayerDependencyTests.cs:12-58andDomainPurityTests.cs:13-26re-enforce Shared→Domain→Application→Infra→API/UI and Domain framework-freedom via NetArchTest. Reinforced by §34 (impl 8).- Remediation: Preserve by keeping both gates wired into CI and adding a regression test whenever a new layer/package is introduced; document the targets file in CLAUDE.md. To push the residual point, narrow the broader base-controller/UoW surfaces noted in §1.
- Expected delta: Preserves +9 impl on §3/§34; protects §16 maintainability from regression.
Reference-quality test strategy — define-once fitness functions + an enforced coverage gate — §14 (impl 9): right-shaped pyramid (1880
[Fact]/[Theory]across 262 files) with executable architecture fitness functions (LayerDependency, DomainPurity, MicroserviceExtraction, DependencyVersion, PiiConvention, PiiErasureContract, AggregateConvention, EventVersioningConvention, FrameworkSanity, Specification, SliceCohesion) authored once in the shippedMMCA.Common.Testing.Architecturepackage (85 methods across 28 abstract bases) and re-run identically across all three repos (ADR-015), a zero-discovery min-tests guard, a shipped reusable E2E package, and a 68.3% line-coverage floor that fails the build (ci.yml:226, ratcheted from 53% by the 2026-07 coverage program, measured ~70.3%).- Remediation: Preserve by keeping the floor green and ratcheting it upward over releases; add mutation testing on the Core tier to reach a 10.
- Expected delta: Holds §14 at impl 9; mutation testing on the Core tier is the remaining path toward 10; protects §28.
Supply-chain hardening with a license-pin build gate, a blocking SBOM, and Dependabot held-package guards — §32 (impl 9, weight 3):
Directory.Build.props:8-10(lock files, NuGetAudit, audit=all);Directory.Packages.props:41-43(MassTransit 8.5.5 pinned with documented v9 commercial-license crash hazard, ADR-016);DependencyVersionTestsBase.cs:24-37fails the build if any MassTransit package goes major≥9; 24packages.lock.json; CycloneDX SBOM a hard release gate (release.yml:45-50); and a committed.github/dependabot.yml:24-35withsemver-majorignores on every held package — the pin discipline now lives in config as well as the test.- Remediation: Automate the coordinated consumer version sweep (currently policy) to close the one remaining minor gap.
- Expected delta: Automating the sweep nudges §32 maturity toward fully-exemplary; preserves +9 impl.
Centralized, alg-pinned security with shipped anti-automation defenses and PBKDF2 hashing — §11 (impl 8):
WebApplicationBuilderExtensions.cs:364,380pins ValidAlgorithms per RS256/HS256 (defeats alg-confusion) with full issuer/audience/lifetime/signing-key validation,:217extends the RS256 pin to the JWKS-forwarded path;:34rate-limit bypass for health/.well-known/grpc;LoginProtectionService.cs:50-56,74-82adds exponential brute-force lockout + per-IP registration limit (ADR-029);PasswordHasher.cs:24,52,58hashes with PBKDF2-HMAC-SHA512 (600k iterations, legacy-salt verify+migrate,FixedTimeEquals, ADR-032) — all returningResult.Failurerather than throwing.- Remediation: Keep the CI vulnerable-package gate green; document the deployer's vault/managed-identity binding (the acknowledged gap holding impl at 8) with a reference wiring sample.
- Expected delta: A documented MI binding sample nudges §11 toward 9 and supports §17/§26.
Architecture-test-enforced microservices extraction seams + event versioning — §7 (impl 9): ADR-006 (database-per-service), transport-agnostic
IMessageBus.cs:28, and the sharedArchitectureRules.Transport.cs:19rule (subclassed inMicroserviceExtractionTests.cs:10) forbidding Application/Domain/Shared→MassTransit and Grpc→Domain/Application/Infrastructure. Newly reinforced by §6'sBaseIntegrationEvent.SchemaVersion(ADR-010) +EventVersioningConventionTests, so cross-service contracts now carry an explicit version.- Remediation: Demonstrate one end-to-end lift-out (the Notifications module) in a sample to convert 'extractable by design' into 'extracted and proven'; add an upcaster/compat policy to ADR-010.
- Expected delta: Preserves +9 impl on §7; an upcaster policy lifts §6 toward 10.
Top 5 risks
Note (twenty-first wave, v1.121.0): earlier waves closed risks previously listed here (§29's restore drill, the §27 i18n train, §24 forms enforcement, §22's firefox gate, §23's WebVitals gate, §25's navigation drift gate), and this wave closes another: §12's perf gate is now a required check on
main, so the category returns to the protect list and its risk entry is retired. A documentation-drift risk entered in its place and was closed the same day:CONTRIBUTING.mdstill advertised the pre-promotion gate list, and because scorecard adjudications cite that file, its staleness mis-grounded §12's own score for a full cycle (fixed in MMCA.Common PR #100,658786b; kept in the list with its resolution for provenance). The remaining deeper execution each risk names (per-service cost attribution, CD-to-Azure, a CI migration-apply step, consumer-side privacy governance) still lives downstream.
FinOps execution and governance live downstream — §31 (impl 7, maturity 2, the lowest maturity on the board; a documented accepted cap per the backlog's Deliberate / accepted section): the framework now ships real cost levers (an OTel
Telemetry:TracesSampleRatiosampler knob, the outbox per-message log trimmed to Debug,OutboxPollFilterProcessor, aCOST.mdwith attribution-tag + cost-guard samples), but right-sizing, reversible scale-events, and per-service cost attribution are consumer/IaC concerns a library cannot perform, and no in-repo process/review enforces cost discipline — so maturity stays at 2.- Remediation: This is largely structural; the in-repo half is done. Further movement needs the consumer apps to demonstrate measured right-sizing + per-service cost attribution that the framework's knobs feed.
- Expected delta: +1 maturity on §31 (2→3) once a consumer-side cost-attribution convention exercises the framework knobs.
API contract stability has no in-repo automatic enforcement: §9 (impl 9, maturity 3, weight 2). The contract substance is genuinely strong (centralized RFC 9457 ProblemDetails mapping, header-based versioning with a backward-compatible v1.0 default, the ADR-034 generic query contract, OpenAPI + Scalar UI helpers), but the OpenAPI/contract drift gate is deliberately consumer-owned (
OpenApiEndpointExtensions.cs:13states the framework does not duplicate the consumer-host snapshot gate), and no §9-specific fitness function or CI job exists in-repo, so contract discipline is enforced by review and convention, the rubric's Maturity-3 band. Unlike every other Maturity-4 category here, §9 has no category-specific automatic gate. (§25, previously listed here, closed this wave on theNavigationContractTestsdrift gate.)- Remediation: Either add a minimal in-repo contract-surface fitness check over the framework-owned pieces (e.g. assert the ProblemDetails status map and the version-header default against a committed expectation), or record the consumer delegation as a deliberate accepted cap in the backlog so the category stops reading as an open gap.
- Expected delta: +1 maturity on §9 (3→4) if gated in-repo; alternatively a documented acceptance mirroring §31's treatment.
Deployment and data-migration gating verified only downstream — §17 DevOps (impl 8: a reference
samples/deploymentBicep/OIDC sample +DEPLOYMENT.mdnow ship and lint clean, but a library can't self-deploy — real CD-to-Azure lives in consumers) and §8 Data (impl 8: migration application is policy-driven — services self-migrate at boot under ADR-030, but that is runtime, not a verifiable CI migration-apply step here). These remain mature mechanisms whose verifiable execution sits downstream — much of the residual axis gap.- Remediation: Add a CI step that applies/validates migrations against an ephemeral DB (closes the §8 gate) and a smoke-deploy of the
samples/deploymentsample (raises §17 toward proven-not-just-referenced). - Expected delta: +1 impl each on §8/§17; closes more of the Implementation-axis gap.
- Remediation: Add a CI step that applies/validates migrations against an ephemeral DB (closes the §8 gate) and a smoke-deploy of the
Compliance governance is consumer-owned; the in-repo guard verifies a mechanism, not the category — §30 (impl 8, maturity 3, weight 2): the headline §30 red flags are closed in-framework (the
IAnonymizableerasure seam, AES-256-GCM at rest, and the shipped+testedPiiRedactormasking[Pii]members before logs/telemetry), andPiiErasureContractFitnessTestsnow forces a[Pii]type throughPiiRedactor+IAnonymizableso the erasure contract is no longer un-gated. But that gate verifies the mechanism rather than gating a repo-wide convention (the structuralPiiConventionTestsscan is still vacuous — no PII-bearing type lives in Common's Domain), and the governing process — the personal-data inventory, DSAR/export endpoints, consent capture, residency verification, retention execution — is delegated to consumers. Maturity held at 3 (reviewed this cycle): the category as a whole is not automatically enforced.- Remediation: The in-repo mechanism is done; further maturity movement needs the consumer apps to carry the personal-data inventory + DSAR/consent/residency process the framework's
[Pii]/IAnonymizableseams feed (documented in ADR-005). - Expected delta: +1 maturity on §30 (3→4) is a consumer-side governance lift, not an in-repo one; the framework half is complete.
- Remediation: The in-repo mechanism is done; further maturity movement needs the consumer apps to carry the personal-data inventory + DSAR/consent/residency process the framework's
The documented merge-gate list has drifted from live branch protection, and adjudications cite itRESOLVED 2026-07-21, same day it was raised (MMCA.Common PR #100,658786b): the gate list and the reproduce snippet now name all eight live contexts, verified byte-identical against the protection API, and the file now directs readers to that API as authoritative over the committed copy. Recorded here for provenance; the finding as raised read: (§34 / §16, both impl 8)CONTRIBUTING.md:57-64lists five required gates and calls webkit advisory, and its reproduce snippet (CONTRIBUTING.md:104-112,124) omits webkit, the Helpdesk consumer-source canary, and the perf gate, while liverequired_status_checksonmaincarries all eight contexts (branch-protection API, this run;ci.yml:116-118already asserts all three browser engines are required). This is the rubric's "stale docs" red flag with a governance edge: the twentieth wave held §12 at Maturity 3 partly on this file's authority, and the score was only corrected a cycle later.- Remediation: Done. The gate list and reproduce snippet were synced and the API is now named as authoritative; the backlog item under #34 is closed with the proving reference.
- Expected delta: no index move, as expected; it protects future scores from being mis-grounded and removes a §16/§34 stale-docs deduction. §34 holds M4/I8 this cycle; the next re-score can weigh the fix on fresh evidence.
Cross-repo comparison
How this repo's quality relates to the other consumers — where the framework's quality propagates, where a repo diverges or under-uses it, and where a consumer does better than the shared baseline — is maintained once, for all three repos, in ../Docs/Architecture/CrossRepoComparison.md (it is inherently cross-repo and would only drift if copied into each scorecard).