Architecture governance
MMCA.Common: Architecture Remediation Backlog
Derived from the scorecard: Maturity 96.6% (317/328) and Implementation 85.7% (703/820), framework v1.244.0, evidence as of 2026-10-18. Tasks are ranked on both scorecard axes, one band per axis. The maturity band holds every category with maturity < 4, ranked by priority = (4 − maturity) × weight; the implementation band holds every category with implementation <= 8, ranked by **implPriority = max(0, 9 − implementation) × weight** (target 9: a 10 is awardable at re-score time, but it is recognition, not scheduled work). Ties break by weight descending, then category number. A category reaches the Resolved table only at maturity 4 AND implementation >= 9. The maturity band holds 4 categories, 11 gap points; the implementation band holds 16 categories, 35 gap points. Levers are cited only where this ledger or the scorecard records one; an unnamed lever is named at the next re-score, never invented here.
Open work at a glance
Triage 2026-10-19. Every open row was weighed for whether its remaining work is worth building for one developer and about 67 peak users. The #24 lever shipped in v1.245.0 (Common #567) and is under Resolved. Rows marked closed carry no scheduled work: each reason is under Deliberate and accepted (Closed as over-engineering, 2026-10-19), and a re-score must not re-propose one without new evidence. Scores are unchanged until the next re-score, so every row keeps its band position.
| Priority | # | Category | M / I | Band | Next action |
|---|---|---|---|---|---|
| 4 (M), 2 (I) | #31 | Cost Efficiency / FinOps | 2 / 8 | Maturity + Implementation | None scheduled: a documented accepted cap (see Deliberate and accepted) |
| 3 (M), 3 (I) | #1 | SOLID Principles | 3 / 8 | Maturity + Implementation | Closed 2026-10-19: the remaining criteria have only metric-proxy gates |
| 3 (I) | #4 | Domain-Driven Design | 4 / 8 | Implementation | Closed 2026-10-19: strategic DDD lives in the consumers; the string tenant id is a decision |
| 3 (I) | #11 | Security | 4 / 8 | Implementation | Closed 2026-10-19: per-service or transport-level gRPC identity is not adopted |
| 3 (I) | #29 | Resilience & Business Continuity | 4 / 8 | Implementation | Closed 2026-10-19: consumer IaC work, already drilled in ADC and Store |
| 2 (M), 2 (I) | #17 | DevOps & Deployment | 3 / 8 | Maturity + Implementation | Closed 2026-10-19: a library cannot deploy itself |
| 2 (M), 2 (I) | #30 | Compliance, Privacy & Governance | 3 / 8 | Maturity + Implementation | Closed 2026-10-19: consent, lawful basis and residency are consumer-owned |
| 2 (I) | #5 | Vertical Slice Architecture | 4 / 8 | Implementation | Closed 2026-10-19: Common has no business slices |
| 2 (I) | #12 | Performance & Scalability | 4 / 8 | Implementation | Closed 2026-10-19: the load tier stays scheduled (accepted) |
| 2 (I) | #13 | Observability & Operability | 4 / 8 | Implementation | Closed 2026-10-19: production alerting is deployer-owned |
| 2 (I) | #20 | Design System & UI Consistency | 4 / 8 | Implementation | Closed 2026-10-19: the !important overrides are kept by decision |
| 2 (I) | #23 | Front-End Performance | 4 / 8 | Implementation | Open: make the consumers' wasm-payload-budget job a required check (a branch-protection change in ADC and Store, no code) |
| 2 (I) | #24 | Forms, Validation & UX Safety | 4 / 8 | Implementation | Lever shipped in v1.245.0 (Common #567); the residual gaps are closed 2026-10-19; re-score pending |
| 2 (I) | #33 | Developer Experience & Inner Loop | 4 / 8 | Implementation | Closed 2026-10-19: the gaps are documented workstation gotchas |
| 2 (I) | #34 | Architecture Governance & Docs | 4 / 8 | Implementation | Closed 2026-10-19: no defect behind the residual |
| 1 (I) | #27 | Internationalization (i18n) | 4 / 8 | Implementation | Closed 2026-10-19: two LTR locales need no RTL or CLDR work |
Open items
[closed] #1 · SOLID Principles
- Gap (maturity): SRP, DIP and the throw half of LSP are enforced automatically: a ratcheted constructor-dependency ceiling is subclassed in Common (
Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencies/FrameworkConstructorDependencyTests.cs:25; services 7 at:33, controllers 5 at:37, handlers 7 at:40), beside the compile-time layer targets and the NetArchTest layer, purity and clock-read subclasses, andSubstitutabilityTestsBase(Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/SubstitutabilityTestsBase.cs:34) is subclassed with no exclusions atTests/Architecture/MMCA.Common.Architecture.Tests/Governance/Substitutability/CommonSubstitutabilityTests.cs:10. OCP and ISP have no automated gate, the type-sniffing half of LSP is ungated (ChildEntityHandlerBase.cs:139,IdentityModuleDbSeederBase.cs:162), and S107 and S1200 sit at severity suggestion (.editorconfig:478,492). - Gap (implementation):
NotSupportedExceptionappears in 19 Source files, and the MAUI capability files catchFeatureNotSupportedExceptionrather than throw;MMCA.Common.UI.Mauisits outsideCommonArchitectureMap(CommonArchitectureMap.cs:9), so the LSP gate does not scan it.ApplicationDbContextresolves about 13 collaborators through an injectedIServiceProvider(ApplicationDbContext.cs:48), out of sight of the SRP ceiling. TheInternalCommandDeadLetter8-field record is excluded from the ceiling by design (FrameworkConstructorDependencyTests.cs:57-68). - Lever: closed 2026-10-19 (see Closed as over-engineering, 2026-10-19).
[closed] #4 · Domain-Driven Design
- Gap: strategic DDD (bounded contexts, ubiquitous language) is realized downstream: the Domain layer holds one aggregate family (Notifications) plus auth support types. The tenant identifier is a plain string by deliberate decision rather than a strong type; ADR-073 records the tenancy model, and
Tests/Architecture/MMCA.Common.Architecture.Tests/Domain/EntityModel/TenantEntityConventionTests.cs:20gates theTenantIdmarker. Common subclassesEntityConventionTestsBaseandImmutabilityTestsBase(Domain/EntityModel/EntityConventionTests.cs:12,ImmutabilityTests.cs:11). - Lever: closed 2026-10-19 (see Closed as over-engineering, 2026-10-19).
[closed] #11 · Security
- Gap: authorization is RBAC with capability indirection plus the opt-in, claim-trusting
OwnerOrAdminFilter(Source/Presentation/MMCA.Common.API/DependencyInjection.cs:85) rather than a resource/attribute policy engine; the ADR-037EncryptedStringConverteris unadopted in Source; the workflows carry no CodeQL or secret-scanning step; and the written threat model the rubric requires is absent repo-wide (an unmet criterion that counts against the score, recorded as not scheduled work under Deliberate and accepted). - [closed] Close the internal trust gaps.
- [accepted] (a) the broker consumer restores user id, roles and tenant from unsigned
MMCA-*headers (Source/Core/MMCA.Common.Infrastructure/Messaging/Consumers/ConsumerOriginRestore.cs:44,53; roles as plain text atSource/Core/MMCA.Common.Shared/Messaging/MessageHeaders.cs:31; zero HMAC or signature matches underMessaging), so any party able to publish to the broker can assert roles. Signing the headers is recorded under Deliberate and accepted, not scheduled; - [closed] (b) in-cluster gRPC forwards the user JWT over deliberate cleartext h2c (
Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:97-99). An opt-in app-layer service token ships (Source/Presentation/MMCA.Common.Grpc/Authentication/ServiceTokenServiceCollectionExtensions.cs:35,63,85,144, attached per client byServiceTokenHttpClientBuilderExtensions.cs:28), but it is HS256 over one key shared by every service (ServiceTokenSettings.cs:13,30), so any service can mint any service name, the transport stays cleartext, and only Store adopts it (zero matches inMMCA.ADC/Source): make the identity per-service or transport-level; - (c) tenant resolution defaults to claim-only (
Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:61-62, header opt-in documented at:18-19), with a tenant conformance base inMMCA.Common.Testing(Source/Hosting/MMCA.Common.Testing/Conformance/TenantResolutionTestsBase.cs:27, subclassed atTests/Hosting/MMCA.Common.Testing.Tests/Conformance/TenantResolutionConformanceTests.cs:11).
- [accepted] (a) the broker consumer restores user id, roles and tenant from unsigned
- Lever: closed 2026-10-19, item (b) included (see Closed as over-engineering, 2026-10-19); a future re-score must not mint a sub-item for the threat model, for signing the broker headers or for a per-service service identity.
[closed] #29 · Resilience & Business Continuity
- Gap: tested restores, RTO/RPO per service and measured production SLOs, which the framework's own guide records as consumer-IaC work (
common-RESILIENCE.md:5-6). The in-repo restore drill is a build gate and holds Maturity 4. - Lever: closed 2026-10-19; nothing is schedulable in-repo.
[closed] #17 · DevOps & Deployment
- Gap (both bands): the
sample-deployment-validatejob is a compile check (az bicep buildatci.yml:719and:723, job atci.yml:707) and is not among the 8 required contexts, nor is package-consumption; a release has no rollback beyond the irreversible nuget.org versions; and a library cannot deploy itself, so real CD-to-Azure lives in consumers. - Lever: closed 2026-10-19. A scheduled Azure smoke deploy of
samples/deploymentis recorded under Deliberate and accepted.
[closed] #30 · Compliance, Privacy & Governance
- Gap (both bands): the in-repo mechanism is complete (erasure extension point,
PiiRedactor, a fitness-gated erasure contract, DSAR export, audit trail with retention purge,PRIVACY.md, a fail-closed CSV export atSource/Presentation/MMCA.Common.API/Controllers/EntityControllerBase.cs:231,272, and a non-vacuous PII marking rule atTests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:10-14), but the audit-trail and export surfaces are opt-in,DataResidencyTestsBasehas only a self-test probe subclass (Governance/DataResidencyTestsBaseTests.cs:24), the lawful-basis inventory gatePiiInventoryTestsBase(Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Privacy/PiiInventoryTestsBase.cs:33) has only a self-test subclass (Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/Privacy/PiiInventoryTestsBaseTests.cs:14), and consent/lawful basis is absent (zero matches in Domain, Application and Infrastructure;PRIVACY.md:20states the framework records neither). - Lever: closed 2026-10-19; further maturity movement needs the consumer apps to carry the consent, lawful-basis and residency process that the framework's
[Pii]/IAnonymizableextension points feed (ADR-005). Account deletion now also revokes and anonymizes refresh sessions (see Resolved).
[closed] #5 · Vertical Slice Architecture
- Gap: Common is an SDK with no business use-case slices of its own, so slice cohesion is proven in the consumer repos, and handler bases that implement
ICommandHandler/IQueryHandlerover a generic parameter stay exempt from the slice rule by design (Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.Slices.cs:31). - Lever: closed 2026-10-19 (a slice rule with a frozen allowlist is recorded under Deliberate and accepted, not scheduled).
[closed] #12 · Performance & Scalability
- Gap: the rubric's load/stress-at-realistic-volumes criterion is met in part: the load tier (
Tests/Performance/MMCA.Common.LoadTests,OutboxThroughputLoadTests.csandPagedQueryLoadTests.cs) runs weekly or on dispatch only, on SQLite, and is not a merge gate (.github/workflows/load-tests.yml:16, cron17 4 * * 0), and Common carries no capacity-provisioning evidence. The opt-inMessageBus:PrefetchCount/ConcurrentMessageLimitbackpressure knobs (Source/Core/MMCA.Common.Infrastructure/Messaging/MessageBusSettings.cs:222,232) cover the backpressure half. - Lever: closed 2026-10-19 (a load tier on every pull request is recorded under Deliberate and accepted, not scheduled).
[closed] #13 · Observability & Operability
- Gap: alerts, dashboards and runbooks are provisioned and used only by deployers. The sample's SLO alerts, runbook and workbook (
samples/deployment/main.bicep:338) are compile-only in CI; the shippedObservabilityConventionTestsBaserequires a workbook by default (Source/Hosting/MMCA.Common.Testing.Architecture/Bases/Governance/ObservabilityConventionTestsBase.cs:60). - Lever: closed 2026-10-19.
[closed] #20 · Design System & UI Consistency
- Gap: the shared layout scoped CSS carries 42
!importantoverrides (28 inLayout/NavMenu.razor.css, 14 inLayout/MainLayout.razor.css) with raw nav colors, and a badge override block duplicated betweenLayout/MainLayout.razor.css:76andNavMenu.razor.css:274-292; removing the overrides is recorded under Deliberate and accepted. The brand hex is defined in C# and CSS (Source/Presentation/MMCA.Common.UI/Theme/BrandColors.cs:13,wwwroot/app.css:63), mirrored byTests/Presentation/MMCA.Common.UI.Tests/Theme/BrandColorTokenTests.cs:35-38rather than sourced from one token. - Lever: closed 2026-10-19.
[ ] #23 · Front-End Performance
- Make the WASM payload budget a merge gate in the consumers. The composite action ships in Common (
.github/actions/wasm-payload-budget/action.yml) and both consumers run it on pull requests (MMCA.ADC/.github/workflows/deploy.yml:476-497, budget 5,900 KB at:497;MMCA.Store/.github/workflows/deploy.yml:493-514, budget 5,700 KB), but in neither is it a required check (the required contexts are build-and-test, supply-chain, integration-tests and coverage). Kept open on 2026-10-19 because it costs one branch-protection setting per consumer and no code: the job is skipped on non-code pull requests (MMCA.ADC/.github/workflows/deploy.yml:478,if: ... needs.changes.outputs.code == 'true'), and a skipped job satisfies a required check, so documentation PRs are not blocked. Running it in Common's ownci.ymlis not applicable (see Deliberate and accepted). The web-vitals budgets sit at the good-band values (LCP 2500, FCP 1800, TTFB 800, CLS 0.1, INP 200;Tests/Presentation/MMCA.Common.UI.E2E.Tests/WebVitals/WebVitalsE2ETests.cs:19-23) on all three required engines. No component in Common UI overridesShouldRender.- Evaluated and not adopted: route-level WASM lazy loading. It needs a framework lazy-module registry, an
OnNavigateAsynchook in Common'sRoutes.razorand deferred DI registration, and in ADC the landing module (Conference) and a layout-rendered module (Engagement) must load eagerly anyway, so little would be deferred. The payload-budget action is the #23 lever instead. Revisit when a consumer gains a large module that is neither the landing route nor rendered by the layout.
- Evaluated and not adopted: route-level WASM lazy loading. It needs a framework lazy-module registry, an
[~] #24 · Forms, Validation & UX Safety
- Bring the shared change-password card to client/server parity. Shipped in v1.245.0 (Common #567):
ValidateNewPasswordappliesPasswordComplexity,MaxLengthdefaults toPasswordComplexity.MaximumLength(ChangePasswordCard.razor.cs:59), and a server refusal naming a password field lands on that field as well as in the toast. The original finding follows.ChangePasswordCardchecks onlyLength >= MinLengthon the client (Source/Presentation/MMCA.Common.UI/Components/Auth/ChangePasswordCard.razor.cs:88-91) and leavesMaxLengthunbounded by default (:45), while the server appliesStrongPasswordRules(complexity plus a 128-character cap,Source/Core/MMCA.Common.Application/Validation/CommonValidationRules.cs:191,197) to change-password (MMCA.ADC/Source/Modules/Identity/MMCA.ADC.Identity.Application/Users/Validation/ChangePasswordRequestValidator.cs:18). A password the server refuses passes the form, and the refusal surfaces as a toast (ChangePasswordCard.razor.cs:130) rather than a field message. HaveValidateNewPasswordcall the sharedPasswordComplexityrule (Source/Core/MMCA.Common.Shared/Auth/PasswordComplexity.cs:52) and defaultMaxLengthto the server cap, so the client rejects what the server rejects with a field-level message. Deferred to the next Common release, not scheduled now. - Gap beyond the lever (closed 2026-10-19): server FluentValidation messages are English literals (
Source/Core/MMCA.Common.Application/Auth/Validation/LoginRequestValidator.cs:16), Register has no unsaved-changes guard, the shipped forms gate matches string markers, and forgiving input (masks, undo) is thin. See Closed as over-engineering, 2026-10-19.
[closed] #33 · Developer Experience & Inner Loop
- Gap: the criteria are met with several small, documented gaps: source mode binds a consumer to Common's last-built Debug reference assembly and needs a manual rebuild (
MMCA.Common/CONTRIBUTING.md:150), contributors are told to expect CI-only analyzer and restore round-trips (CONTRIBUTING.md:167), the AppHost stalls when launched headless and its test tier fails the run but is not a required check (.github/workflows/ci.yml:851-852), package-consumption is not a required check either, and the agent guardrail hooks live in the workspace harness rather than in Common (Common's.claude/settings.local.jsonis an allow-list with stale entries and configures no hooks). - Lever: closed 2026-10-19.
[closed] #34 · Architecture Governance & Documentation
- Gap: governance prose defers to the generated
FACTS.mdfor version, package count and fitness counts (FACTS.md:4,14,19,81,84) and to the ADR index for the ADR range (Website/docs-src/adr/README.md:6); an ADR template ships atWebsite/docs-src/adr/_template.md, and the workspace architecture map marks its body figures historical and points atFACTS.md(Docs/Architecture/ArchitecturalAnalysis.md:3). Some ADR index rows have grown into long revision logs, and Common carries no dedicated change-coupling or tech-debt-ledger gate of its own. - Lever: closed 2026-10-19.
[closed] #27 · Internationalization (i18n)
- Gap: breadth: two locales with zero RTL support under
Source/Presentation, a two-category plural mechanism with the CLDR selector deferred (Source/Presentation/MMCA.Common.UI/Globalization/StringLocalizerPluralExtensions.cs:14-18), and the culture list hard-coded in the framework (Source/Core/MMCA.Common.Shared/Globalization/SupportedCultures.cs:18). Time zones, emitted error codes foresand the default emails are closed (see Resolved). - Lever: closed 2026-10-19.
Implementation band
Every category at implementation <= 8, ranked by implPriority = max(0, 9 − implementation) × weight. Four of these categories (#1, #17, #30, #31) also sit in the maturity band; this band records only their implementation half. The rows keep their 2026-10-18 scores until the next re-score; as of the 2026-10-19 triage only #23 carries scheduled work, #24's lever has shipped, and every other row is closed (see Closed as over-engineering, 2026-10-19).
| implPriority | # | Category | w | Impl | Recorded lever |
|---|---|---|---|---|---|
| 3 | #1 | SOLID Principles | 3 | 8 | not yet identified (the constructor ceiling is subclassed at Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencies/FrameworkConstructorDependencyTests.cs:25 and the LSP throw half at Governance/Substitutability/CommonSubstitutabilityTests.cs:10; OCP, ISP and type-sniffing have no gate, and ApplicationDbContext resolves collaborators through IServiceProvider) (#1 is also in the maturity band) |
| 3 | #4 | Domain-Driven Design | 3 | 8 | not yet identified (strategic DDD is realized downstream, the Notifications family is the only in-repo aggregate, and the tenant identifier is a plain string by decision) |
| 3 | #11 | Security | 3 | 8 | closed 2026-10-19 (per-service or transport-level service identity is not adopted; see Closed as over-engineering, 2026-10-19): the shipped token is opt-in HS256 over one shared key (Source/Presentation/MMCA.Common.Grpc/Authentication/ServiceTokenServiceCollectionExtensions.cs:35, ServiceTokenSettings.cs:13,30) over cleartext h2c (Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:97-99), adopted by Store only; unsigned broker origin headers (ConsumerOriginRestore.cs:53) and the absent written threat model are accepted, not scheduled; ownership is opt-in claim-trusting OwnerOrAdminFilter (Source/Presentation/MMCA.Common.API/DependencyInjection.cs:85) rather than ABAC, and the ADR-037 EncryptedStringConverter is unadopted in Source |
| 3 | #29 | Resilience & Business Continuity | 3 | 8 | tested restores, RTO/RPO per service, and measured production SLOs, recorded by the framework's own guide as consumer-IaC work (common-RESILIENCE.md:5-6) |
| 2 | #5 | Vertical Slice Architecture | 2 | 8 | not yet identified (Common has no business use-case slices of its own, and generic-parameter handler bases stay exempt from the slice rule, Source/Hosting/MMCA.Common.Testing.Architecture/Rules/Cqrs/ArchitectureRules.Slices.cs:31) |
| 2 | #12 | Performance & Scalability | 2 | 8 | not yet identified (the load tier runs weekly or on dispatch only, on SQLite, and is not a merge gate by decision, .github/workflows/load-tests.yml:16; the opt-in backpressure knobs at Source/Core/MMCA.Common.Infrastructure/Messaging/MessageBusSettings.cs:222,232 cover the backpressure half) |
| 2 | #13 | Observability & Operability | 2 | 8 | not yet identified (the sample ships SLO alerts, a paired runbook and an SLO workbook, samples/deployment/main.bicep:177,196,237,280,338, gated by Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/SampleDeploymentObservabilityTests.cs:12,24; production alerting and dashboards are deployer-owned) |
| 2 | #17 | DevOps & Deployment | 2 | 8 | none scheduled in-repo: the Bicep job is a compile check and is not among the required contexts (the validate job at ci.yml:707, az bicep build at :719 and :723); a scheduled Azure smoke deploy is accepted, not adopted |
| 2 | #20 | Design System & UI Consistency | 2 | 8 | not yet identified (42 !important overrides kept by decision, Layout/NavMenu.razor.css and Layout/MainLayout.razor.css; brand hex mirrored in C# and CSS, Theme/BrandColors.cs:13 and wwwroot/app.css:63) |
| 2 | #23 | Front-End Performance | 2 | 8 | make the consumers' wasm-payload-budget job a required check (PR-only today, MMCA.ADC/.github/workflows/deploy.yml:476, MMCA.Store/.github/workflows/deploy.yml:493); running it in Common's ci.yml is not applicable, and route-level lazy loading is evaluated and not adopted |
| 2 | #24 | Forms, Validation & UX Safety | 2 | 8 | shipped in v1.245.0 (Common #567): ChangePasswordCard applies PasswordComplexity and defaults MaxLength to the server cap (Source/Presentation/MMCA.Common.UI/Components/Auth/ChangePasswordCard.razor.cs:59); re-score pending; the residual gaps are closed 2026-10-19 |
| 2 | #30 | Compliance, Privacy & Governance | 2 | 8 | not yet identified (#30 is also in the maturity band) |
| 2 | #31 | Cost Efficiency / FinOps | 2 | 8 | documented accepted cap, not scheduled work (see Deliberate and accepted); the default-ON probe-telemetry filter and its span processor ship in-repo (Aspire/Extensions.Telemetry.cs:19,129,235: config key, ProbeTelemetryFilterProcessor registration and default-ON read), and the category ranks first on the maturity band |
| 2 | #33 | Developer Experience & Inner Loop | 2 | 8 | not yet identified |
| 2 | #34 | Architecture Governance & Docs | 2 | 8 | not yet identified |
| 1 | #27 | Internationalization (i18n) | 1 | 8 | not yet identified (no RTL support; two locales with a hard-coded culture list, SupportedCultures.cs:18) |
Σ implPriority = 35 across the 16 live rows (3 + 3 + 3 + 3 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 1).
Maturity band
Every category at maturity < 4, ranked by priority = (4 − maturity) × weight.
| Priority | # | Category | w | Maturity | Recorded lever |
|---|---|---|---|---|---|
| 4 | #31 | Cost Efficiency / FinOps | 2 | 2 | documented accepted cap, not scheduled work (see Deliberate and accepted): no cost, budget or FinOps convention is enforced by review or CI anywhere in-repo, and the unmet criteria are consumer/IaC execution |
| 3 | #1 | SOLID Principles | 3 | 3 | not yet identified: SRP (the constructor ceiling at Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencies/FrameworkConstructorDependencyTests.cs:25), DIP and the LSP throw half (Governance/Substitutability/CommonSubstitutabilityTests.cs:10) are enforced automatically, while OCP, ISP and LSP type-sniffing have no automated gate and S107/S1200 sit at severity suggestion (.editorconfig:478,492) |
| 2 | #17 | DevOps & Deployment | 2 | 3 | the Bicep validate job is compile-only and absent from the 8 required contexts, so the CD/IaC-apply axis is not automatically enforced; a library cannot deploy itself |
| 2 | #30 | Compliance, Privacy & Governance | 2 | 3 | the governing process (personal-data inventory, consent capture, residency verification) is absent in-repo or consumer-resident, so the category as a whole is not automatically enforced |
Σ priority = 11 across the 4 maturity-band rows (4 + 3 + 2 + 2).
Deliberate and accepted
Recorded decisions and deferred findings: each is real, none is scheduled work.
[accepted] #31 · Cost Efficiency / FinOps: held at Maturity 2 / Implementation 8 by documented acceptance
The computed maturity 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). The in-repo levers ship and are documented: the Telemetry:TracesSampleRatio OTel sampler knob, the outbox per-message log at Debug, the default-ON probe-telemetry filter, a fifth rubric §31 cost knob (Telemetry:EnablePollyDurationMetrics, CHANGELOG.md:233), and the cost guide's cost-attribution-tag plus cost-guard samples (common-COST.md). No cost convention is enforced by review or CI (Tests/Architecture has zero cost/FinOps matches; the .github hits are incidental prose, for example the comment at ci.yml:330), so Maturity stays 2. Further movement is a consumer-side lift. Reversing this entry is a user decision.
[accepted] #11 · A written threat model is not scheduled work
The three §11 threat-model documentation items are out of the workspace program by user decision and are not to be re-scheduled without asking. The rubric's §11 criteria name a written threat model, no such document exists in MMCA.Common (zero "threat model" matches in SECURITY.md), and its absence is a named §11 red flag, so the criterion stays unmet and counted: #11 holds at Maturity 4 / Implementation 8 with its implementation-band row at implPriority 3. The #11 lever does not carry the threat model as schedulable work, and a re-score must not mint a sub-item for it.
[accepted] Options cut as over-engineering
Each was weighed and declined by user decision; none is scheduled, and a re-score must not re-propose one as a lever.
- #11 (a): signing the broker origin headers. It earns no score (8 to 9 needs the excluded threat model). Exploiting the unsigned headers needs broker send rights, which narrowing Store's Service Bus SAS rules to Send/Listen would cover more cheaply, and signing would cost key distribution and rotation in every service.
- #17: a scheduled Azure smoke deploy of
samples/deployment. It adds new Azure credentials, ongoing spend and a new CI failure path for a library, while ADC and Store prove the same Bicep/OIDC path in production. - Item 15b: cache invalidation in
ChildEntityServiceBase. Only needed if Store re-adoptsIUiReadCache; build it then. - #20: removing the 42
!importantoverrides. They exist to beat MudBlazor's own styles, and removing them carries a real risk of visual regressions in both apps. - #1: an ISP member-count ceiling. A metric proxy that invites artificial interface splits; the LSP rule is kept.
- #5: a slice rule with a frozen allowlist. Its first act would be to freeze the existing violations; low confidence.
- #12: the load tier on every pull request. SQLite numbers on shared runners make a flaky merge gate and cost Actions minutes; the tier stays scheduled.
[accepted] Closed as over-engineering, 2026-10-19
Each row below was weighed against one developer, three consumer apps and about 67 peak users. None has a defect behind it that the existing gates miss, and each lever would add permanent machinery for a score point. Scores are unchanged; a re-score records these as the cap and must not re-propose one without new evidence (a named re-open trigger, a real incident, or an explicit ask).
- #1 SOLID. The automatable halves (SRP ceiling, DIP layers, the LSP throw rule) are gated; OCP, ISP and type-sniffing have only metric proxies (the ISP ceiling is already declined). Raising S107/S1200 would fire on every DI constructor the dependency ceiling already governs. Moving
ApplicationDbContextoff itsIServiceProviderresolution is a refactor with no observed defect behind it, and pullingMMCA.Common.UI.MauiintoCommonArchitectureMapwould need a MAUI TFM in the architecture test project for thin Essentials wrappers. - #4 DDD. Strategic DDD is a property of an application, and the consumers carry it. A strongly typed tenant id is a lockstep-wide breaking change for no observed defect; the
TenantIdmarker gate stays. - #11 (b) per-service or transport-level gRPC identity. Per-service keys mean key distribution and rotation in every service for traffic that never leaves the Container Apps environment, the same cost profile that declined broker header signing. The shared-key token stays opt-in, and ADC's anonymous internal surfaces are accepted with compensating controls in the ADC backlog. Re-open: a service reachable from outside the environment, or a second team owning a service.
- #5, #12, #13, #17, #29, #30. Each open criterion is consumer-resident or needs a deployed app (slices, load at realistic volume, production alerting, CD to Azure, tested restores and SLOs, consent and residency). ADC and Store meet them where they exist. Nothing further is schedulable in a NuGet library.
- #20 Design System. The
!importantoverrides are kept by decision (above); the brand hex mirrored in C# and CSS is pinned byBrandColorTokenTests, so drift already fails CI. - #24 residuals. Localized server validation messages, an unsaved-changes guard on Register, AST-level form gates and input masks. The client mirrors the server rules, so a server message is rarely seen; a Register form holds three fields.
- #27 i18n. Two LTR locales (en-US, es): RTL support, the CLDR plural selector and a configurable culture list are work for a third locale. Re-open: a third locale, or any RTL locale.
- #33 DevX and #34 Governance. The DevX gaps are documented workstation gotchas (stale Debug ref assembly, CI-only analyzer round-trips, headless AppHost) with no cheap fix; the long ADR index rows are cosmetic.
- FR-1 package split. A breaking, lockstep-wide re-shape for three consumers that between them use most of the bundled providers. The cost of the shared graph is advisory suppressions, handled per advisory. Re-open: an external adopter, or an advisory in an unused provider that cannot be suppressed.
- FR-2 throwing
Result<T>.Value.Valueis declaredT?(Source/Core/MMCA.Common.Shared/Abstractions/Result.cs:207), so under nullable analysis and TreatWarningsAsErrors an unchecked dereference on a reference type is already a build error. A throwing getter would reintroduce exceptions into the Result API. - FR-3
TResult : Resultconstraint. The architecture rule already asserts that command and query results derive fromResult; a generic constraint would duplicate that gate with a breaking signature change. - FR-4 preview extension types. The watch condition is met: extension members are generally available in C# 14, the default language version for
net10.0, so consumers do not needLangVersionpreview to call theextension(IServiceCollection)registrations. - FR-6
MMCA.Common.UI.Mauitests. The capability contracts and fallbacks are tested inMMCA.Common.UI.Tests; what remains are thin Essentials wrappers verified on device, and ADC and Store now compile their MAUI heads weekly (maui-audit.yml).
[n/a] #23 · Running wasm-payload-budget in Common's own ci.yml
Common has no WASM app of its own, so there is no payload for its ci.yml to measure; the composite action ships here (.github/actions/wasm-payload-budget/action.yml) for the consumers to run. The open #23 item is the consumers' required check only. The category score is unaffected.
[accepted] Dual-registry publishing, and the release-workflow filename is load-bearing
Every release publishes to both nuget.org and GitHub Packages (ADR-053). The nuget.org leg uses keyless OIDC trusted publishing: NuGet/login exchanges the workflow's id-token for a short-lived, single-use API key, so there is no stored NUGET_API_KEY secret (id-token: write at release.yml:23 and :160, NuGet/login at :133 and :249, nuget.org pushes at :140 and :257, the API key read from steps.nuget-login.outputs). The trusted-publishing policy on nuget.org is pinned to this workflow file, so renaming or relocating release.yml breaks the token exchange by design; the constraint is recorded in-file at release.yml:126-128 (and :243-245 for the MAUI job), at its point of use. FACTS.md:20, emitted by FactsGenerator.cs:208, names nuget.org alongside GitHub Packages.
[accepted] Consumers skipped v1.128.0 through v1.130.0
MMCA.ADC, MMCA.Store and MMCA.Helpdesk went from 1.127.0 straight to 1.131.0 and never pinned 1.128.0, 1.129.0 or 1.130.0 (CHANGELOG.md:9-15). This is ADR-016 lockstep behavior, not drift: 1.128.0 was distribution-only (assemblies byte-identical to 1.127.0), and 1.129.0 and 1.130.0 were superseded within the same day by 1.131.0. Recorded so an audit reading the version ladder does not score the window as three missed lockstep sweeps.
[accepted] Domain design decisions (#4)
- Cross-aggregate navigation is deliberately not forbidden. Aggregate roots reference other roots via
[Navigation]FK references loaded by the navigation populators (ADR-002), for exampleSession.Event/Session.Roomin ADC; a strict rule would contradict ADR-002 and break the consumers' aggregates. Money.operator+throws on a currency mismatch by design (a C# operator cannot returnResult<T>);Money.Add(...)is the documentedResult-returning path (covered byAddition_DifferentCurrencies_ThrowsInvalidOperationException/Add_DifferentCurrencies_ReturnsFailure).BaseDomainEvent.DateOccurredreads the ambient clock by design. A domain event's occurrence instant is the moment the aggregate raises it, the correct event-sourcing/audit semantic (four domain tests enforce it); moving the stamp to the SaveChanges boundary would turn occurrence-time into persist-time. Documented inBaseDomainEvent.
[accepted] Framework decisions kept as they are
- The ADR-017 in-process idempotency lock fallback stays:
IdempotencyFilteruses the striped per-process semaphore only when a host registers noIDistributedLock. - The gated Scheduler / AuditTrail table mapping stays: both tables map only when their feature is enabled.
- §16 streamed inspection covers fragments, not the assembled answer (
Source/Core/MMCA.Common.AI/Guardrails/ContentPolicyGuardrail.cs:176-189): deliberate and recorded, not scheduled. The other two §16 residuals (a host-side way to require the injection policy rather than any guardrail,Source/Core/MMCA.Common.AI/DependencyInjection.cs:208-217,AiSettings.cs:110; a non-empty default or a documented reason for the emptyBlockedResponsePatterns,Guardrails/ContentPolicySettings.cs:57, short-circuit atGuardrails/ContentPolicyGuardrail.cs:269) are the 9→10 rung, recognition at re-score time rather than scheduled work. Retrieval is out of scope by ADR-120 (120-governed-chat-client-boundary.md:388-391) and is unexercised, not unmet. [ServiceContract]adoption on the consumers'*.Contractsprojects is optional: all three consumers subclassServiceContractPurityTestsBase, and the attribute-driven rule stays a documented ratchet until a type carries the attribute.- Two performance refactors are deferred with rationale: an interceptor
DetectChangesreduction (the second detection pass may be load-bearing for audit stamps; it needs a dedicated EF-internals investigation, and the failure mode is silent data loss), and a by-id fast path around the dynamic query pipeline (a larger refactor, with the pressure mostly removed by ADR-040).
[closed] Full-review findings FR-1..FR-7 (closed 2026-10-19)
Each is real and none is scheduled; each was closed on 2026-10-19 (reasons under Closed as over-engineering, 2026-10-19). IDs follow the C-1..C-7 precedent (FR = full review).
- [closed] 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 SQLite advisory GHSA-2m69-gcr7-jv3q is the recorded 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.) - [closed] FR-2 (§15) -
Result<T>.Valuethrow-on-failure guard. Reading.Valueon a failed result silently returnsnull/default; 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. (Effort M, breaking.) - [closed] 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 by an architecture rule asserting command/query result types derive fromResult. (Effort M, breaking.) - [closed] 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.) - [closed] 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 are verified only on-device, and the package sits outsideCommonArchitectureMap(CommonArchitectureMap.cs:9), so the LSP gate does not cover it either. Options: a windows-job unit tier for the wrapper logic, or a documented on-device smoke checklist. (Effort M.)
Mostly consumer-assessed: #21 Accessibility, #26 Front-End Security
The shared Common.UI surface is scored here; each app's concrete posture is scored downstream. For #26, a host may register its own ICspPolicyProvider (first-registered provider wins through TryAddSingleton, Aspire/Security/SecurityHeaders.cs:288), so each app's concrete CSP is consumer-assessed; for #21, axe breadth covers the gallery's representative states, and deep consumer states are scored in the consumer repos.
Resolved
Categories at maturity 4 AND implementation >= 9 (protect them, do not regress), then the closed items inside categories that are still open.
| # | Category | M / I | Evidence |
|---|---|---|---|
| #2 | Design Patterns | 4 / 9 | Specification composition with EF-translatable And/Or/Not, Domain/Specifications/Specification.cs:62/88/114 |
| #3 | Clean Architecture | 4 / 9 | Compile-time layer guards Source/Build/MMCA.Common.LayerEnforcement.targets:20 plus NetArchTest LayerDependencyTests.cs:9 |
| #6 | CQRS & Event-Driven | 4 / 9 | EF-backed inbox dedup, EfInboxStore.cs:18, check at IntegrationEventConsumer.cs:42 and MarkProcessedAsync at :78; the inbox is opt-in (MessageBusSettings.cs:64), which holds Implementation at 9 |
| #7 | Microservices Readiness | 4 / 9 | Anti-Corruption Layer named at Source/Presentation/MMCA.Common.Grpc/DependencyInjection.cs:21-22,71,78-80 and README.md:71, Strangler Fig route in ADR-007:23 and ADR-008:11,41; extraction rule under a required check (MicroserviceExtractionTests.cs:11) |
| #8 | Data Architecture | 4 / 9 | Required-gate CI migration-apply with outcome assertions, ci.yml:582,601, plus MigrationApplyProofTests.cs:95 and the cascade-soft-delete rule CascadeSoftDeleteConventionTestsBase.cs:29-31 |
| #9 | API & Contract Design | 4 / 9 | OpenAPI committed-baseline diff OpenApiBaselineTests.cs:45-77 plus the [ServiceContract] purity rule ArchitectureRules.Contracts.cs:32; regenerate the baseline deliberately in the pull request that changes the contract |
| #10 | Messaging & Integration Architecture | 4 / 9 | Distributed idempotency lock Infrastructure/Concurrency/RedisDistributedLock.cs:36 behind IdempotencyFilter.cs:31-34; layered broker retry at Infrastructure/DependencyInjection.cs:957,961 |
| #14 | Testability & Test Strategy | 4 / 9 | 68.3% line-coverage floor ci.yml:409 and the --minimum-expected-tests 2000 guard at :163 |
| #15 | Best Practices & Code Quality | 4 / 9 | Five analyzers at error with TWAE and audit=all, Directory.Build.props:7-13, blocking vulnerability audit ci.yml:144; FR-2 stays recorded under Deliberate and accepted |
| #16 | AI-Native Application Architecture | 4 / 9 | ContentPolicyGuardrail with startup pattern validation, Source/Core/MMCA.Common.AI/Guardrails/GuardrailServiceCollectionExtensions.cs:67-70, plus the prompt-contract pin PromptContractPinTestsBase.cs:59-62 and golden-replay gate GoldenReplayTestsBase.cs:57-62 |
| #18 | UI Architecture & Components | 4 / 9 | Smart DataGridListPageBase<TDto> over dumb primitives, MMCA.Common.UI/Pages/Common/DataGridListPageBase.cs:18, bUnit-gated in Tests/Presentation/MMCA.Common.UI.Tests/Components/ |
| #19 | State Management & Data Flow | 4 / 9 | Client-side staleness policy IUiReadCache.cs:32,51,59,66 plus the live dirty accessor UnsavedChangesGuard.razor:34,36,52 |
| #21 | Accessibility (a11y) | 4 / 9 | Required chromium axe gate over both themes, DarkModeE2ETests.cs:30, and role="status" loading state Components/PageLoadingState.razor:3 |
| #22 | Responsive & Cross-Browser | 4 / 9 | All three engines are required merge gates, .github/workflows/ci.yml:232 (job ui-e2e at :223) |
| #25 | Navigation & Information Arch | 4 / 9 | Route/auth drift gate NavigationContractTests.cs:29,44 plus the typed route NotificationInbox.razor:2 (NavigationFlow.md:21) |
| #26 | Front-End Security | 4 / 9 | Hardened default CSP Aspire/Security/SecurityHeaders.cs:67-69, pinned by SecurityHeadersMiddlewareTests.cs:157-161 |
| #28 | Front-End Testing & Quality | 4 / 9 | Markup-snapshot regression tier Source/Hosting/MMCA.Common.Testing.UI/Infrastructure/MarkupSnapshot.cs over PrimitivesSnapshotTests.cs |
| #32 | Dependency & Supply-Chain | 4 / 9 | MassTransit-major gate DependencyVersionTestsBase.cs:24-37, blocking SBOM release.yml:107,225, signed provenance release.yml:98,217 |
| #1 (item) | Constructor-dependency ceiling subclassed in Common | 3 / 8 | Tests/Architecture/MMCA.Common.Architecture.Tests/Cqrs/ConstructorDependencies/FrameworkConstructorDependencyTests.cs:25 (services 7 at :33, controllers 5 at :37, handlers 7 at :40) |
| #1 (item) | Narrowed constructors and per-engine data-source strategy | 3 / 8 | AuthenticationServiceBase.cs:64-71 (7 parameters); Persistence/DataSources/Engines/IDataSourceEngine.cs:18 via DataSourceEngines.For; engine-neutral RequestExplicitKeyInsert() at IUnitOfWork.cs:45 |
| #4 (item) | Result-returning factories fitness-gated | 4 / 8 | DomainFactoriesReturnResult and the aggregate rules, AggregateConventionTestsBase.cs:14-24 |
| #5 (item) | Slice-cohesion fitness function | 4 / 8 | ArchitectureRules.Slices.cs:35,65 with fixture self-tests SliceCohesionFitnessTests.cs:21,32,43 |
| #11 (item) | CI vulnerability gate, security invariants, OWASP note | 4 / 8 | ci.yml:144 via .github/actions/nuget-vulnerability-audit/action.yml:39,63; AnonymousEndpointTestsBase subclassed in Common and every consumer; SECURITY.md:84-86 |
| #11 (item) | Secure-by-default RequireHttpsMetadata |
4 / 8 | Single AddForwardedJwtBearer definition at API/Startup/WebApplicationBuilderExtensions.Authentication.cs:52 |
| #11 (item) | SECURITY.md documents the shipped Key Vault helper |
4 / 8 | SECURITY.md:83-86 (AddCommonKeyVaultConfiguration, no-op unless KeyVault:Uri) |
| #11 (item) | Claim-only tenant resolution by default, tenant conformance base | 4 / 8 | Source/Core/MMCA.Common.Infrastructure/Persistence/Tenancy/TenancySettings.cs:61-62; Source/Hosting/MMCA.Common.Testing/Conformance/TenantResolutionTestsBase.cs:27, subclassed at TenantResolutionConformanceTests.cs:11 |
| #12 (item) | Performance regression gate as a required check | 4 / 8 | ci.yml:310 job matching the required context, baseline Tests/Performance/perf-baseline.json:3-13 |
| #13 (item) | SLO alerting as code with a paired runbook | 4 / 8 | samples/deployment/main.bicep:177,196,237,280 gated by SampleDeploymentObservabilityTests.cs:12; outbox meter registered at Extensions.Telemetry.cs:308 |
| #13 (item) | Sample SLO workbook | 4 / 8 | samples/deployment/main.bicep:338 (Microsoft.Insights/workbooks) required by SampleDeploymentObservabilityTests.cs:24 (RequireWorkbook => true) |
| #17 (item) | Reference Bicep secret binding, Dependabot, gated release | 3 / 8 | Secrets array samples/deployment/main.bicep:151-152 with binding at :164; .github/dependabot.yml:4,85; environment: release at release.yml:19,156 |
| #18 (item) | EditorRequired convention check on shared components |
4 / 9 | Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/EditorRequiredParameterConventionTests.cs:26 |
| #20 (item) | Dark-palette contrast gate; MudBlazor as the only component library | 4 / 8 | Dark PrimaryContrastText/ErrorContrastText at Theme/MMCATheme.cs:66,93 locked by Layout/DarkModeE2ETests.cs:30; no Bootstrap in the RCL (wwwroot/app.css:108) |
| #20 (item) | No raw brand hex outside the two token definitions | 4 / 8 | Layout/ReconnectModal.razor.css:100 reads var(--mmca-primary); #1565C0 only at Theme/BrandColors.cs:13 and wwwroot/app.css:63, pinned by BrandColorTokenTests.cs:35-38 |
| #20 (item) | Zero inline style declarations, gated | 4 / 8 | Tests/Architecture/MMCA.Common.Architecture.Tests/Ui/ComponentConventions/InlineStyleTests.cs:12 over Bases/Ui/InlineStyleTestsBase.cs |
| #23 (item) | Packaged-asset hygiene and grid virtualization | 4 / 8 | wwwroot about 398 KB with a Content Remove for .map files; opt-in virtualization DataGridListPageBase.cs:155,643 |
| #23 (item) | Good-band web-vitals budgets on three engines | 4 / 8 | WebVitals/WebVitalsE2ETests.cs:19-23 (2500/1800/800/0.1/200), required ui-e2e at ci.yml:232 |
| #23 (item) | Consumers run the shipped WASM payload budget | 4 / 8 | MMCA.ADC/.github/workflows/deploy.yml:476,494 (5,900 KB at :497); MMCA.Store/.github/workflows/deploy.yml:493,511 |
| #24 (item) | Password length cap and guard wiring | 4 / 8 | ResetPasswordModel.cs:27-28 matches CommonValidationRules.cs:197; guard at RoleAdminEdit.razor:7 and NotificationSend.razor:25 |
| #24 (item) | Change-password card at client/server parity | 4 / 8 | ChangePasswordCard.razor.cs:59 (MaxLength = PasswordComplexity.MaximumLength), complexity in ValidateNewPassword, server refusal on the field (Common #567, v1.245.0) |
| #9 (item) | Parameter-aware OpenAPI breaking-change detector | 4 / 9 | Source/Hosting/MMCA.Common.Testing/Conformance/OpenApiBreakingChangeDetector.cs:271 (RemovedParameter plus six parameter and request-body kinds; Common #567), adopted by ADC and Store at v1.245.0 |
| #30 (item) | Refresh sessions revoked and anonymized on account deletion | 3 / 8 | DeleteUserHandlerBase through RefreshSessionRevocation.cs:77 and IRefreshSessionStore.GetAllByUserAsync (IRefreshSessionStore.cs:60; Common #567), forwarded by ADC #280 and Store #213 |
| #24 (item) | One Unicode-aware password rule on the auth pages, client and server | 4 / 8 | Shared/Auth/PasswordComplexity.cs:52, pinned by PasswordRuleParityTests.cs:36; resource-keyed auth errors RegisterModel.cs:16; confirm-before-revoke Sessions.razor.cs:130,195 |
| #26 (TD-08) | Same-origin data-call proxy adopted by ADC and Store | 4 / 9 | Framework half Source/Presentation/MMCA.Common.UI.Web/SameOriginProxy/SameOriginApiProxyEndpoint.cs:29; MMCA.ADC/Source/Hosts/UI/MMCA.ADC.UI.Web/Program.cs:161,284, MMCA.Store/Source/Hosts/UI/MMCA.Store.UI.Web/Program.cs:153,262, both pinning 1.244.0 (MMCA.ADC/Directory.Packages.props:128, MMCA.Store/Directory.Packages.props:20) |
| #27 (item) | UTC instants rendered in the viewer's time zone | 4 / 8 | Source/Presentation/MMCA.Common.UI/Services/Culture/ViewerTimeZone.cs:18 (registered at MMCA.Common.UI/DependencyInjection.cs:198), used at NotificationInbox.razor:58, NotificationList.razor:82, Sessions.razor.cs:244 |
| #27 (item) | Pluralization and the plural-sentence convention check | 4 / 8 | Globalization/StringLocalizerPluralExtensions.cs:40, 6 facts at StringLocalizerPluralExtensionsTests.cs:43, plus PluralSentenceResourceTests.cs:18 |
| #27 (item) | Emitted error codes covered for es |
4 / 8 | Source/Presentation/MMCA.Common.API/Resources/ErrorResources.resx and ErrorResources.es.resx (81 entries each, Auth.InvalidCredentials at :115), gated by ErrorResourceCoverageTests.cs:158 |
| #27 (item) | Localized default transactional emails | 4 / 8 | ForgotPasswordHandlerBase.cs:117,136 (localized subject, pluralized minutes), Users/Emails/UserEmailResources.cs:32 (recipient PreferredCulture), SendEmailConfirmationHandlerBase.cs:156 |
| #29 (item) | Build-gated restore drill and fault injection | 4 / 8 | DatabaseRestoreDrillTests.cs:27; fault injection ResilienceCircuitBreakerFaultInjectionTests.cs:17-61 |
| #30 (item) | Erasure extension point, PII redaction, outbox purge | 3 / 8 | PiiErasureContractFitnessTests.cs:19-40, Domain/Privacy/PiiRedactor.cs:24-142, OutboxCleanupService.cs:19 |
| #30 (item) | PRIVACY.md, fail-closed CSV export, non-vacuous PII marking rule |
3 / 8 | MMCA.Common/PRIVACY.md; EntityControllerBase.cs:231,272; Governance/PiiConventionTests.cs:10-14 |
| #33 (item) | Glob-generated UseLocalMMCA package swap list |
4 / 8 | build/LocalSource/MMCA.Common.LocalSource.targets:32, imported at each consumer's Directory.Build.targets:6 |
| #34 (item) | CHANGELOG backfill, dual-registry FACTS string, required-gates doc | 4 / 8 | CHANGELOG.md:691,716; FactsGenerator.cs:208 → FACTS.md:20; CONTRIBUTING.md:57-66,113-123 |
| #34 (item) | ADR template and the analysis doc's pointer to FACTS.md |
4 / 8 | Website/docs-src/adr/_template.md; Docs/Architecture/ArchitecturalAnalysis.md:3 marks its body figures historical |
| CD-1 | GetAllForLookupAsync forwards its predicate |
n/a | Source/Core/MMCA.Common.Application/Services/EntityQueryService.cs:278-282, test EntityQueryServiceTests.cs:462-480 |
| CD-2 | Lookup projection over a value-object property | n/a | Non-string name property projected in its CLR type and formatted in memory, EFReadRepository.cs:222,280,295 |
| FR-5 | Cascade soft-delete semantics (#8) | n/a | Opt-in DeleteChildren<TChild,TChildId> at AuditableAggregateRootEntity.cs:273 forced per aggregate by CascadeSoftDeleteConventionTestsBase.cs:29-31 |
| FR-7 | CS1591 ratchet mechanism (#34) | n/a | No repo-wide CS1591 suppression, Directory.Build.props:27-30 (NoWarn is RMG020;S8970;RS0041); 11 Source csproj files still carry their own CS1591 NoWarn, the per-project long tail |
| C-1..C-7 | Defect fixes with pinning tests | n/a | Lockout-backoff clamp LoginProtectionService.cs:54-58 (C-1), query failure metric LoggingQueryDecorator.cs:39 (C-3), plus the OAuth return URL, child-service bearer token, lookup escaping, purge clock and session-cookie clock fixes (C-2, C-4..C-7) |