Architecture governance
MMCA.Common — Architecture Remediation Backlog
Derived from ArchitectureScorecard.md (canonical two-axis scoring: Maturity 96.9% / Implementation 84.6%, framework v1.123.0; the 2026-07-23 twenty-second-wave two-pass re-score at HEAD c911480, working tree clean, moved no scores: all 34 categories re-confirmed at their twenty-first-wave values. Five first-pass proposals were refuted on adversarial re-verification and held at prior: three lifts (§9 Maturity 3→4, §10 Implementation 8→9, §30 Maturity 3→4) and two downgrades (§23 and §24, each Implementation 8→7). Categories still below Maturity 4: #9, #17, #30 (M3) and #31 (M2), so 30 of 34 sit at Maturity 4. The prior twenty-first-wave entry, retained for provenance: the 2026-07-21 re-score at HEAD 4a4fc05 moved exactly one score, §12 Performance & Scalability Maturity 3→4, because 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 basis for holding it at 3; seven first-pass lifts (§4, §9, §11, §19, §20, §26, §29) were refuted and the remaining 26 categories re-confirmed.)
The wave-by-wave priority ranking below is the historical single-axis review (index 80%, 218/272, 2026-06-08/09); it is retained for provenance and is superseded by the in-repo two-axis scorecard, which is the live source of scores.
Tasks are every applicable category scoring < 4, ranked by priority = (4 − score) × weight.
Higher priority = bigger weighted gap = more index points per unit of effort.
Scope: the single-axis item counts below are historical (the wave-by-wave ranking is superseded by the two-axis scorecard, per the note above). Under the live two-axis scorecard there are no N/A categories (§27 i18n is now scored after ADR-027) and 30 categories sit at Maturity 4 (#1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34; #22 and #23 joined 2026-07-15 on the nineteenth-wave re-score, #25 and #33 joined 2026-07-17 on the twentieth-wave re-score, and #12 rejoined 2026-07-21 on the twenty-first-wave re-score when its perf gate became a required check); the open work is the 4 categories still below Maturity 4. Ranked by two-axis priority = (4 − maturity) × weight: #31 FinOps (Maturity 2, weight 2, computed priority 4) is the highest weighted gap but a documented accepted cap (see Deliberate / accepted below), not scheduled work; then the weight-2 Maturity-3 pack at priority 2 (#9, #17, #30).
Two fixes each clear multiple items — do them once:
- MassTransit v8 guard closes the medium red flags in #32 and #16.
- bUnit component tests lift #28 and #18 (and cover the #19 guard bug).
Progress — first wave (2026-06-08)
Implemented in MMCA.Common — ✅ verified 2026-06-09: dotnet build -c Release is clean (0 warnings / 0 errors, all analyzers) and all 9 test projects pass (~1,611 tests, 0 failures), including 28 architecture tests (3 new MassTransit fitness cases) and 90 UI tests (6 new bUnit tests). No GITHUB_TOKEN is needed — MMCA.Common restores entirely from nuget.org.
- ✅ #32 / #16 — MassTransit v8 fitness test.
DependencyVersionTestsparsesDirectory.Packages.propsand fails if the MassTransit major hits 9. Remaining in #32: lock files, SBOM, CHANGELOG/versioning policy. - ✅ #29 / #6 — broker retry policy.
ConfigureBrokerTransportappliesUseMessageRetry(exponential) on both RabbitMQ and Azure Service Bus, configurable via newMessageBusSettings.RetryLimit/RetryMinIntervalSeconds/RetryMaxIntervalSeconds;IntegrationEventConsumercomment + log corrected. Delayed redelivery deliberately omitted (needs the RabbitMQ delayed-exchange plugin absent from the Aspire container). Remaining in #29: RTO/RPO, restore drill, alerting. Remaining in #6: consumer inbox/dedup + event Id. - 🟡 #28 / #18 — bUnit harness. Added
bunit(pinned 2.0.66 — v2BunitContext/Renderfor xUnit v3),BunitTestBase(MudServices + loose JSInterop), and 6 passing tests forEmptyState+MobileCardList. Remaining: MobileInfiniteScrollList + UnsavedChangesGuard tests, axe-core a11y, E2E-in-CI. - ✅ #30 — compliance seam (partial).
IAnonymizableerasure seam (Domain),OutboxCleanupServicepurging processed rows older thanOutbox:RetentionDays(default 7), and ADR-005. Remaining (consumer-side): make PII entitiesIAnonymizable, add erasure/DSR + export endpoints, stop logging PII.
Progress — second wave (2026-06-09)
✅ Verified: dotnet build -c Release clean (0/0) and all 9 test projects pass (1,511 tests, 0 failures).
- ✅ #32 / #16 — supply-chain. NuGet lock files (
RestorePackagesWithLockFile, 20 committed),nuget.configpackageSourceMapping (*→nuget.org), CycloneDX SBOM step inrelease.yml, CHANGELOG.md + VERSIONING.md (SemVer + breaking-change + consumer-sweep policy). With the Wave-1 fitness test, #32 and #16 reach 4. - ✅ #11 — security. CI vuln-audit gate (
dotnet list package --vulnerable+NuGetAudit=all) and SECURITY.md (security model, OWASP note, consumer responsibilities). Item 13 (NetArchTest security invariants) deferred to consumer suites — infeasible as NetArchTest, and the framework's CORS / anonymous-endpoints are already correct. - ✅ #13 — observability.
AddMeter("MMCA.Common.Outbox")(dead-letter counter now exported) + CQRS RED histograms (cqrs.command/query.duration, tagged by name + outcome) viaCqrsMetrics, registered in AspireWithMetrics. - ✅ #17 — DevOps.
.github/dependabot.yml(nuget + actions, MassTransit-major ignored); symbols switched to embedded (orphansnupkgremoved — verified viadotnet pack). - ✅ #34 — governance. Refreshed the stale DB-per-service passages in
Docs/Architecture/ArchitecturalAnalysis.md; added ADR-006 (database-per-service) + ADR-007 (gRPC extraction) + ADRs/README.md index. - ✅ #9 — contracts (partial). Corrected the
ServiceContractAttributedoc (no longer claims a framework test that doesn't exist; enforcement is the consumer's). OpenAPI generation deferred.
Progress — third wave (front-end, 2026-06-09)
✅ Verified: build clean (0/0) and all 9 test projects pass (1,519 tests, 0 failures); UI tests 90 → 98 (8 new bUnit tests).
- ✅ #19 — UnsavedChangesGuard live-accessor. Added optional
Func<bool>? IsDirtyAccessor; the guard reads current dirty state at navigation time (CurrentIsDirty), fixing the one-render param-lag foot-gun. Additive/non-breaking; covered by bUnit tests. - ✅ #23 — MobileInfiniteScrollList cap. New
MaxRenderedItems(default 500) bounds DOM growth — infinite scroll stops fetching at the cap. (Virtualizewould conflict with the IntersectionObserver loader.) Covered by a bUnit test. - ✅ #28 / #18 — bUnit coverage. Added tests for
MobileInfiniteScrollList,UnsavedChangesGuard, and thePageError/PageLoading/PageHeaderprimitives. - ✅ #20 — design-system (partial). Collapsed the duplicated
#1565C0brand hex to the--mmca-primary/--mmca-primary-darkCSS vars (single CSS source) + a sync note inMMCATheme. Bootstrap→MudBlazor NavMenu chrome migration deferred (riskier). - ✅ #28 / #5 — axe-core a11y. Added
Deque.AxeCore.Playwright(4.7.2) + aPage.AssertNoAccessibilityViolationsAsync()helper to the shipped E2E package (compiles here; the assertion runs in consumer E2E flows).
Deferred (no host / larger / low value): browser-journey-in-Common-CI (Common is a library — no app to run E2E against), the Bootstrap NavMenu migration, and the EditorRequired convention check.
Progress — fourth wave (breaking changes + consumer sweep, 2026-06-09)
✅ Verified across all three repos (built/tested via local.props against Common source — no token): Common 1,523, ADC 1,241, Store 1,088 tests, 0 failures; all CI solutions build 0/0.
- ✅ #16 —
UserNotification.Create→Result<UserNotification>(Common-internal; 4 call sites updated — no consumer code calls it). - ✅ #4 / #15 — aggregate-factory fitness test.
AggregateConventionTestsreflects over the Domain assembly asserting each aggregate root has a staticCreatereturningResult<T>. Cross-aggregate-nav rule deliberately omitted (navigation-populator pattern, ADR-002). Consumers' 15 aggregates already comply. - ✅ #6 / #19 — consumer-side idempotency (inbox).
MessageIdonBaseDomainEvent/IDomainEvent;InboxMessageentity + EF config;IInboxStore(EfInboxStore/NoOpInboxStore) with dedup inIntegrationEventConsumer; opt-inMessageBus:EnableInbox(default off);OutboxCleanupServicealso purges processed inbox rows. Unit-tested. - ✅ 5 EF migrations (
AddInboxMessages): ADC Identity/Conference/Engagement/Notification (per-service DBs) + Store (shared) — each createsInboxMessages+ the uniqueMessageIdindex, generated against Common source.
Remaining (manual/opt-in): set MessageBus:EnableInbox=true per service once its migration is applied; optionally mirror the Result-return fitness assertion into ADC/Store EntityConventionTests (multi-assembly; they already comply). Publishing Common + bumping consumers off local.props is a release step (needs the feed/token).
Progress — v1.80.0 (2026-06-26)
The single-axis backlog above is from the 2026-06-08/09 review (index 80%). The framework has since reached v1.82.0 and the canonical scoring was the in-repo, two-axis
ArchitectureScorecard.md(Maturity 92.2% / Implementation 82.9% at that wave — current: 92.8% / 85.0%, see the scorecard header). This entry records what shipped and the remaining framework-side follow-ups; it does not re-derive the single-axis priority ranking above.
- ✅ #11 / #1 — permission-based authorization (opt-in).
IPermissionRegistry/PermissionRegistryBuilder(Shared) +[HasPermission]/PermissionPolicyProvider/PermissionAuthorizationHandler(API), wired viaAddAuthorizationPolicies+AddPermissions, backward-compatible with the named role policies; 13 unit tests. Adopted by ADC (≈20 endpoints +RoleNames.ContentEditor). RBAC-with-capability-indirection (policy-based, not resource/attribute-based). - ✅ #14 / #1 —
TimeProvideradoption. Injected intoTokenService(tokeniat/nbf/exp) and the notification read handlers;UserNotification.MarkAsRead(DateTime readOnUtc)now takes an explicit UTC timestamp. RegisteredTimeProvider.Systemsingleton. - ✅ #34 — ADR-019 (layered rate limiting) documents the pre-existing authenticated-only global limiter; ADRs 017/018 committed; ADR set now 001-019.
Framework-side follow-ups:
- ✅ Rate-limiter partition/exemption tests (#11/§ADR-019).
IsRateLimitBypassed/GlobalRateLimitPartitionare nowinternal(viaInternalsVisibleTo) andRateLimitPartitionTestscovers the bypass paths, anonymous-vs-authenticated branching, and the per-user partition-key fallback (name → user_id → IP → constant). (2026-06-26) - ✅ Controlled-clock notification handler tests (#14). Both mark-as-read handler tests now inject a
fixed
TimeProviderand assert the stampedUserNotification.ReadOn. (2026-06-26) - ✅
BaseDomainEvent.DateOccurredambient clock — accepted as deliberate, not removed (#4). A domain event's occurrence instant is the moment the aggregate raises it, so the creation-time default is the correct event-sourcing / audit semantic (and four domain tests enforce it). Relocating the stamp to the SaveChanges boundary would shift occurrence-time → persist-time and regress that semantic; threading a clock through every aggregate is disproportionate. Documented as a deliberate choice inBaseDomainEventrather than changed. (decision 2026-06-26)
Progress — v1.81.0/v1.82.0 + governance pass (2026-06-26)
Released since v1.80.0 (v1.81.0, v1.82.0) plus a sixth governance pass currently in flight (uncommitted). All of it lands in categories already scored 9-10, so the two-axis indices were unchanged at that wave (Maturity 92.2% / Implementation 82.9%; current: 92.8% / 85.0%); these are evidence/governance enrichments, not score-movers.
- ✅ #9 — Scalar OpenAPI UI (opt-in, released v1.81.0).
MapCommonScalarUi()renders/scalar/{doc}from the generated document, non-Production only, via the bundledScalar.AspNetCore 2.16.6(no CDN). The committed-baseline drift gate stays deliberately consumer-owned (the API surface lives in the consumer hosts). §9 impl held at 9. - ✅ #31 —
COST.mdFinOps note (released v1.81.0). Consolidates the framework's cost levers (telemetry poll-span filtering, outbox poll/retention tuning) and the right-sizing / attribution / surge-revert defaults consumers set. Doc enrichment; §31 impl held at 6 (execution is consumer/IaC). - ✅ #11 / #26 — RS256 pinned on the JWKS-forwarded auth path (v1.82.0).
ValidAlgorithms = [RsaSha256]on the forwarded-JWT validation path, matching the in-process pin. - ✅ #11 / #26 — security-response headers centralized (ADR-023, uncommitted pass). One pluggable
middleware in
MMCA.Common.Aspire.Security(AddCommonSecurityHeaders+ICspPolicyProvider+SecurityHeadersMiddleware, unit-tested) replaces per-host hand-rolled headers. §11/§26 impl held at 9 (default static CSP deliberately omitsscript-src/style-srcuntil a host registers a provider). - ✅ #34 / #16 — FACTS.md now generated + CI drift-gated (uncommitted pass).
build/factscomputes the framework facts from source;ci.yml:27-28runsdotnet run --project build/facts -- . --checkas a drift gate, so version / package count / ADR range / fitness counts can no longer drift. The rubric (ArchitectureEvaluationCriteria.md) andFACTS.mdare now version-controlled in-repo. ADR set now 001-023 (ADR-023 added). §34 impl held at 9 (residual: ArchitecturalAnalysis.md in the uncommittable workspace root; plus this pass is mid-commit).
Open follow-up surfaced this cycle (governance hygiene, not a score-mover):
- Commit the sixth governance pass — ADR-023, the source-generated CI-drift-gated
FACTS.md+build/facts, the in-repo rubric, and this two-axis scorecard all shipped in v1.83.0 (b9a6a28), resolving the prior cycle's "ADR-023 uncommitted" §34 caveat. (Done 2026-06-27.) - [~] Backfill the CHANGELOG and commit the docs pass. Partly addressed, superseded by the
v1.85.0 follow-up below: a
[1.85.0]CHANGELOG entry was added (commitf224595), but v1.83.0 and v1.84.0 still have no release notes and the v1.85.0 docs governance pass (ADRs 024/025/026, theFACTS.mdADR-count bump, ADR cross-links, this scorecard/backlog) is still uncommitted. Tracked now under "Progress — v1.85.0 → Open follow-up". (§34, transient hygiene nit, effort S.)
Progress — v1.83.0/v1.84.0 (2026-06-27)
Released since v1.82.0 (v1.83.0, v1.84.0) plus a docs-only governance pass currently in flight (uncommitted). One score moved at this wave: §30 Implementation 7→8. The canonical scoring at v1.84.0 was Maturity 92.2% / Implementation 83.1% (was 82.9%) per the in-repo
ArchitectureScorecard.md; the v1.85.0 eighth wave below then took it to 92.8% / 85.0%.
- ✅ #30 —
PiiRedactorlog-masking shipped (v1.84.0, score 7→8).Domain/Privacy/PiiRedactor.csmasks every[Pii]-marked member (shallow, value-erasing,[REDACTED]token, per-type reflection cache) before an entity carrying personal data reaches a structured log or telemetry attribute — closing the §30 red flag the rubric names verbatim ("PII in logs/telemetry"), previously documented-but-missing. Covered by 7PiiRedactorTests(incl. "never emits the clear-text PII values"). §30 maturity holds at 3: DSAR/export endpoints, consent capture, the personal-data inventory, residency verification, and retention execution stay consumer-owned, andPiiConventionTestsstill passes vacuously in-repo (no PII-carrying type lives here; no fitness function forces types through the redactor). - ✅ #34 — sixth governance pass committed (v1.83.0). ADR-023 (security-response headers), the
source-generated CI-drift-gated
FACTS.md+build/facts, the in-repo rubric, and the two-axis scorecard all shipped, resolving the prior "ADR-023 uncommitted" caveat. §34 holds at M4/I9. - ✅ #13 / #29 — warm-up / readiness subsystem documented (ADR-025).
WarmupHostedService+WarmupReadinessGate+OpenIdConnectMetadataWarmupTask(wired intoAddServiceDefaults) gate/health/readyuntil startup warm-up runs, holding cold replicas out of rotation (gate opens even on task failure = availability over warmth, lazy-retry under ADR-009). Enrichment, not a score move: §13 holds at I9 and §29 holds at 3/7 because the subsystem ships without unit tests and the §29 recovery gaps (restore drill, RTO/RPO, SLOs) are unchanged. (See the new #29 follow-up below.) - ✅ #6 — two-channel notifications documented (ADR-024). The pre-existing SignalR-push + durable
UserNotification-inbox seams (IPushNotificationSender/INotificationRecipientProvider, no-op defaults) are now formally recorded. §6 evidence enriched, no move.
Framework-side follow-ups surfaced this cycle:
- #29 — unit-test the warm-up/readiness subsystem. RESOLVED in the eighth wave (v1.85.0):
Tests/Hosting/MMCA.Common.Aspire.Tests/Warmup/{WarmupReadinessGate,WarmupHostedService,WarmupReadinessHealthCheck}Tests.csnow cover the gate latch/idempotency/thread-safety, the hosted service running eachIWarmupTaskonce + opening the gate even on task failure, and the health-check transitions. This converted "warm-up exists" into "warm-up verified" and lifted §29 Implementation 7→8.
Progress — v1.85.0 (eighth wave: under-8 Implementation remediation, 2026-06-27)
The under-8 Implementation remediation (commit
78e5312, tagv1.85.0, HEAD7082a5f) lifted every category scored Implementation < 8 with shipped, tested in-repo evidence, and additionally moved one maturity score. Re-verified against current source. Canonical scoring is now Maturity 92.8% / Implementation 85.0% per the in-repoArchitectureScorecard.md(was 92.2% / 83.1%). Full Release build clean (0 warnings); 1651 tests pass.
- ✅ #5 — Vertical Slice: Implementation 7→8 AND maturity 3→4.
ArchitectureRules.Slices.cs+SliceCohesionTestsBase(sharedMMCA.Common.Testing.Architecture, the 18th fitness base) + a CommonSliceCohesionTestssubclass fail the build if a use-case slice's handler/validator is stranded from its same-assembly command/query contract. Because this is automatic CI enforcement of the slice convention, §5 now meets the rubric's maturity-4 "enforced automatically by tests/CI" bar (like every other fitness-gated category) — the one maturity move this cycle. §5 moves to the level-4 protect list. - ✅ #12 — Performance: Implementation 7→8.
Tests/Performance/MMCA.Common.Benchmarks(BenchmarkDotNet smoke harness, outside the.slnx) makes hot-path spec efficiency measured, not assumed; the max-page-size guard already shipped at v1.84.0. - ✅ #17 — DevOps: Implementation 7→8. Reference
samples/deployment/{foundation,main}.bicep(Container Apps + ACR-via-managed-identity + Key Vault + SQL + cost tags + budget; lint clean viaaz bicep build) +DEPLOYMENT.md(OIDC federated-credential + UAMI bootstrap + smoke-gate/auto-rollback). Held at 8 — a library can't self-deploy; full CD-to-Azure lives in consumer repos. - ✅ #24 — Forms/Validation: Implementation 7→8. Register/Login converted to
EditForm+DataAnnotationsValidator+ per-fieldValidationMessageover typedRegisterModel/LoginModel(PasswordComplexityAttributemirroring the server rule), closing the "errors not tied to the input" red flag;AuthModelValidationTests+RegisterFormTestscover it. - ✅ #25 — Navigation: Implementation 7→8. In-shell
Pages/Forbidden.razor(403) wired intoRoutes.razor(NotAuthorized→<Forbidden/>) +NavigationFlow.mddocumenting the Common UI route/role model;ForbiddenTestscover it. - ✅ #29 — Resilience: Implementation 7→8. Warm-up subsystem now unit-tested (above) +
RESILIENCE.md(baseline SLO/error-budget template + restore-drill runbook reference). Maturity held at 3 — the drill itself executes in consumer IaC; no in-repo measured RTO/RPO or SLO. - ✅ #31 — FinOps: Implementation 6→7. OTel
Telemetry:TracesSampleRatio→ParentBasedSampler(TraceIdRatioBasedSampler)knob (unit-tested, the biggest trace-ingestion lever) + outbox per-message log moved Information→Debug +COST.mdcost-attribution-tag/cost-guard samples. Maturity held at 2 — right-sizing/attribution/reversible-scale is consumer/IaC. - ✅ #9 / #34 —
ServiceContractAttributedoc-comment corrected. It no longer claims a dedicated[ServiceContract]architecture test exists in each consumer solution; it now states the contract-purity invariant is upheld by the transport/layer-purity fitness rules (ADR-015) and that the attribute is an available documentation marker no contract type carries yet — closing the long-standing #9 "documents a test that doesn't exist" sub-item (§9 already impl 9, no score move). - ✅ #10 / #34 — ADR-026 (two-tier caching strategy) added. Documents the
ICacheServicesubstrate (startup-time memory-or-distributed swap viaAddCaching) + the HTTP output-cache edge, and the TTL-backstopped best-effort prefix invalidation — formalizing pre-existing §10 code (no score move).
Open follow-up surfaced this cycle (governance hygiene, not a score-mover):
- Commit the v1.85.0 docs governance pass + backfill the CHANGELOG. ADRs 024/025/026 are
untracked, the
FACTS.mdADR-count bump (23→26) + ADR-003/004/005/010/015 cross-links + theServiceContractAttributedoc-fix are modified, and this scorecard/backlog refresh is uncommitted. The CHANGELOG now carries a[1.85.0]entry but still lacks v1.83.0 and v1.84.0 sections (and[Unreleased]is empty), so those two releases have no notes. Add the 1.83.0 + 1.84.0 CHANGELOG sections and commit the docs pass so §34 traceability is consistent again. (§34, transient hygiene nit, effort S.)
Progress — v1.86.0→v1.92.0 (ninth wave: i18n + re-score, 2026-06-29)
Re-scored against current source at framework v1.92.0 (HEAD
93ffcac, dirty tree). Canonical scoring is now Maturity 91.7% / Implementation 84.1% (was 92.8% / 85.0%) per the in-repoArchitectureScorecard.md. Five scores moved: one new category (§27), one offsetting maturity regression (§23), and three closer-evidence recalibrations (§11, §22, §30-reviewed). Both indices dip slightly — honest re-calibration plus a newly-scored immature category, not regressed work.
- ➕ #27 — i18n flipped N/A → Maturity 2 / Implementation 6 (NEW open item). 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>, edge error localization keyed byError.Code, a culture cookie forwarded asAccept-Language, andUser.PreferredCulture. The last N/A category is now scored, so all 34 count. Gap (the freshest in-repo gap, weight 1, priority 2): no missing-key/translation-coverage CI gate, no pseudo-localization pass, culture-less formatting guarded only by an advisory analyzer (MA0076). (See the Priority-2 #27 item below.) —Shared/Globalization/SupportedCultures.cs:18;API/Localization/ErrorResourceSource.cs+*.es.resx;UI/Components/CultureSwitcher.razor. - 🔻 #11 — Security Implementation 9→8 (recalibration; still Maturity 4). "Strong", not "Exemplary":
vault/managed-identity secret binding is deployer-owned and authz is RBAC-with-capability-indirection,
not resource/attribute-based. Enriched this wave (no further move): ADR-032 PBKDF2-HMAC-SHA512 password
hashing (
PasswordHasher.cs, 600k iterations + legacy-salt migrate +FixedTimeEquals, 11 tests) and ADR-029 brute-force protection now documented. - 🔻 #22 — Responsive Implementation 8→7 (recalibration). Cross-browser gate is chromium-only (firefox/webkit advisory), the 48px touch-target rule is cart-drawer-scoped, no density options. Already tracked consumer-assessed; no new item.
- 🔻 #23 — Front-End Performance Maturity 4→3 (recalibration). The patterns are convention/review-enforced, not automatically gated or measured (no Core Web Vitals/Lighthouse anywhere). Already an open Priority-2 item (#23); the regression aligns the backlog with reality.
- ◐ #29 — broker retry sub-items now CLOSE.
ConfigureBrokerTransportappliescfg.UseMessageRetry(exponential) on both RabbitMQ (DependencyInjection.cs:432) and Azure Service Bus (:449); theIntegrationEventConsumercomment + the doc-comment are corrected. The Priority-3 #29 descriptive text ("noUseMessageRetry") is drifted and corrected below.UseDelayedRedeliverystays deliberately omitted (DependencyInjection.cs:408, accepted). Category #29 itself stays open at Maturity 3 on the unchanged recovery gaps (no in-repo RTO/RPO, drilled restore, SLOs). - ◐ #30 — PII erasure contract now gated; Maturity held at 3 (reviewed). A new
PiiErasureContractFitnessTestsbuild gate forces a[Pii]DataSubjectSamplethroughPiiRedactor+IAnonymizable(Tests/Architecture/.../PiiErasureContractFitnessTests.cs:19-40), closing the prior "vacuous PII guard" sub-item. Maturity was reviewed and held at 3 (not lifted to 4): the gate verifies the erasure mechanism, but the structuralPiiConventionTestsscan is still vacuous (no PII-bearing type in Common's Domain) and the broad §30 governance (DSAR/consent/residency/retention/inventory) is consumer-resident. See the #30 clarification below. - ✅ Evidence enrichment, no score move: ADR-028 day/dark theme (§20 — wired toggle, raw-hex/
!importantdeductions hold), ADR-030 startup sole-migrator (§8/§17 — runtime self-migration, not the CI migration-apply gate those gaps name), ADR-031 feature-flag management (§10). ADR set grew 026→032;FACTS.mdfitness counts advanced (71 methods/18 bases, Common runs 38).
Open follow-up surfaced this cycle (governance hygiene, not a score-mover):
- Commit the v1.86.0→v1.92.0 docs/source pass. DONE 2026-06-30 (commit
5321aee): ADR-032 + the modified ADRs 001/007/008/017/020/022/030 +ADRs/README.md+FACTS.md+ theWebApplicationExtensions.csrate-limiter-ordering edit committed; §34 traceability consistent again.
Progress — tenth wave (focused in-repo remediation, 2026-06-30)
Four scores moved up on shipped, tested in-repo evidence; both indices rose for the first time in several waves: Maturity 91.7% → 92.9% (301/324), Implementation 84.1% → 84.9% (688/810). Full Release build clean (0/0); 1670 unit/arch/bUnit tests + 12 chromium E2E pass. Commits
21fbdf9(§27),c04f456(§29),a28ce98(§28),fbb463b(§21).
- ✅ #27 — i18n: Maturity 2→3, Implementation 6→7. Closes the two ADR-027 §7 follow-ups the scorecard
named. (a) Translation-coverage fitness gate:
ResourceTranslationsAreComplete(sharedMMCA.Common.Testing.Architecture, the 19th fitness base) run asLocalizationResourceTestsagainstSupportedCultures.Allfails the build if any base.resxunderSource/lacks a complete, non-empty sibling for a required culture, so coverage is verified not assumed. (b) Culture-less formatting is now a build gate:MA0076raisedsuggestion→errorin.editorconfig; the 33 surfaced sites (validation messages, gRPC error details, UI log/notification text, tests) now use explicitInvariantCulture. ADR-027 §7/§8 document both gates plus a locale-addition governance step. Held below M4: no pseudo-localization pass, only two locales. - ✅ #29 — Resilience: Maturity 3→4. The in-repo restore drill (
DatabaseRestoreDrillTests) runs on every CI build (a build gate in the unit tier, NOT a non-gating scheduled cron, which is the standard that keeps a scheduled drill at M3), andRESILIENCE.mdnow records the framework's measured restore baseline (~5 ms median RTO over 5 runs, 0-row RPO byte-for-byte asserted). The recovery procedure is thus demonstrated, measured, and automatically enforced in-repo, meeting the M4 bar. Implementation held at 8: production RTO/RPO against real cloud backups + measured production SLOs stay consumer IaC. - ✅ #28 — Front-End Testing: Implementation 8→9. Closes "no visual-regression layer" with a
render-snapshot (golden-markup) regression tier:
MarkupSnapshot(shipped inTesting.UIfor consumer reuse) normalizes per-render MudBlazor GUIDs and diffs shared-primitive markup against committed baselines (PrimitivesSnapshotTests, 5 baselines), failing the build on an unintended structural change. Deterministic and OS-independent (markup, not pixels), so it runs in the in-solution unit tier on every CI platform with no per-platform golden management (the Windows-dev-box-cannot-produce-Linux-CI-pixel-goldens constraint). - ✅ #21 — Accessibility: Implementation 8→9. Broadened the chromium axe gate to the loading
(named progressbar) and error (alert) component states, and added
ACCESSIBILITY.md(documented manual screen-reader pass: landmarks/focus-order/ARIA-names/form-error association). Broadening the scan found and fixed a real WCAG 4.1.2 defect:PageLoadingStatecarried a prohibitedaria-labelon a bare<div>around an anonymous progressbar (nowrole="status"+ a named spinner).
Open follow-ups surfaced this wave:
- #20 dark-mode palette contrast (Implementation, NEW). The §21 dark-mode axe prototype found the
dark palette's filled-primary button label and error-alert message text fail WCAG AA contrast
(
PaletteDark.Primary/Errorpaired with auto-computed text). Tracked here (documented inACCESSIBILITY.md), deliberately NOT gated yet; tuning the dark palette is the remediation. (§20, M.) RESOLVED 2026-07-11 (remediation wave 1):PaletteDark.PrimaryContrastText/ErrorContrastTextare now dark (rgba(0,0,0,0.87), the Material dark-theme treatment, mirroring the standingWarningContrastTextfix); the dark-mode axe scan is GATED (DarkModeE2ETestsin the blocking chromiumui-e2ejob: Login + Components re-scanned dark, reproduced both failures pre-fix, green post-fix).ACCESSIBILITY.mdknown-limitations updated. This is also the recorded §21 path back to Implementation 9 (re-score at the next cycle). - [~] Release done, sweep noted (deliberate). The tenth wave was released as
v1.93.0(git tag at HEAD3e72bfa;FACTS.mdrecords it). Sweeping all 13 packages into ADC/Store/Helpdesk is the separate, cross-repo step and is not verifiable from this repo (memory records the sweep on 2026-06-30; confirm in each consumer'sDirectory.Packages.props). (§16/§34.)
Progress — eleventh wave (ADR governance, 2026-06-30)
No score moves. A full 34-category evidence re-score at framework v1.93.0 (HEAD
3e72bfa, dirty tree) re-confirmed every category at its tenth-wave value; indices hold at Maturity 92.9% (301/324) / Implementation 84.9% (688/810). The wave records two pre-existing mechanisms as ADRs and syncs the scorecard/FACTS.mdprose; no remediation lever moved.
- ✅ ADR-033 + ADR-034 written (governance, no score move). Both document mechanisms that already ship
in framework code: ADR-033 (resource-ownership authorization) records the
OwnerOrAdminFilter+OwnershipHelperaxis (single-resource 403 on acustomer_id-claim mismatch + an ownershipSpecificationrow-scoping collection queries, one admin bypass; opt-in, claim-trusting, not ABAC, Store-adopted), and ADR-034 (generic entity controllers + dynamic query contract) records theEntityControllerBase/AggregateRootEntityControllerBasegeneric REST surface + OData-lite query contract. ADR-033 is the resource-ownership criterion §11's Implementation-8 cap named, but it stays ownership-not-ABAC + opt-in, so §11 correctly holds at I8 (watch-item, not a lever). - ✅ Scorecard +
FACTS.mdprose synced to ADR set 001-034 and 72 fitness methods / 19 bases (Common runs 39); the stale §16/§34 ADR-count and Top-strength fitness/test counts were corrected.
Open follow-up surfaced this wave (governance hygiene, not a score-mover):
- #34: commit the ADR 033/034 docs pass. The tree is dirty (ADRs 033/034 added; ADRs 015/026/030 +
ADRs/README.md+FACTS.mdmodified) while the scorecard/backlog now reference 001-034, the recurring per-cycle traceability nit, resolves on commit. (§34, effort S; #34 holds M4/I9.) RESOLVED 2026-07-03 (fourteenth wave): the working tree is clean at v1.101.0 (HEAD5e55be2), ADRs through 036 are committed, andFACTS.mdmatches the tag.
Progress — twelfth wave (under-8 Implementation lift, v1.94.0 pending, 2026-06-30)
Two Implementation scores move up, Maturity holds: Implementation 84.9% → 85.3% (691/810), Maturity 92.9% (301/324) unchanged. Full Release build clean, 1685 tests pass. Held for review at this writing (v1.94.0 not yet tagged), so the tree is dirty against v1.93.0.
- #22 · Responsive & Cross-Browser — Implementation 7→8. Closes the two execution gaps the prior 7
named: (a) grid density options now ship on
DataGridListPageBase(DenseGrid+ToggleDensity(), round-tripped throughListPageState/ URL keyd/ sessionStorage, unit-tested inListPageStateServiceTests+ListPageQueryStateServiceTests); (b) the 48px touch target is generalized from the cart-drawer-only rule into a shared.mmca-touch-targetaffordance (cart drawer + mobile cards + data-grid pager), enforced by a phone-viewport Playwright bounding-box test (passes locally, 13/13 UI E2E); (c) aRESPONSIVE.mddevice/breakpoint/browser matrix is documented (closing the "matrix implicit" note), referenced fromCLAUDE.md. Maturity held at 3: firefox/webkit still advisory (chromium-only blocking gate). - #27 · Internationalization · Implementation 7→8. A real pseudo-localization pass ships:
PseudoLocalizer.Transform(accents every letter, ~40% padding, bracket sentinel, preserves{0}placeholders) applied by aPseudoStringLocalizer/PseudoStringLocalizerFactorydecorator overIStringLocalizerFactory(registered inAddUIShared, inert unless the pseudo culture is active), activated by a Development-onlyqps-Plocculture wired intoUseCommonRequestLocalization+MapCultureEndpoint+ theCultureSwitcher, withSupportedCultures.PseudoLocaledeliberately kept out ofAllso the translation-completeness gate is unaffected. Unit-tested inPseudoLocalizationTests. Closes the pseudo-localization gap the prior 7 named. Maturity held at 3: only two locales ship and the pseudo pass is a dev diagnostic, not a CI gate. - #27 · Internationalization · Maturity 3→4 + Implementation 8→9 (fifteenth wave, 2026-07-03, i18n
completion train; ADR-027 Decision 9). Both remaining holds are closed with CI-enforced evidence:
(a) the pseudo pass is now a REQUIRED CI gate (
PseudoLocalizationE2ETestsin the blocking chromiumui-e2ejob:[!!sentinel round-trip + no horizontal overflow under ~40% expansion on/login,/register,/components, plus anen-USleak guard; the gallery host enablesqps-Plocunconditionally as unpackaged test infrastructure); (b) a second fitness gate (LocalizedTextConventionTests, subclassing the new sharedLocalizedTextConventionTestsBase) fails the build on hard-coded snackbar/title/<PageTitle>/breadcrumb/NavItemliterals. Implementation 9: MudBlazor chrome localized (ResxMudLocalizer+MudTranslations.{resx,es.resx}, all 145 built-in keys en+es, DI-resolution-tested); the framework's own chrome fully externalized (SharedResource22→136 keys: NavMenu, auth pages, error/empty/loading states, ReconnectModal, notification pages, UI.Web SSR Error page);ErrorMessages.Successfragment concatenation[Obsolete](whole-sentence page keys);Common.Error.*no longer surfaces rawex.Message;NavItem.TitleResourceculture-aware nav;LocalizationResourceTestsnon-vacuous floor (MinimumBaseResources = 3). Held below 10: two locales, no RTL. §27 joins the protect set at M4/I9. - [accepted] #31 · Cost Efficiency / FinOps — Implementation deliberately capped at 7 (not chased). A
documented structural acceptance, not an open lever: the two unmet §31 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 resource annotations is inert for the hand-written-
main.bicepconsumers (ADC/Store), so even the one library-addressable criterion does not move the score for the actual consumers. The in-repo levers are already shipped (Telemetry:TracesSampleRatiosampler, outbox-log trim to Debug,COST.mdattribution-tag + cost-guard samples). Further movement is a consumer-side lift, not an in-repo one. (§31 holds M2/I7; seeCOST.mdand the §31 scorecard row.)
Deferred follow-up (recorded, not done this wave):
- [~] #22: promote firefox (then webkit) from advisory to a blocking cross-browser gate once observed
reliably green, to lift §22 Maturity 3→4. (
ci.yml:89; effort S, gated on a green streak.) FIREFOX PROMOTED 2026-07-12 (remediation wave 5):ui-e2e'scontinue-on-errornow exempts only webkit; firefox is a required merge gate alongside chromium (observed clean over the recent main-run streak). Webkit stays advisory (2 flaky reds in its last 10 main runs, 2026-07-11 09:59 and 2026-07-12 00:45); promote it once it holds a comparable streak. §22 Maturity 3→4 candidacy recorded for the next re-score (the recorded lever named the firefox promotion as the move, with webkit staged).
Progress - fourteenth wave (clean-tree evidence re-score at v1.101.0, 2026-07-03)
A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.101.0 (HEAD
5e55be2, working tree clean: the recurring uncommitted-docs caveat is closed). Two scores moved. Canonical scoring is now Maturity 94.1% (305/324) / Implementation 83.6% (677/810) per the in-repoArchitectureScorecard.md(was 93.5% / 84.0%). Four further first-pass downgrade proposals were not applied: §25 (I8 to 7) and §34 (M4/I8 to M3/I7) were refuted by the adversarial verifier, and §7/§9/§13 (each I9 to 8, band recalibrations) were declined on review (kept at I9); those rows received evidence corrections only.
- ✅ #24 · Forms, Validation & UX Safety: Maturity 3→4 (CLOSED, moved to the level-4 protect list). The
shared auth-form convention is enforced automatically in the CI-gated
.slnxunit tier:Tests/Presentation/MMCA.Common.UI.Tests/Pages/Auth/AuthModelValidationTests.cs(8 facts: required/email/complexity/password-match) plusRegisterFormTests.cs(the per-fieldValidationMessagerenders on an empty submit and the auth service is never invoked), meeting the rubric M4 "enforced automatically by tests/CI" bar, consistent with §19's M4 on the same CI-gated guard tests. Caveat recorded: the sharedFormsConventionTestsBase(Testing.Architecture/Bases/FormsConventionTestsBase.cs:41,51) is consumer-scoped (it enumerates create forms underSource/Modules, absent in Common, and no Common subclass exists), so the in-repo M4 rests on the bUnit/model tests, not that fitness base. - 🔻 #14 · Testability: Implementation 9→8 (band recalibration, no work regressed). 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 internally-consistent value. Maturity holds at 4. Path back to 9: ratchet the coverage floor upward and add mutation testing on the Core tier.
- ✅ #34 follow-up (commit the ADR 033/034 docs pass) RESOLVED. Clean tree at v1.101.0 (HEAD
5e55be2); ADRs through 036 committed,FACTS.mdmatches the tag. Ticked in the eleventh-wave section above. - ◐ Evidence-cell corrections, scores unchanged: §7 (the extraction rule body lives in the shared
Testing.Architecturepackage:ArchitectureRules.Transport.cs:19plusBases/MicroserviceExtractionTestsBase.cs:13, subclassed atMicroserviceExtractionTests.cs:10); §9 (Scalar.AspNetCoreis 2.16.7 atDirectory.Packages.props:24, not 2.16.6 at:17); §13 (driftedAspire/Extensions.csanchors re-pointed to:37/:92/:147-148/:161/:264/:268/:277/:306/:314, and the stale "warm-up ships without unit tests" hold-reason removed: 9 warm-up tests have existed since the eighth wave, so the hold at I9 rests solely on deployer-owned SLO alerting/dashboards/runbooks).
Progress - defect-fix wave C-1..C-7 (2026-07-05)
Seven approved defect fixes, each behavior change landed with its pinning test flipped (or a new
regression test) in the same change; build 0/0 and the full .slnx suite green. One new test-only
package: Microsoft.Extensions.TimeProvider.Testing 10.7.0.
- ✅ C-1 (security, §11)
LoginProtectionService: clamped the exponential-backoff shift exponent (excess >= 31 formerly yielded negative or wrapped lockout TTLs); backoff theory extended with deep rows. - ✅ C-2
OAuthControllerBase.CompleteAsync: safereturnUrllookup with/fallback instead ofKeyNotFoundExceptionwhen the ticket lacks the item; regression test added. - ✅ C-3 (§13)
LoggingQueryDecorator: business failures now recordoutcome=failedoncqrs.query.durationplus a warning log (parity with the command decorator); the pin documenting the old asymmetry as intentional was flipped. - ✅ C-4 (BREAKING)
ChildEntityServiceBase: derives fromAuthenticatedServiceBaseand attaches the Bearer token on POST/DELETE; ctor now requiresITokenStorageService(consumer subclasses must pass it in the release sweep). - ✅ C-5
EntityServiceBase.GetAllForLookupAsync:namePropertynowUri.EscapeDataString-escaped; escape-needing test added. - ✅ C-6
OutboxCleanupService: optional trailingTimeProviderctor param (defaults to System); the purge sweep is now deterministically unit-tested withFakeTimeProviderover in-memory SQLite (old processed rows purged, newer/pending survive, per-source error isolation,EnableInboxgate). - ✅ C-7
SessionCookieAuthenticationHandler: expiry check moved fromDateTime.UtcNowto the base handler'sTimeProvider; deterministic fake-clock expiry test added.
Progress: sixteenth wave (clean-tree re-score at v1.106.0, 2026-07-06)
A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.106.0 (HEAD
6f8b917, one commit past the v1.106.0 tag, working tree clean). One score moved. Canonical scoring is now Maturity 94.4% (306/324) / Implementation 84.1% (681/810) per the in-repoArchitectureScorecard.md(was 94.4% / 83.7%).
- ✅ #14 · Testability & Test Strategy: Implementation 8→9. The fourteenth wave capped §14 at 8 on
"a modest 53% gated coverage floor"; that floor was ratcheted 53.0 → 68.3 (
.github/workflows/ci.yml:226, commitb75fa8f, measured ~70.3%) 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), so the top band is now supported. Maturity holds at 4 (CI-gated fitness tests, the blocking coverage-floor gate, and the zero-discovery min-tests guard). Held below 10 by the one remaining Exemplary gap: no mutation testing on the Core tier. §14 stays on the level-4 protect list. - ◐ #34 · Architecture Governance & Docs: held at M4/I8 (adversarial 8→7 declined). The re-score's
adversarial pass proposed docking §34 to Implementation 7 because the scorecard's own prose was stale against
the CI-gated
FACTS.md(it self-dated v1.101.0 / ADRs 001-036 whileFACTS.mdreports v1.106.0 / 001-038). That staleness is cured by this very refresh (the rewritten scorecard is current at commit time); the durable Strong-8 cap remains the uncommittableArchitecturalAnalysis.mdin the workspace root. Evidence refreshed to ADR set 001-038 and 78 fitness methods / 25 bases (Common runs 40). - ◐ Evidence enrichment, no score move: ADR-037 (field-level encryption at rest:
EncryptedStringConverterships but is explicitly latent/unadopted, §11/§30) and ADR-038 (supply-chain provenance: records the SBOM as generated-not-yet-signed, §32) land in categories already at 8-9; the C-1..C-7 defect fixes (§11 backoff-overflow clamp, §13 query-failure RED-metric parity) tighten existing mechanisms without moving a band.
Doc-hygiene follow-ups surfaced this wave (outside the scorecard/backlog, not score-movers):
-
SECURITY.md:5still says "thirteen packages" (should be fourteen) andGETTING-STARTED.md'sDirectory.Packages.propssample lists 13 package entries plus a stale1.77.0example version. Minor §34-adjacent staleness against the CI-gatedFACTS.md(14 packages); refresh in a docs pass. (§34, effort S.)
Progress - seventeenth wave (evidence re-score at v1.108.0, 2026-07-09)
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 amendment awaiting push) moves one score and closes nothing: no open item below is proven shipped this run, and no below-Maturity-4 category reached M4, so the priority ranking is unchanged (#31 at computed priority 4, the documented accepted cap; the seven weight-2 Maturity-3 categories at priority 2).
- ◐ #21 · Accessibility: Implementation recalibrated 9→8 (Maturity holds 4). The tenth-wave 9 sat in the
Exemplary band while the shipped, user-toggleable dark theme carries two documented, deliberately un-gated
WCAG AA contrast failures (filled-primary button label + error-alert text,
ACCESSIBILITY.mdknown limitations), a directly unmet §21 color-and-contrast criterion. With the standing smaller gaps (no automated focus-trap/reading-order assertion, manual pass only; axe breadth scoped to the gallery's representative states) the honest band is Strong; held at 8, not 7, because the gaps read as one minor cluster whose contrast half is a single palette-tuning item already tracked as the open §20 follow-up above (no duplicate item added; tuning the dark palette and gating the dark-mode axe scan is also the path back to 9). - ◐ #8 · Data Architecture: held at M4/I8 (adversarial 8→9 declined). The first pass proposed an
Implementation lift; refuted because the rubric's §8 "migrations run in CI/CD" criterion stays structurally
unmet in-repo (
ci.ymlhas no migration-apply step; ADR-030 boot-time self-migration is a runtime mechanism, not that gate), so the scorecard's standing "Held at 8 (not lifted)" note stands. - ◐ #22 · Responsive & Cross-Browser: held at M3/I8 (adversarial 8→9 declined). No repo change since the
twelfth-wave 7→8 lift; firefox/webkit remain advisory
continue-on-error(ci.yml:89). The open follow-up above (promote firefox to blocking, +1 Maturity) is still the sole §22 lift. - ◐ Evidence enrichment, no score move: ADR-039 (live channel push: ephemeral events over the notification
hub via
ILiveChannelPublisher) and the ADR-012 mixed-endpoint transport-profile amendment land in categories already scored 8-9; the ADR range refreshes to 001-039 (FACTS.md: 78 fitness methods / 25 bases, Common runs 41).
Progress - eighteenth wave (runtime performance wave, 2026-07-10)
A cross-repo runtime-performance audit (4 parallel auditors: framework, ADC, Store, hosting/config) found the framework strong on read-path fundamentals (no-tracking, SQL pagination, batched populators, pipeline split-query, outbox smart-wait) but flagged a cluster of hot-path costs, all fixed this wave (details in
CHANGELOG.md[Unreleased] and ADR-040). Mostly §12 Performance & Scalability plus §26 caching evidence:
- ✅ §12 · Outbox mark-processed set-based + async (
ExecuteUpdateAsync; was a nested synchronousSaveChanges()blocking a thread-pool thread per event-raising command);InProcessEventBusbatch publish = 1 save + 1 update (was 2 round trips per event). - ✅ §12/§26 · ADR-040
PublicEndpointOutputCachePolicy: authenticated requests no longer bypass the output cache on[AllowAnonymous]user-independent GETs (the UI's Bearer-on-every-request made the whole output-cache layer serve 0% of logged-in traffic). - ✅ §26 · Query cache hardening: stampede protection in
CachingQueryDecorator(per-key double-check locking);Result/Result<T>JSON round-trip converter (a Redis cache hit previously could not rehydrate: latent production incident once Redis appears); batched prefix invalidation (512-key deletes); single-copy serialization. - ✅ §12 · Retry ownership: standard resilience handler capped at 1 retry (UI policy owns user-facing
retries; stacked budgets amplified brownouts up to 16x); gRPC client resilience unified with the Aspire
values via new
HttpResilienceDefaults(Shared) + restoredPooledConnectionLifetime/keep-alive. - ✅ §12 · Allocation/reflection batch: lazy
Resulterror list + shared success instance; typed-DTO list responses skip per-rowExpandoObjectshaping when nofieldsrequested (BREAKING: query-service generics widened toobject; wire format unchanged); dispatcher closed-type cache; compiled failure factory;Type.GetTypecache;LocalView.FindEntry; split-query heuristic inEFReadRepository.ApplyIncludes; command started-log to Debug + source-generated scope; gzip Fastest. - ⏸ Deferred with rationale: interceptor
DetectChangesreduction (the second detection pass may be load-bearing for audit stamps; needs a dedicated EF-internals investigation; silent-data-loss failure mode) and a by-id fast path around the dynamic query pipeline (larger refactor; pressure mostly removed by ADR-040).
Progress - remediation wave 1 (cross-repo wave plan, 2026-07-11)
First wave of the 2026-07-11 cross-repo remediation plan (workspace plan file). Ships the shared §18/§19 fitness bases the ADC/Store maturity lifts need, closes the tenth-wave #20 dark-palette item, and adds a §23 measurement gate. Full Release build 0/0; 2223 tests green; gallery E2E 21/21 (19 prior + 2 dark-mode) plus 2 new vitals tests.
- ✅ §18/§19 shared fitness bases (the ADC/Store maturity 3→4 levers, consumed on the next sweep).
UIArchitectureConventionTestsBase(code-behind 400-line cap + inline@code120-line cap, non-vacuity guard) andStateManagementConventionTestsBase(no mutable static state inLayer.Uiassemblies via reflection,AllowedStaticMembersfor recorded exceptions; plus a no-singleton*StateService/*StateContainersource scan). Both subclassed in-repo (dog-food): the §19 gate caught and fixed two real §18 violations (MobileInfiniteScrollList~205 andNotificationBell~135 inline@codelines, both split to code-behind partials, snapshots/bUnit green) and surfacedErrorMessages._localizer(recorded as the one allowed static: write-once wiring seam, ADR-027). - ✅ #20 dark-mode palette contrast RESOLVED + GATED (§20/§21). Dark
PrimaryContrastText/ErrorContrastTextnowrgba(0,0,0,0.87);DarkModeE2ETests(Login + Components, dark palette via themmca_themecookie) reproduced both documented AA failures pre-fix and now gates them in the blocking chromiumui-e2ejob. §21 Implementation 8→9 candidacy recorded for the next re-score (CONFIRMED on the eighteenth-wave re-score, 2026-07-12: §21 is M4/I9). - ✅ §23 measurement gate.
WebVitalsE2ETestsasserts LCP/TTFB/CLS budgets on the gallery Login + Components pages (shippedWebVitalsCollector) inside the blockingui-e2ejob, so the shared-chrome front-end performance conventions are now measured AND enforced (the two gaps the §23 maturity-3 recalibration named). §23 maturity 3→4 candidacy recorded for the next re-score (CONFIRMED and applied to the scorecard table on the nineteenth-wave re-score, 2026-07-15: §23 is M4/I8, on the protect list). - ✅ §34 hygiene.
GETTING-STARTED.mdno longer restates the current consumer version (linksFACTS.md; sample version marked illustrative). Noted for a future docs pass:CHANGELOG.md's[Unreleased]section still accumulates content shipped in v1.86.0 through v1.114.0 without per-release headings.
Progress - eighteenth wave (evidence re-score at v1.115.0, 2026-07-12)
A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.115.0 (HEAD
37d0a3b, working tree clean, at the release tag). Three front-end scores move. Canonical scoring is now Maturity 95.1% (308/324) / Implementation 84.3% (683/810) per the in-repoArchitectureScorecard.md(was 94.4% / 83.7%). The three candidacies the remediation-wave-1 entry recorded are now score-confirmed.
- ✅ #21 · Accessibility: Implementation 8→9 (CLOSED, stays on the protect / consumer-assessed list). The
two documented, deliberately un-gated dark-theme WCAG AA contrast failures that capped §21 at 8 in the
seventeenth wave (filled-primary button label + error-alert message text) are fixed: dark
PrimaryContrastText/ErrorContrastText=rgba(0,0,0,0.87)(Source/Presentation/MMCA.Common.UI/Theme/MMCATheme.cs:60,73), and the dark-mode axe scan is now a blocking gate (DarkModeE2ETestsin the required chromiumui-e2ejob,.github/workflows/ci.yml:114). This is exactly the "tune the dark palette and gate the dark-mode axe scan is the path back to 9" the prior §21 row named, and it closes the remediation-wave-1 candidacy above. +3 index points (weight 3). Maturity holds at 4. - ✅ #20 · Design System & UI Consistency: Implementation 8→9. The same dark-palette fix, gated by the
same blocking dark-mode axe scan, closes the WCAG AA contrast half of §20's I8 deduction. +2 index points
(weight 2). Not a full clear: the Bootstrap-chrome→MudBlazor migration (Priority-2 #20 below) and the
residual
!important/raw-hex inwwwroot/app.css:122remain OPEN (the re-score judged them minor enough for I9, so that Priority-2 item stays unchecked). Maturity holds at 4. - ✅ #23 · Front-End Performance: Maturity 3→4 (CLOSED, moved to the level-4 protect list). The
front-end performance conventions the thirteenth wave recalibrated to review-enforced are now measured AND
automatically enforced:
WebVitalsE2ETestsasserts LCP/TTFB/CLS budgets on the gallery Login + Components pages inside the required chromiumui-e2emerge gate (Tests/Presentation/MMCA.Common.UI.E2E.Tests/WebVitalsE2ETests.cs:43,.github/workflows/ci.yml:114,145, measurement viaSource/Hosting/MMCA.Common.Testing.E2E/Infrastructure/WebVitalsCollector.cs:17), meeting the rubric M4 "enforced automatically (CI)" bar. This closes the remediation-wave-1 candidacy above. §23 leaves the priority-2 band, becomes the 27th Maturity-4 category, and joins the protect list. Implementation held at 8: desktopMudDataGridstill uses server paging rather than row virtualization. - ◐ Five adversarially-refuted first-pass proposals, no score move (recorded for the next cycle to
re-adjudicate). §7 Microservices Readiness (proposed Implementation 9→8, an unforced band recalibration
re-litigating a fourteenth-wave decline; holds M4/I9), §10 Cross-Cutting Concerns (proposed 8→9 rejected,
the three documented hold-reasons still in source; holds M4/I8), §25 Navigation (proposed M2/I6 downgrade
not supported, every mechanism present on a clean tree; holds M3/I8), §26 Front-End Security (proposed 8→9
rejected, the CSP
script-src/style-srcgap unclosed; holds M4/I8), and §34 Governance (proposed 8→7 on a transient stale-prose basis this refresh cures; holds M4/I8). - ✅ Evidence enrichment, no score move (ADRs 040-045, since v1.108.0). 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-gatedFACTS.mdreports 15 packages / ADR set 001-045 / 85 fitness methods across 28 bases (Common runs 46) and the scorecard rows are synced to match.
Progress - twentieth wave (evidence re-score at v1.117.0, 2026-07-17)
A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.117.0 (HEAD
76d70cf, working tree clean). Four scores move. Canonical scoring is now Maturity 96.3% (312/324) / Implementation 84.6% (685/810) per the in-repoArchitectureScorecard.md(was 95.1% / 84.3%). Two of the three candidacies recorded on 2026-07-16 are score-confirmed; the third (§12) is declined on live branch-protection evidence.
- ✅ #25 · Navigation & IA: Maturity 3→4 (CLOSED, moved to the level-4 protect list). The navigation-contract
drift gate the scorecard risk list prescribed now ships and gates merges:
NavigationContractTestsreflects over every routableMMCA.Common.UIpage (RouteAttribute/AuthorizeAttribute) and asserts route set-equality plus auth-posture agreement against the embeddedNavigationFlow.mdroutes table, with a non-vacuous 8-route floor (Tests/Architecture/MMCA.Common.Architecture.Tests/NavigationContractTests.cs:29,44,MMCA.Common.Architecture.Tests.csproj:12embeds the doc,MMCA.Common.slnx:45puts the gate in the CI-gated unit tier), meeting the rubric M4 "enforced automatically (CI)" bar and closing the §25 red flag. Implementation holds at 8: the gate is enforcement, not new execution breadth, and deep-link param typing beyond list-state stays light (plain string route templates, only the sanitized?returnUrl=query state). - ✅ #33 · Developer Experience: Maturity 3→4 + Implementation 8→9 (CLOSED, moved to the level-4 protect
list). The
consumer-source-buildcanary promotion recorded below is score-confirmed: the job is a required merge gate (nocontinue-on-error; the "Consumer source build (Helpdesk)" context verified in live branch protection,ci.yml:267-303,CONTRIBUTING.md:62), so a framework change that breaks a source-mode consumer blocks the merge automatically. The headless-hang and library-not-runnable notes stand as implementation caps only (I9, not I10). - ✅ #22 · Responsive: Implementation 8→9 (candidacy confirmed; stays on the protect list). webkit was
promoted to a required merge gate 2026-07-16 after 11 consecutive green main runs (
ci.yml:111-114), so all three engines now block merges, closing the row's single stated hold-at-8 reason ("webkit remains advisory").RESPONSIVE.md's browser matrix updated with this refresh (it still listed webkit as advisory). - ◐ #13 · Observability: Implementation 9→8 (band recalibration, no work lost; stays at Maturity 4).
Two of the six §13 criteria (SLO alerting/dashboards, runbooks) are deployer-owned, the rubric's Strong
band rather than Exemplary: the same deferred-to-consumer calibration §17/§29 already carry. The in-repo
substance (unconditional warm-up readiness gate with 9 unit tests, RED-metric parity, poll-span filtering)
is re-confirmed; the row's stale
Aspire/Extensions.csline anchors were corrected. - ⏸ #12 · Performance: Maturity 3→4 candidacy DECLINED (stays open at M3/I8). The latency-regression
gate shipped 2026-07-16 (
--job Short+build/perfgatevsTests/Performance/perf-baseline.json,ci.yml:174-200) and is a real qualitative upgrade over the runs-clean smoke, but the job's context is absent from the liverequired_status_checkslist (branch-protection API;CONTRIBUTING.md:57-62agrees), so a red perf gate does not block a merge and the rubric's merge-gate bar is unmet. Remaining step is administrative: promote the context to branch protection once observed reliably green (see the reopened #12 item below). - ⏸ #17 · DevOps: Implementation 8→9 candidacy DECLINED (stays open at M3/I8). The
sample-deployment-validatejob (blocking,az bicep buildon every push/PR,ci.yml:309-322) keeps the reference sample continuously valid, but it is a compile check and the workflow's own comment states a real what-if/deploy stays consumer-side, so it is neither new deployment execution (I9) nor automatic CD enforcement (M4). - ◐ Three adversarially-refuted first-pass proposals, no score move. §9 API & Contracts (proposed M3→4
rejected: the contract drift gate is deliberately consumer-owned,
OpenApiEndpointExtensions.cs:13, and no §9-specific CI gate exists in-repo; holds M3/I9 and enters the scorecard risk list in §25's vacated slot), §17 DevOps (proposed M3→4 rejected on the same compile-check-only evidence as the impl candidacy; holds M3/I8), §31 FinOps (proposed M2→3/I7→8 rejected: the proposal re-cited the identical evidence already grounding M2/I7; the accepted cap stands unchanged). - ✅ Counts refresh. The source-generated, CI-gated
FACTS.mdreports 15 packages / ADR set 001-048 / 85 fitness methods across 28 bases (Common runs 49); the scorecard's stale "Common runs 46" prose was synced to match.
Progress - twenty-first wave (evidence re-score at v1.121.0, 2026-07-21)
A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.121.0 (HEAD
4a4fc05, working tree clean). One score moves. Canonical scoring is now Maturity 96.9% (314/324) / Implementation 84.6% (685/810) perArchitectureScorecard.md(was 96.3% / 84.6%). The cycle's value is mostly in what it refused to move: seven proposed lifts were refuted against source.
- ✅ #12 · Performance & Scalability: Maturity 3→4, CLOSED and returned to the protect list. The
twentieth wave declined this candidacy because the perf gate's context was absent from
required_status_checks. That is no longer true: live branch protection onmainrequires 8 contexts includingPerformance gate (BenchmarkDotNet Short + baseline verify), matching the job name at.github/workflows/ci.yml:179, and the job has nocontinue-on-error(ci.yml:196-204), so a violation of the committedTests/Performance/perf-baseline.jsonceilings blocks the merge. Implementation holds at 8 (load and stress timing at realistic volumes stays consumer-side). This is the only index move: 312 + 2 = 314 maturity points. - ◐ Seven adversarially-refuted first-pass proposals, no score move. §4 DDD (I8→9 rejected: only
Notificationsis a bounded context inSource/Core/MMCA.Common.Domain, a criterion the row itself cites as its cap; the real v1.120.0 domain-event correctness fixes do not close it), §9 API & Contracts (M3→4 rejected: the three contract-test bases are subclassed only in Store/ADC, never in Common's own tests), §11 Security (I8→9 rejected: the deployment sample's Key Vault secret binding is still incomplete in the bicep template and no authz commit has landed since 2026-07-01), §19 State Management and §26 Front-End Security (held at prior on re-verification), §20 Design System (a proposed downgrade I9→8 rejected; 9 re-confirmed), §29 Resilience (I8→9 rejected: zero commits touch resilience, restore-drill, or warm-up files since the twentieth-wave baseline, and the resilience guide still states the capping gap). - ✅ New item under #34, opened and closed same day:
CONTRIBUTING.md's gate list had drifted from live protection. It advertised five required gates and called webkit advisory, while protection requires eight, and the reproduce snippet omitted webkit, the Helpdesk canary, and the perf gate (replaying it would have downgraded protection). Load-bearing rather than cosmetic: this file's authority is what held §12 at Maturity 3 for a cycle after its gate was promoted. Fixed in MMCA.Common PR #100 (658786b), which also points readers at the branch-protection API as authoritative over the committed copy. §34 holds M4/I8: this closes a stale-docs deduction, not an enforcement gap. - 📎 Path provenance note added to Deliberate / accepted:
COST.mdand the other in-repo doc paths cited by historical entries moved toWebsite/docs-src/in the 2026-07-20 centralization. - ✅ Counts refresh. The source-generated, CI-gated
FACTS.mdreports 15 packages / 91 fitness methods across 30 bases (Common runs 55) / coverage floor 68.3%.
Progress - twenty-second wave (evidence re-score at v1.123.0, 2026-07-23)
A full 34-category, two-pass evidence re-score (per-category scorer plus adversarial verifier) at framework v1.123.0 (HEAD
c911480, working tree clean). No score moves. Canonical scoring holds at Maturity 96.9% (314/324) / Implementation 84.6% (685/810) perArchitectureScorecard.md. The cycle's value is entirely in what it refused to move: five first-pass proposals (three lifts, two downgrades) were refuted against source.
- ◐ Three adversarially-refuted lift proposals, no score move. §9 API & Contracts (M3→4 rejected
again:
OpenApiContractTestsBaseis subclassed only in consumer hosts, never in Common's own tests, andOpenApiEndpointExtensions.cs:13records the delegation as deliberate, so no in-repo CI gate enforces the contract); §10 Cross-Cutting Concerns (I8→9 rejected: the three documented hold-reasons are still in source: the distributed cache path is a no-op without a realIConnectionMultiplexer(DependencyInjection.cs:158), the idempotency semaphore is in-memory rather than cross-instance-exclusive, and resilience config is partly literal); §30 Compliance (M3→4 rejected: the only automatic gate,PiiErasureContractFitnessTests, proves the erasure mechanism, while the structural[Pii]scan stays vacuous in-repo and the governing process (inventory, DSAR, consent, residency, retention) is consumer-resident). - ◐ Two adversarially-refuted downgrade proposals, no score move. §23 Front-End Performance and §24
Forms, Validation & UX Safety (each I8→7 rejected on a fresh re-read of every cited file): no
regression exists. §23's web-vitals budget gate is in fact stronger than when its 8 was set (all three
browser engines now required,
ci.yml:107), and §24's auth-form surface (Register/LoginEditForm+ DataAnnotations,PasswordComplexityAttribute,UnsavedChangesGuard.IsDirtyAccessor, the CI-gated bUnit/model tests) is intact. - 📎 Evidence enrichment, no band move. The v1.122.0-v1.123.0 capability train (typed filter DSL
operators, the
EntityQueryPipelinepage-size clamp, cache-observability warnings, theIIntegrationEventPublisherremoval with callers moved toIEventBus) lands in categories already scored 8-9. - ✅ Open set and rankings unchanged. #9, #17, #30 (M3, priority 2 each) and #31 (M2, computed
priority 4, documented accepted cap); the Deliberate / accepted section stands as written.
FACTS.mdreports 15 packages / 91 fitness methods across 30 bases (Common runs 55).
Deferred - 2026-07-19 full review (recorded, not scheduled)
The 2026-07-19 full framework review shipped its accepted fixes on the review branch (rollback on business failure + post-commit dispatch, outbox leases + dead-letter visibility, integration-event routing via
IMessageBus; ADR-003/014/030 revisions record them). The items below were reviewed and deliberately deferred: each is real, none is scheduled, and each records why it did not ship with the wave. IDs follow the C-1..C-7 precedent (FR = full review).
- FR-1 (§32/§16) - Re-split
MMCA.Common.Infrastructureinto opt-in provider packages (Cosmos / AzureMessaging / Media). The single Infrastructure package drags all three EF providers (SQL Server, Cosmos, SQLite), three messaging stacks (in-process, RabbitMQ, Azure Service Bus via MassTransit), and ImageSharp into every consumer's dependency graph, SBOM, and vulnerability surface, whether or not the consumer uses them (the suppressed SQLite advisory GHSA-2m69-gcr7-jv3q is a live example: every consumer inherits it for an engine most never enable). Deferred: a package split is a breaking, lockstep-wide re-shape (ADR-016) that needs its own design pass and consumer sweep. (Effort L.) - FR-2 (§15) -
Result<T>.Valuethrow-on-failure guard. Reading.Valueon a failed result silently returnsnull/default today; a guard that throws would convert the silent-null trap into a loud contract violation. Deferred as a breaking behavioral change (consumers may depend on the lenient read); the trap is documented in theResult<T>doc-comments for now. (Effort M, breaking.) - FR-3 (§6) -
TResult : Resultcompile-time constraint on handler signatures. The decorator pipeline assumes handler results areResult-shaped (the Transactional decorator pattern-matchesResult { IsFailure: true }); a generic constraint would make that assumption compile-time instead of runtime. Deferred as a breaking generic-signature change; covered in the interim by a new architecture rule asserting command/query result types derive fromResult. (Effort M, breaking.) - FR-4 (§33) - Reconsider the C# preview extension-type DI surface. DI registration methods
use
extension(IServiceCollection)blocks (LangVersion: preview). As the public registration surface of a published framework this is an adoption risk: consumers must also build with a preview language version until the feature GAs. Revisit when .NET ships the feature as stable; reverting to classic extension methods is mechanical but wide. (Effort M, watch item.) - FR-5 (§8) - Cascade soft-delete semantics. Soft-deleting an aggregate root leaves its children active: global query filters hide the root but child rows (and anything reached through them) stay live. Correct behavior is per-aggregate (some children should follow the root, some must survive it), so this needs a per-aggregate design pass, not a blanket cascade helper. (Effort M-L, design first.)
- FR-6 (§14) -
MMCA.Common.UI.Mauihas zero automated tests. The one MAUI-TFM package is built and packed by the dedicated windows CI jobs (ADR-042) but nothing exercises it: the capability contracts and fallbacks are tested inMMCA.Common.UI.Tests, while the thin Essentials wrappers themselves are verified only on-device. Options: a windows-job unit tier for the wrapper logic, or a documented on-device smoke checklist. (Effort M.) - FR-7 (§34) - CS1591 ratchet. XML doc coverage is enforced by convention, not the
compiler:
CS1591sits inNoWarn(Directory.Build.props:16), so a public member can ship undocumented without a build break. Ratchet per-project (remove the suppression where already clean, then expand) rather than repo-wide at once. (Effort S per project, long tail.)
🔴 Priority 6 — highest leverage
[x] #28 · Front-End Testing & Quality — score 2 → 4 (weight 3) · RESOLVED 2026-06-27
The package ships reusable Blazor primitives with no fast test tier.
(medium) No component tests for the UI library— RESOLVED:Tests/Presentation/MMCA.Common.UI.Testsreferencesbunit(2.7.2) + the shippedMMCA.Common.Testing.UIharness and ships 29 component tests across the branching primitives (MobileCardList,MobileInfiniteScrollList— empty/cards/cap/click/error+retry),UnsavedChangesGuard,NotificationBell,DeleteConfirmation,PageStateScope,RedirectToLogin, and thePageHeader/PageLoadingState/PageErrorStateprimitives (PrimitivesTests).(low) No axe/Lighthouse or visual-regression step in— RESOLVED:ci.ymlDeque.AxeCore.Playwright(4.12.0) is pinned and shipped inMMCA.Common.Testing.E2E(Page.AssertNoAccessibilityViolationsAsync()); theui-e2eCI job runs a cross-browser matrix (chromium required gate; firefox/webkit advisory) over the backend-less gallery with 6 axe-core WCAG 2.1 AA assertions + render smoke.
Fix
- Add bUnit; write render/parameter/
EventCallbacktests, starting with the branching components (MobileCardList,MobileInfiniteScrollList). → 29 component tests inMMCA.Common.UI.Tests. - Wire
Deque.AxeCore.Playwrightinto the existing E2E flows (≥1 a11y assertion). → 6 axe assertions across Login/Register/Components/Notifications. - Run at least one browser journey in MMCA.Common CI so regressions in the shipped E2E helpers are caught here, not only downstream. →
ui-e2ejob (.github/workflows/ci.yml), gallery host self-served, chromium gate.
[x] #30 · Compliance, Privacy & Data Governance — score 1 → 4 (weight 2) · RESOLVED 2026-06-27
Single-axis review only. In the live two-axis scorecard §30 is Maturity 3 / Implementation 8 — the in-repo erasure mechanism is complete (and now fitness-gated, see the 2026-06-29 item below), but the broad governance process (DSAR/consent/residency/retention/inventory) is consumer-owned, so two-axis maturity is held at 3, not 4.
Soft-delete is the only deletion model — no lawful erasure path. (All three fix items shipped; see the wave-1 progress entry above and the 2026-06-27 closeout below.)
(medium)— RESOLVED:AuditableBaseEntity.Delete()setsIsDeleted=true… the exact GDPR/CCPA conflict the rubric names.IAnonymizableerasure seam (Domain/Interfaces/IAnonymizable.cs), enforced by thePiiConventionTestsfitness rule (a[Pii]-marked property obligesIAnonymizable); the AES-256-GCMEncryptedStringConverterships for retrievable PII.(low) Processed outbox rows … are never purged— RESOLVED:OutboxCleanupServicepurges processed outbox (and inbox) rows older thanOutbox:RetentionDays(default 7) from every relational source.(low) No PII/consent/DSR machinery— RESOLVED (framework seam):[Pii]marker +PiiConventionTests+EncryptedStringConverter, and nowPiiRedactormasks[Pii]members before they reach a structured log / telemetry attribute (closing the documented-but-missing log-redaction half of the[Pii]contract). DSR/erasure endpoints remain consumer-owned (ADC ships them — see ADC #30).
Fix
- Add an
IAnonymizable/ erasure-orchestration seam that reconciles soft-delete with subject deletion (anonymize-in-place, preserve audit trail). →IAnonymizable+ ADR-005 +PiiConventionTestsguard. - Add an outbox-purge background option with configurable retention. →
OutboxCleanupService(Outbox:RetentionDays). - Write an ADR framing the soft-delete-vs-erasure tradeoff and the consumer's data-controller obligations. →
ADRs/005-soft-delete-vs-erasure.md. - (2026-06-27) Make the
[Pii]log-masking real —PiiRedactor(Domain/Privacy/PiiRedactor.cs) masks every[Pii]-marked member (shallow, value-erasing) so an entity carrying personal data can be logged without leaking clear-text PII; thePiiAttributedoc previously advertised this policy but no implementation existed. Covered by 7PiiRedactorTests. - (2026-06-29) Gate the erasure contract with a fitness function —
PiiErasureContractFitnessTests(Tests/Architecture/.../PiiErasureContractFitnessTests.cs:19-40) forces a[Pii]-markedDataSubjectSamplethroughPiiRedactor+IAnonymizableend-to-end, so the redaction/erasure mechanism is no longer un-gated. Note: this verifies the mechanism; the repo-widePiiConventionTestsscan stays vacuous (no PII-bearing type lives in Common's Domain) and the DSAR/consent/residency/inventory process stays consumer-owned, so two-axis §30 maturity is held at 3.
🟠 Priority 3 — score 3, weight 3 (one rung from a 4)
[x] #29 · Resilience, Reliability & Business Continuity — 3 → 4 · RESOLVED 2026-06-30 (tenth wave) — now on the level-4 protect list
(medium) No broker retry policy on the extracted-microservice path— RESOLVED (re-verified 2026-06-29):ConfigureBrokerTransportappliescfg.UseMessageRetry(exponential) on both RabbitMQ (DependencyInjection.cs:432) and Azure Service Bus (:449), and theIntegrationEventConsumercomment + log are corrected.UseDelayedRedeliveryis deliberately omitted (DependencyInjection.cs:408, accepted — needs the RabbitMQ delayed-exchange plugin).Gap (why #29 stays open at Maturity 3): no in-repo backup/restore drill, RTO/RPO, failover, or SLOs.— CLOSED (tenth wave, Maturity 3→4): the in-repoDatabaseRestoreDrillTestsruns as a build gate on every CI build (seed→backup→catastrophic-wipe→restore→verify on ephemeral SQLite; 0-row RPO byte-for-byte asserted), andRESILIENCE.mdrecords the measured restore baseline (~5 ms median RTO over 5 runs), meeting the M4 "enforced automatically" bar. Production RTO/RPO against real cloud backups + measured prod SLOs stay consumer IaC, so Implementation is held at 8. (chaos/fault-injection covered below.)
Fix
- Fault-injection / chaos test landed (C-8, 2026-06-19).
ResilienceCircuitBreakerFaultInjectionTests(Grpc.Tests) drives an always-failing dependency through the standard resilience handler and asserts the circuit breaker trips and short-circuits further calls;OutboxProcessorTests.IntegrationEventPublishFailure_DegradesGracefully_BuffersForRedeliveryasserts the outbox buffers the event (retry++, left unprocessed) when the broker is unreachable instead of crashing the processor. - Add a default
UseMessageRetry(backoff + jitter) inConfigureBrokerTransport; expose a hook for consumers to tune it (MessageBusSettings.RetryLimit/RetryMinIntervalSeconds/RetryMaxIntervalSeconds). (UseDelayedRedeliverydeliberately omitted — accepted.) - Correct or remove the misleading comment + log message. (Done —
IntegrationEventConsumer.cs:59-60+ the doc-comments atDependencyInjection.cs:401,408.)
[ ] #32 · Dependency & Supply-Chain Management — 3 → 4 (weight 3, framework)
- (medium) The safety-critical MassTransit v8 pin (
Directory.Packages.props:28-36) is guarded only by a prose comment; a blanket "update all" once bumped it to v9.1.2, which crashes every broker-enabled host at startup — and CI never starts a broker, so the build stays green. (Matches the standing MassTransit-v8 constraint.) - (low) No lock files or SBOM for 11 published packages; no documented breaking-change/SemVer policy or CHANGELOG.
Fix (the pin fix also closes #16's medium)
- Replace the exact pin with a constrained range
[8.5.5,9.0.0), or add a fitness test asserting the MassTransit major stays ≤ 8. - Enable
RestorePackagesWithLockFile+ commit lock files. - Add a CycloneDX SBOM step to the release workflow.
- Publish a brief versioning / breaking-change policy + CHANGELOG.
[ ] #11 · Security — 3 → 4 (no confirmed red flags)
- Gap: no explicit CI dependency-vuln gate or
<auditSources>in nuget.config; no security fitness tests; insecure dev defaults (requireHttpsMetadata=false, permissive dev CORS); no SECURITY.md/threat model.
Fix
- Add a
dotnet list package --vulnerable(or restore--audit) CI gate. - Add NetArchTest security invariants (no stray
[AllowAnonymous]; noAllowAnyOrigin+AllowCredentials). - Commit a SECURITY.md with an OWASP Top-10 review note.
[ ] #4 · Domain-Driven Design — 3 → 4 (no confirmed red flags)
- Gap: no DDD-specific fitness functions; minor factory inconsistencies (
UserNotification.Createreturns a bare entity;Money.operator+throws on currency mismatch).
Fix
- Add NetArchTest rules: aggregates expose private ctors + factory methods; no cross-aggregate navigation properties.
- Normalize the factory convention to always return
Result<T>.
[ ] #18 · UI Architecture & Component Design — 3 → 4 (no confirmed red flags)
- Gap: no bUnit/render tests; component conventions review-only.
Fix
- (shared with #28) add bUnit coverage for the primitives.
- Consider an analyzer/convention check for
EditorRequiredcontracts on shared components.
[ ] #19 · State Management & Data Flow — 3 → 4
- (low)
UnsavedChangesGuardexposesIsDirtyonly as a[Parameter];HandleBeforeInternalNavigationAsyncreads it one render late, so clearing dirty +NavigateTowithout an interveningStateHasChanged()still shows the dialog.Source/Presentation/MMCA.Common.UI/Components/UnsavedChangesGuard.razor:24,38-55. Untested. (This is the known param-lag foot-gun.)
Fix
- Add an optional
Func<bool>?live-accessor parameter so the guard reads current dirty state at navigation time. - Cover with a bUnit test.
🟡 Priority 2 — score 3, weight 2 (polish / hardening)
[ ] #6 · CQRS & Event-Driven
- (medium) No consumer-side idempotency/inbox for at-least-once broker delivery — duplicate side effects possible in any non-idempotent consumer. (low) Same misleading "MassTransit will retry" comment.
- Ship an optional EF-backed inbox/dedup filter keyed on a message id; add a unique event Id to base events.
[ ] #16 · Maintainability & Evolvability
- (medium) Blanket NuGet update reintroduced known-bad MassTransit v9 (commit
87d54ee) — fixed by a comment, not a rule. (low) No CHANGELOG/breaking-change policy for 11 published packages. - Closed by the #32 pin fix + add a per-release CHANGELOG.
[ ] #13 · Observability & Operability
- (low) The outbox dead-letter Meter
MMCA.Common.Outboxis created but noAddMetercall exists → the dead-letter counter is never exported (contradicts CLAUDE.md); mitigated by an Error-level log. - Add
AddMeter("MMCA.Common.Outbox")toWithMetrics; emit RED histograms for command/query latency.
[ ] #17 · DevOps & Deployment
- (low) Security/audit only implicit (no Dependabot/CodeQL/audit step).
- Add Dependabot + an explicit audit job; push
.snupkgsymbol packages (currently built but never published).
[ ] #9 · API & Contract Design
- (low)
ServiceContractAttributedocuments architecture-test enforcement that does not exist. - Implement the NetArchTest rule (or remove the claim); add OpenAPI generation + a contract snapshot test.
[ ] #20 · Design System & UI Consistency
- (low) Bootstrap chrome (NavMenu top bar/hamburger) coexists with MudBlazor in the shared package.
- Migrate remaining Bootstrap chrome → MudBlazor, drop the bundled Bootstrap CSS; source the brand hex from one token.
[x] #23 · Front-End Performance: CLOSED at Maturity 4 (nineteenth-wave re-score, 2026-07-15) → moved to the level-4 protect list
(low)RESOLVED (third wave):MobileInfiniteScrollListappends every page into oneMudStackwith no virtualization/cap.MaxRenderedItems(default 500) bounds DOM growth.- Add
Virtualizewindowing or a rendered-item cap. → theMaxRenderedItemscap (third wave), and the §23 measurement gate is confirmed:WebVitalsE2ETestsasserts LCP/TTFB/CLS budgets inside the blocking chromiumui-e2ejob (Tests/Presentation/MMCA.Common.UI.E2E.Tests/WebVitalsE2ETests.cs:43,.github/workflows/ci.yml:105-115), so the remediation-wave-1 maturity 3→4 candidacy is confirmed and applied to the scorecard table.
[x] #33 · Developer Experience & Inner Loop: CLOSED at Maturity 4 / Implementation 9 (twentieth-wave re-score, 2026-07-17) → moved to the level-4 protect list
- (low, residual, recorded not scheduled) The package local-dev swap list (fifteen packages per
FACTS.md; the "11-package" count here had gone stale) is hand-maintained three times in each consumer'sDirectory.Build.targetsand can silently drift. (Partially mitigated: the requiredconsumer-source-buildcanary now fails the merge if the HelpdeskUseLocalMMCAswap breaks.) - Generate the list from a glob, or add a smoke test that the
UseLocalMMCAswap resolves all packages. - (2026-07-14, partial) A
consumer-source-buildCI canary now builds MMCA.Helpdesk against the PR's framework source viaUseLocalMMCA(.github/workflows/ci.yml:262, documented inCONTRIBUTING.md:74), catching cross-repo source-mode breakage in CI. It is advisory (continue-on-error: true); the nineteenth-wave re-score held §33 at M3/I8 (an advisory gate does not clear the automatic-enforcement or Exemplary bars, and the Aspire-headless-hang plus library-not-runnable caps stand). Promote it to a required gate once proven reliably green. PROMOTED 2026-07-16:continue-on-errorremoved and the "Consumer source build (Helpdesk)" context added to branch protection after 9 consecutive green runs since introduction (2026-07-14), so a framework change that breaks a source-mode consumer now blocks the merge automatically. SCORE-CONFIRMED (twentieth-wave re-score, 2026-07-17): §33 is M4/I9 (the promotion verified in live branch protection; the headless-hang and library-not-runnable notes stand as impl caps only, holding I9 short of 10).
[ ] #34 · Architecture Governance & Documentation
- (low ×2)
Docs/Architecture/ArchitecturalAnalysis.mdcontradicts the code on DB-per-service ("deliberately not database-per-service," race "only mitigated"); the two biggest recent decisions (DB-per-service, gRPC extraction) lack ADRs. - Refresh the analysis doc; write the two missing ADRs; add an ADR index/template.
- (added and CLOSED 2026-07-21, twenty-first-wave re-score; priority 2 band, effort S) Sync
CONTRIBUTING.md's required-merge-gate list and its branch-protection reproduce snippet with live protection. DONE (MMCA.Common PR #100, merged658786b, all 8 required gates green): the prose list now names all eight gates with webkit marked as promoted 2026-07-16 and the perf gate described againstTests/Performance/perf-baseline.json; the reproduce snippet was extended to the same eight contexts and verified byte-identical against the live protection API; and a line now directs readers togh api repos/ivanball/MMCA.Common/branches/main/protectionas authoritative over the committed copy, which is the durable fix for this class of drift. Original finding: Liverequired_status_checksonmaincarries 8 contexts (build-and-test;Build MMCA.Common.UI.Maui (windows, 4 TFMs); UI a11y + render smoke on chromium, firefox, and webkit;coverage;Consumer source build (Helpdesk);Performance gate (BenchmarkDotNet Short + baseline verify)), but the doc lists five gates and still calls webkit advisory (CONTRIBUTING.md:57-64), and the reproduce snippet omits webkit, the Helpdesk canary, and the perf gate (CONTRIBUTING.md:104-112,124).ci.yml:116-118already asserts all three engines are required, so the workflow and the doc disagree. This is load-bearing beyond hygiene: scorecard adjudications cite this file, and its staleness is exactly why §12 was held at Maturity 3 for a cycle after its gate was in fact promoted. Prefer the branch-protection API over the committed snippet when adjudicating.
[x] #5 · Vertical Slice Architecture — DONE (eighth wave: impl 7→8 AND maturity 3→4) → moved to the level-4 protect list
- Slice-cohesion fitness function added:
ArchitectureRules.Slices.cs+SliceCohesionTestsBase(shared package, the 18th fitness base) + Common/ADC subclasses — fails the build if a handler/validator is stranded from its same-assembly contract. Because this is automatic CI enforcement of the slice convention, §5 maturity also rose 3→4 (the rubric's maturity-4 "enforced automatically by tests/CI" bar), so §5 now belongs in "Already at level 4 — protect, don't regress" below.
[x] #12 · Performance & Scalability: CLOSED at Maturity 4 / Implementation 8 (twenty-first-wave re-score, 2026-07-21) → back on the level-4 protect list (reopened 2026-07-15 by the nineteenth-wave Maturity 4→3 recalibration; open for two cycles)
- BenchmarkDotNet smoke project added (
Tests/Performance/MMCA.Common.Benchmarks, outside the .slnx). Max-page-size guard already shipped at v1.84.0 (ApplicationSettings.MaxPageSizeclamp +EntityQueryPipeline.MaxUnboundedResultLimit). -
Maturity 3→4 via the build-gatingRECALIBRATED 4→3 (2026-07-15): the job is present and blocking on every push/PR with noperformance-smokejob.continue-on-error(.github/workflows/ci.yml:175), but it 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 maturity-4 bar requires. No work was lost; the recalibration rests entirely on the smoke-vs-regression distinction, and the shipped guards (smoke gate, page-size clamp, unbounded-query ceiling) keep Implementation at 8. - Add a latency-regression gate (a committed baseline plus tolerance threshold) over the BenchmarkDotNet hot paths to restore §12 to Maturity 4. Blocker: none, pure CI + baseline work; effort M. DONE (2026-07-16): the
performance-smokeCI job now runs the suite with--job Short --exporters jsonand a second step (build/perfgate, dependency-free likebuild/facts) fails the job against the committedTests/Performance/perf-baseline.json: deterministic per-benchmark allocation ceilings (0 / 8000 / 4500 B/op) plus a machine-independent ratio floor (the compiled-expression cache must stay at least 1000x ahead of the recompile anti-pattern; measured ~120,000x), so a broken cache or an allocation storm reds the job instead of running clean. Verified green on real results and red on a seeded ceiling violation.Maturity 3 → 4 candidacy recorded for the next re-score.Candidacy DECLINED (twentieth-wave re-score, 2026-07-17): the job's context is absent from the liverequired_status_checkslist (branch-protection API;CONTRIBUTING.md:57-62agrees), so a red perf gate does not block a PR merge and the rubric's merge-gate bar is unmet; §12 holds M3/I8. - Promote the perf-gate job context to branch protection's required checks, the same promotion path firefox (2026-07-12), webkit (2026-07-16), and the consumer-source-build canary (2026-07-16) completed. DONE, verified 2026-07-21: the live
required_status_checkslist onmaincarries 8 contexts includingPerformance gate (BenchmarkDotNet Short + baseline verify), matching the job name at.github/workflows/ci.yml:179exactly, and the job carries nocontinue-on-error(ci.yml:196-204), so a baseline violation now blocks the merge. §12 restored to Maturity 4 on the twenty-first-wave re-score; Implementation holds at 8 because load and stress timing against realistic volumes stays a consumer-app concern. - (residual, doc half of the promotion) Add the perf-gate context to
CONTRIBUTING.md's required-checks list. It still lists five gates and omits the perf gate, webkit, and the Helpdesk consumer-source canary from both the prose list (CONTRIBUTING.md:57-64) and the branch-protection reproduce snippet (CONTRIBUTING.md:104-112,124). Tracked as the shared doc-sync item under #34 below; it does not hold §12's score. Blocker: none, administrative; effort S.
[x] #17 · DevOps & Deployment: DONE (eighth wave, impl 7→8)
- In-repo reference deployment sample added:
samples/deployment/{foundation,main}.bicep(lint clean viaaz bicep build) +DEPLOYMENT.md(OIDC federated-credential + UAMI bootstrap + smoke-gate/auto-rollback). (Deeper CD-to-Azure lives in consumer repos.) - Sample kept continuously valid in CI (2026-07-16): the new
sample-deployment-validatejob compiles both templates withaz bicep buildon every push/PR, so the §17 reference cannot rot silently (the former lint-clean claim was a point-in-time check). A credentialed what-if/deploy stays consumer-side by design.§17 impl 8→9 candidacy recorded for the next re-score.Candidacy DECLINED (twentieth-wave re-score, 2026-07-17): the validate job is a compile check, not new deployment execution, so §17 holds M3/I8 (a first-pass M3→4 proposal was also refuted on the same evidence: the workflow's own comment states a real what-if/deploy stays a consumer-side concern,ci.yml:309-310).
[x] #29 · Resilience & Business Continuity — DONE (eighth wave, impl 7→8)
- Warm-up subsystem unit-tested (gate/hosted-service/health-check);
RESILIENCE.mdadds an in-repo SLO/error-budget template + restore-drill runbook reference. (The drill itself executes in consumer IaC — ADC'sdr-restore-drill.ps1.)
✅ Already at level 4 — protect, don't regress
#1 SOLID · #2 Design Patterns · #3 Clean Architecture · #5 Vertical Slice (maturity 3→4 on the slice-cohesion fitness function) · #7 Microservices Readiness · #8 Data Architecture · #10 Cross-Cutting Concerns · #14 Testability · #15 Best Practices & Code Quality · #22 Responsive & Cross-Browser (maturity 3→4 on the firefox required merge gate, nineteenth-wave re-score, 2026-07-15; impl 8→9 confirmed on the twentieth-wave re-score, 2026-07-17, after webkit's 2026-07-16 promotion made all three engines blocking) · #23 Front-End Performance (maturity 3→4 on the blocking WebVitalsE2ETests budget gate, confirmed nineteenth-wave re-score) · #24 Forms, Validation & UX Safety (maturity 3→4 on the CI-gated auth-form tests, fourteenth wave) · #25 Navigation & IA (maturity 3→4 on the CI-gated NavigationContractTests drift gate, twentieth-wave re-score, 2026-07-17) · #27 i18n (maturity 3→4 on the fifteenth-wave completion train, 2026-07-03) · #29 Resilience (maturity 3→4 on the build-gated restore drill, tenth wave) · #33 Developer Experience (maturity 3→4 + impl 8→9 on the required consumer-source-build merge gate, twentieth-wave re-score, 2026-07-17) · #12 Performance & Scalability (maturity 3→4 on the required Performance gate (BenchmarkDotNet Short + baseline verify) merge check, twenty-first-wave re-score, 2026-07-21)
(All backed by fitness functions: the regression guard is keeping those tests green. This lists the categories that reached Maturity 4 through tracked remediation; under the live two-axis scorecard the full Maturity-4 set is 30 categories, see the Scope note at the top. #12 Performance & Scalability rejoined this list on 2026-07-21 after two cycles out: the nineteenth-wave re-score recalibrated its Maturity 4→3 because the performance-smoke job was a runs-clean smoke, the latency-regression gate shipped 2026-07-16, and the twenty-first-wave re-score confirmed its context is now in live required_status_checks. Protecting it means keeping Tests/Performance/perf-baseline.json honest: a ceiling raised to silence a red gate regresses the category without changing the score.)
🔒 Deliberate / accepted (documented caps, not scheduled work)
[accepted] #31 · Cost Efficiency / FinOps: held at Maturity 2 / Implementation 7 by documented acceptance
Moved out of the active priority queue on 2026-07-02 (user-approved). Its computed priority = (4 − 2) × 2 = 4 is the highest weighted gap of any open category, but the unmet §31 criteria are consumer/IaC execution a NuGet library cannot perform: right-sizing and reversible scale-events are host-infrastructure actions the framework provisions nothing to take, and per-service cost attribution via Aspire resource annotations is inert for the hand-written main.bicep consumers (ADC/Store), so even the one library-addressable criterion does not move the score for the actual consumers. The in-repo levers are already shipped and documented: the Telemetry:TracesSampleRatio OTel sampler knob, the outbox per-message log trimmed Information→Debug, and the cost guide's cost-attribution-tag plus cost-guard samples. Further movement is a consumer-side lift, not an in-repo one, so §31 is recorded here as an accepted cap rather than scheduled work. (Note on paths: COST.md, RESILIENCE.md, RESPONSIVE.md, ACCESSIBILITY.md, and the ADRs/ folder cited throughout this file's historical entries no longer live in the MMCA.Common repo. The 2026-07-20 centralization moved the documentation library to Website/docs-src/ (guides, ADRs, governance); only CHANGELOG.md, CLAUDE.md, CONTRIBUTING.md, FACTS.md, NavigationFlow.md, README.md, and SECURITY.md remain in-repo. Historical in-repo paths are left as written for provenance.) (See common-COST.md, the §31 scorecard row, and the twelfth-wave [accepted] note above for provenance. Re-adjudicated on the twentieth-wave re-score, 2026-07-17: a first-pass M2→3/I7→8 lift proposal was adversarially refuted for re-citing the identical evidence already grounding M2/I7; the acceptance stands unchanged.)
⚪ Mostly consumer-assessed (the shared Common.UI surface is scored here)
#21 Accessibility · #26 Front-End Security (Assessable mainly in consumer apps; #26 shared surface is covered under #11.)
- #22 Responsive: CLOSED at Maturity 4 / Implementation 9 (impl confirmed twentieth-wave re-score, 2026-07-17). firefox was promoted to a required merge gate alongside chromium on 2026-07-12, and webkit on 2026-07-16 after 11 consecutive green main runs (
.github/workflows/ci.yml:111-114, nocontinue-on-errorremains in the job), so all three engines block merges; on the protect list above. The former residual (webkit advisory) is closed; Implementation 9 is held short of 10 by the gallery-representative-states scope and the doc-only device matrix. - #27 i18n: CLOSED at Maturity 4 / Implementation 9 (fifteenth-wave completion train, 2026-07-03). No longer consumer-assessed/N/A: it became an active in-repo category after ADR-027 shipped en-US + Spanish (superseding the single-locale ADR-011), and the ADR-027 Decision 9 train closed every stated hold: the pseudo-localization pass is a REQUIRED chromium CI gate (
PseudoLocalizationE2ETests:[!!sentinel round-trip + overflow guard +en-USleak guard), the hard-coded-literal gate (LocalizedTextConventionTests) and the translation-coverage gate fail the build, and MudBlazor's built-in chrome localizes viaResxMudLocalizer(145 keys en+es). Held below 10 only by two locales / no RTL; on the protect list above. - #24 Forms/UX Safety: DONE for the shared surface (eighth wave, impl 7→8). Register/Login are now
EditForm+ DataAnnotations + per-fieldValidationMessage(typed models +PasswordComplexityattr + tests). Consumer module forms remain consumer-scored. Maturity reached 4 on the fourteenth-wave re-score (the CI-gatedAuthModelValidationTests+RegisterFormTestsmeet the automatic-enforcement bar); the category is closed and on the protect list above. - #25 Navigation: CLOSED at Maturity 4 / Implementation 8 (twentieth-wave re-score, 2026-07-17). The eighth wave shipped the in-shell
Forbidden(403) page +NavigationFlow.mdfor the Common UI surface (impl 7→8); the nineteenth-wave refusal (no drift gate, no route-auth test) is resolved:NavigationContractTests(route/doc set-equality + auth-posture agreement over the embeddedNavigationFlow.md, non-vacuous floor) runs in the CI-gated.slnxunit tier, exactly the routing fitness check the risk list prescribed, so the M3→4 lift is score-confirmed; on the protect list above. Per-actor module flows remain consumer-scored, and Implementation holds at 8 (deep-link param typing beyond list-state stays light).
Suggested sequencing
- MassTransit v8 fitness test (#32 + #16) — one small test, closes two mediums, prevents a recurring prod crash.
- Broker retry policy (#29 + #6) — the async path is the system's weakest seam.
- bUnit harness (#28 + #18 + #19 guard) — unlocks the whole front-end tier.
- Erasure seam + outbox purge (#30) — the only score-1 category; real compliance exposure.
- Sweep the fitness-function gaps (#4, #11, #5) and doc/CI hygiene (#34, #17, #9, #13) as steady cleanup.