Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/data/search-index.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions docs-src/onboarding/00-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ then applied mechanically (`Tools/invtool/classify.ps1` → `00-group-taxonomy.m
| 4 | [Domain & Integration Events + Outbox Dual-Dispatch](group-04-events-outbox.md) | 38 | Event contracts, dispatcher, transactional outbox/inbox (incl. the async `OutboxFinalizer`), message buses, `OutboxSettings` |
| 5 | [CQRS: Commands, Queries & the Decorator Pipeline](group-05-cqrs-pipeline.md) | 61 | Handler abstraction + the logging/transaction/caching/feature-gate/idempotency decorators (incl. the cache-stampede lock) |
| 6 | [Validation](group-06-validation.md) | 22 | FluentValidation contracts + failure mapping that gate commands |
| 7 | [Persistence & EF Core](group-07-persistence-ef-core.md) | 177 | `SQLServerDbContext` over abstract `ApplicationDbContext`, interceptors (incl. the deferred-dispatch record), repositories, engine-aware entity config, data-source routing, conventions (incl. the soft-delete unique-index convention), factories, and the persistence/audit-trail/connection-string/data-source/tenancy settings classes (regrouped here from the module-system chapter on 2026-09-05) |
| 8 | [Authentication & Authorization](group-08-auth.md) | 162 | JWT/JWKS dual-fetch, the shared `AuthenticationServiceBase<TUser>` login/refresh workflow (`IAuthUser`), current-user/claims, password hashing, cookie sessions, role policies + the permission-based authorization mechanism (registry, `[HasPermission]`), the `RoleValue` base, the external-auth-broker contract ([ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)/043), the JWT/JWKS settings (`JwtSettings`, `JwksSettings`, `JwtSigningAlgorithm`) |
| 9 | [Caching](group-09-caching.md) | 9 | The cache abstraction + its invalidation-aware decorator integration, `CacheSettings` |
| 7 | [Persistence & EF Core](group-07-persistence-ef-core.md) | 177 | `SQLServerDbContext` over abstract `ApplicationDbContext`, interceptors (incl. the deferred-dispatch record), repositories, engine-aware entity config, data-source routing, the per-engine `IDataSourceEngine` strategy behind the `DataSourceEngines` registry ([ADR-130](https://ivanball.github.io/docs/adr/130-per-engine-data-source-strategy.html)), conventions (incl. the soft-delete unique-index convention), factories, and the persistence/audit-trail/connection-string/data-source/tenancy settings classes (regrouped here from the module-system chapter on 2026-09-05) |
| 8 | [Authentication & Authorization](group-08-auth.md) | 162 | JWT/JWKS dual-fetch, the shared `AuthenticationServiceBase<TUser>` login workflow (`IAuthUser`) with refresh-session issue, rotation and reuse detection in `AuthSessionIssuer`, current-user/claims, password hashing + the shared `PasswordComplexity` rules, cookie sessions + the `ISessionCookieStore` family behind the same-origin proxy, role policies + the permission-based authorization mechanism (registry, `[HasPermission]`), the `RoleValue` base, the external-auth-broker contract ([ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)/043), the JWT/JWKS settings (`JwtSettings`, `JwksSettings`, `JwtSigningAlgorithm`) |
| 9 | [Caching](group-09-caching.md) | 9 | The cache abstraction (incl. the shared-store read that skips the local tier for single-use auth records) + its invalidation-aware decorator integration, `CacheSettings` |
| 10 | [Notifications (Push + In-App Inbox + Email)](group-10-notifications.md) | 59 | Push (SignalR), in-app inbox, email, recipient providers, the [ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html) hub-channel live publisher, [ADR-044](https://ivanball.github.io/docs/adr/044-native-push-delivery.html) native OS-level push (Azure Notification Hubs, shipped inert), ADC Notification module + its cross-service gRPC live-channel plumbing + extractable-host Kestrel config |
| 11 | [Navigation Metadata & Populators](group-11-navigation-populators.md) | 12 | EF-decoupled cross-container/cross-source eager loading ([ADR-002](https://ivanball.github.io/docs/adr/002-navigation-populators.html)) |
| 12 | [API Hosting, Middleware, Idempotency & DTO/Contract Mapping](group-12-api-hosting-mapping.md) | 88 | Controller bases (incl. OAuth + service-info + API versioning, [ADR-046](https://ivanball.github.io/docs/adr/046-http-api-versioning.html)), middleware (incl. soft-deleted-user revocation, [ADR-047](https://ivanball.github.io/docs/adr/047-soft-deleted-user-session-revocation.html)), startup, model binders, JSON converters, feature mgmt, idempotency, mapping, edge error localization (i18n), authenticated output caching ([ADR-040](https://ivanball.github.io/docs/adr/040-authenticated-output-caching-for-public-reads.html)), app-association/deep-link endpoints ([ADR-043](https://ivanball.github.io/docs/adr/043-mobile-deep-links-and-native-oauth-callback.html)) |
| 13 | [gRPC & Inter-Service Contracts](group-13-grpc-contracts.md) | 7 | Typed gRPC clients/servers, interceptors, Result-over-the-wire ([ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html)) |
| 13 | [gRPC & Inter-Service Contracts](group-13-grpc-contracts.md) | 7 | Typed gRPC clients/servers, interceptors, the `GrpcWireFormat` UTC/decimal wire helpers, Result-over-the-wire ([ADR-007](https://ivanball.github.io/docs/adr/007-grpc-extraction.html)) |
| 14 | [Module System, Composition & Configuration](group-14-module-system-composition.md) | 87 | `IModule` + Kahn-ordered loader, DI composition roots, data-source attributes, options-binding plumbing (the per-subsystem settings classes live with their subsystems since 2026-09-05), plus the host-level infrastructure services: managed file storage + image processing ([ADR-045](https://ivanball.github.io/docs/adr/045-managed-file-storage-and-avatars.html)), native-push sender + device registrar ([ADR-044](https://ivanball.github.io/docs/adr/044-native-push-delivery.html)), `TenantContext`, the upcasting/fault integration-event consumers and `PeriodicBackgroundService` |
| 15 | [Common UI Framework](group-15-common-ui-framework.md) | 174 | Reusable MudBlazor building blocks: data-grid list page base, theme, common pages/services, the Blazor Server UI-host hardening kit (circuit limits + UI rate limiting), i18n culture bootstrap + day/dark `ThemeService`, user-preference readers/writers, pseudo-localization gate, OAuth UI settings + token storage (Web/WASM), hub-channel subscriptions ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)), the shared `DetailPageBase`, the email-confirmation page + UI service and the client-config endpoint (extracted from ADC in v1.211.0) |
| 15 | [Common UI Framework](group-15-common-ui-framework.md) | 174 | Reusable MudBlazor building blocks: data-grid list page base, theme, common pages/services, the Blazor Server UI-host hardening kit (circuit limits + UI rate limiting), i18n culture bootstrap + day/dark `ThemeService`, user-preference readers/writers, pseudo-localization gate, OAuth UI settings + token storage (Web/WASM), hub-channel subscriptions ([ADR-039](https://ivanball.github.io/docs/adr/039-live-channel-push.html)), the shared `DetailPageBase`, the email-confirmation page + UI service and the client-config endpoint (extracted from ADC in v1.211.0), the same-origin API proxy + session handoff in MMCA.Common.UI.Web ([ADR-131](https://ivanball.github.io/docs/adr/131-same-origin-api-proxy.html)) |
| 16 | [Aspire Orchestration & Service Defaults](group-16-aspire-orchestration.md) | 65 | AppHost wiring, ServiceDefaults, warmup, telemetry, security helpers, the shared `HttpResilienceDefaults` Polly source of truth, the shared `HealthCheckTags` liveness/readiness vocabulary |
| 17 | [ADC Conference, Domain Model & Module Contracts](group-17-conference-domain.md) | 111 | Event/Session/Speaker/Category/Question/Partner aggregates + domain events + invariants + Shared contracts (incl. `ConferencePermissions`, the current/next-event selector + live-validation contracts) |
| 18 | [ADC Conference, Application & Use Cases](group-18-conference-application.md) | 358 | Conference CQRS handlers, validators (incl. the per-field session validation-rule family), DTOs, specs, Sessionize import, decision-support analytics, batch bookmark-count query, event-filtering-by-role handlers, calendar (.ics) export slice ([ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)) |
Expand All @@ -113,7 +113,7 @@ then applied mechanically (`Tools/invtool/classify.ps1` → `00-group-taxonomy.m
| 25 | [ADC Application Host, UI Shell & Cross-Module Composition](group-25-adc-host-composition.md) | 17 | Blazor Web/WASM/WinUI shells, host pages/services, security (the circuit/rate-limit hardening kit now lives in the Common UI chapter), app composition, device-capability DI wiring, one-time preference migrator (the shared ADC Home page now lives in the Conference UI chapter) |
| 26 | [Device Capability Abstraction Layer (Native Contracts, MAUI, Browser & Fallback Adapters)](group-26-device-capability-layer.md) | 103 | Per-capability interface contracts (biometric, geolocation, speech, push registration, clipboard/share/haptics, external auth/links, connectivity/battery, deep links) + their MAUI-native, browser-JS-interop, and inert-fallback implementations, selected per host at DI composition time ([ADR-042](https://ivanball.github.io/docs/adr/042-device-capability-abstraction.html)/043/044/045) |
| 27 | [Common AI Integration](group-27-common-ai-integration.md) | 32 | The optional, provider-agnostic `MMCA.Common.AI` package plus its Anthropic adapter: the provider settings + validator + factory contract, one composition entry point that builds the delegating-client pipeline, the bounding and usage-metering chat clients over `IChatClient`, the token-estimator contract, the usage meter, the IChatGuardrail verdict contract + its GuardrailChatClient decorator, the shipped content-policy guardrail, the chat tool policy, request redaction, the golden-replay evaluation base, and the hashed, versioned prompt contract ([ADR-120](https://ivanball.github.io/docs/adr/120-governed-chat-client-boundary.html)) |
| 28 | [Testing & Quality Infrastructure](group-28-testing-infrastructure.md) | 2,923 | All test projects + reusable Testing/Testing.E2E/Testing.UI/**Testing.Architecture** bases (incl. the handler + decorator-pipeline test bases, the shared production-host/graceful-shutdown bases, the observability-convention fitness base, and the Gallery auth stubs) + architecture-fitness tests + Gallery + the BenchmarkDotNet perf-smoke suite |
| 28 | [Testing & Quality Infrastructure](group-28-testing-infrastructure.md) | 2,923 | All test projects + reusable Testing/Testing.E2E/Testing.UI/**Testing.Architecture** bases (incl. the handler + decorator-pipeline test bases, the shared production-host/graceful-shutdown bases, the observability-convention fitness base, and the Gallery auth stubs) + architecture-fitness tests + Gallery + the BenchmarkDotNet perf-smoke suite + the weekly `MMCA.Common.LoadTests` load tier |

### DevOps & operations chapters
| File | Contents |
Expand Down
36 changes: 20 additions & 16 deletions docs-src/onboarding/devops-aspire.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,22 +634,26 @@ environment, and a shared, optionally vault-encrypted DataProtection key ring ke
portable across replicas. Both are single calls in the framework, so every consumer opts in the same
way rather than inventing its own.

One more per-service extension point is worth noting because it is ADC's, not the framework's:
Conference registers its own OpenTelemetry meter, `"MMCA.ADC.Conference.Scoring"`, on top of the
MMCA.Common meters `AddServiceDefaults` already registers (Conference.Service/Program.cs:119-120). It
carries `scoring.run.failed.terminal`, emitted when a background AI scoring run exhausts its retries and
is abandoned: nothing on the request path reports that failure, so without the counter an incomplete run
is invisible until an organizer notices missing scores (Conference.Service/Program.cs:113-118, the counter
itself at
`Conference/MMCA.ADC.Conference.Infrastructure/Sessions/Scoring/SessionScoringProcessor.cs:96-97`). Two
paid-call cost counters ride that same meter name rather than a second meter: `scoring.tokens.input` and
`scoring.tokens.output`, each tagged with the model id and the prompt version
(`.../Sessions/Scoring/AnthropicScoringService.cs:344` and `349`). Reusing the name is the point: a host
that exports one instrument exports all of them, so no second `AddMeter` call is needed. The two tags are
the ones that move spend, a model swap changes the per-token price and a prompt revision changes the
token count, which is why the per-session usage log stays forensics while these counters are the
aggregate a budget alert queries (AnthropicScoringService.cs:381-387). Like the framework meters, the
name is a literal (`SessionScoringProcessor.cs:59`) so the host's startup wiring does not have to
One more AI signal is worth noting because ADC's session scorer is its only producer. Conference
subscribes its host to the `"MMCA.Common.AI"` meter and activity source explicitly
(Conference.Service/Program.cs:172-174), a name `AddServiceDefaults` also registers
(`MMCA.Common/Source/Hosting/MMCA.Common.Aspire/Extensions.Telemetry.cs:86` and `:315`). It carries the
paid-call cost counters `mmca.ai.input_tokens` and `mmca.ai.output_tokens`, emitted by the governed chat
client rather than by ADC code and tagged with `model`, `prompt_name`, `prompt_version` and `provider`
(`MMCA.Common/Source/Core/MMCA.Common.AI/Observability/AiUsageMeter.cs:26-32`, tags at `:161-164`).
They replace the former per-service `MMCA.ADC.Conference.Scoring` meter and its `scoring.tokens.*`
counters, and the AI spend alert queries the new names (Conference.Service/Program.cs:156-165). Reusing
the one name is the point: it is also the governed client's `ActivitySource`, so one name turns on both
the token metrics and the per-call spans. The tags that move spend are the model (a swap changes the
per-token price) and the prompt version (a revision changes the token count), which is why these
counters are the aggregate a budget alert queries. A background scoring run that exhausts its retries is
no longer counted on a Conference meter: it runs as an internal command
(`MMCA.ADC/Source/Modules/Conference/MMCA.ADC.Conference.Application/Sessions/UseCases/DecisionSupport/ScoreEventSessions/ScoreEventSessionsInternalCommand.cs:39`),
so the framework's `internal_commands.dead_letter.count`, tagged `reason` `attempts_exhausted`, reports
it
(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/InternalCommands/Processing/InternalCommandMetrics.cs:52-58`).
Like the framework meters, the name is a literal (Conference.Service/Program.cs:164-165) so the host's
startup wiring does not have to
reference an Infrastructure type.

---
Expand Down
20 changes: 11 additions & 9 deletions docs-src/onboarding/group-02-domain-building-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ in it.
`.../Pages/Session/SessionDetail.razor.cs:104`, `.../Pages/Event/EventDetail.razor.cs:86`, and
`MMCA.ADC/Source/Modules/Engagement/MMCA.ADC.Engagement.UI/Pages/Feedback/EventFeedback.razor.cs:284`;
Store's detail pages import the same namespace (for example
`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.UI/Pages/Product/ProductDetail.razor.cs:4`).
`MMCA.Store/Source/Modules/Catalog/MMCA.Store.Catalog.UI/Pages/Products/ProductDetail.razor.cs:4`).
Unit-covered by `DomainHelperTests`
(`MMCA.Common/Tests/Core/MMCA.Common.Shared.Tests/Extensions/DomainHelperTests.cs`, G25).
- **Caveats / not-in-source**: supported target types are exactly those enumerated; anything else
Expand Down Expand Up @@ -1031,7 +1031,7 @@ in it.
`MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiConventionTests.cs:20` (the *scan*
is structurally vacuous today, the framework Domain ships no data-subject type),
`MMCA.ADC/Tests/Architecture/MMCA.ADC.Architecture.Tests/Governance/PiiConventionTests.cs:3`, and
`MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/PiiConventionTests.cs:3`. The framework
`MMCA.Store/Tests/Architecture/MMCA.Store.Architecture.Tests/Governance/PiiConventionTests.cs:29`. The framework
closes that vacuity gap with a non-vacuous companion, `PiiErasureContractFitnessTests`
(`MMCA.Common/Tests/Architecture/MMCA.Common.Architecture.Tests/Governance/PiiErasureContractFitnessTests.cs:19`),
which forces a representative `[Pii]`-carrying sample through both halves end to end (recognized and
Expand Down Expand Up @@ -1474,13 +1474,13 @@ in it.
line.
- **Why it's built this way**: EF Core stores `Address` as an **owned type** via `OwnsOne`, stated in
the remarks (`Address.cs:12-14`) and done for real in
`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Infrastructure/Persistence/EntityConfiguration/CustomerConfiguration.cs:43`,
`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Infrastructure/Persistence/EntityConfiguration/CustomerConfiguration.cs:42`,
which flattens the six properties into `AddressLine1`, `AddressLine2`, `AddressCity`,
`AddressState`, `AddressZipCode`, `AddressCountry` columns on the `Customer` table rather than a
child table. Owned types have value semantics at the persistence level, which is exactly the
domain semantic.
- **Where it's used**: the Store Identity `Customer` aggregate owns one (configuration cited above,
with every `HasMaxLength` reading an `AddressInvariants` constant, `CustomerConfiguration.cs:47-73`);
through Common's `OwnsAddress`, where every `HasMaxLength` reads an `AddressInvariants` constant, `MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeBuilderExtensions.cs:138-164`, with a second `OwnsOne` overriding only the unicode facet, `CustomerConfiguration.cs:42-55`);
[`RegisterRequest`](group-08-auth.md#registerrequest) carries an optional `Address? Address = null`
(`MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/Requests/RegisterRequest.cs:18`); the
[`AddressLine1Rules<T>`](group-06-validation.md#addressline1rulest-addressline2rulest-cityrulest-countryrulest) family and
Expand Down Expand Up @@ -1517,8 +1517,10 @@ in it.
reference `AddressInvariants.AddressLine1MaxLength` without depending on `Address` itself, keeping
the Infrastructure-to-Shared coupling thin. `[Rubric §3, Clean Architecture]`.
- **Where it's used**: called from `Address.Create` (`Address.cs:78`); every max-length constant is
read by `CustomerConfiguration` in Store Identity
(`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Infrastructure/Persistence/EntityConfiguration/CustomerConfiguration.cs:47-73`)
read by Common's `OwnsAddress`
(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Configuration/EntityTypeBuilderExtensions.cs:138-164`),
which `CustomerConfiguration` in Store Identity calls
(`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Infrastructure/Persistence/EntityConfiguration/CustomerConfiguration.cs:42`)
and by the [`AddressLine1Rules<T>`](group-06-validation.md#addressline1rulest-addressline2rulest-cityrulest-countryrulest) family in the
Application layer.

Expand Down Expand Up @@ -1848,9 +1850,9 @@ in it.
(`.../EmailValueConverter.cs:60`) for an optional `Email?`.
- **Where it's used**: the Store Identity `Customer` aggregate holds `public Email Email`
(`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Domain/Customers/Customer.cs:36`) and
builds it through `Email.Create` in both `Create` (`Customer.cs:77`) and `ChangeEmail`
(`Customer.cs:153`); its EF configuration applies `.HasConversion(new EmailValueConverter())`
(`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Infrastructure/Persistence/EntityConfiguration/CustomerConfiguration.cs:36`).
builds it through `Email.Create` in both `Create` (`Customer.cs:78`) and `ChangeEmail`
(`Customer.cs:154`); its EF configuration applies `.HasConversion(new EmailValueConverter())`
(`MMCA.Store/Source/Modules/Identity/MMCA.Store.Identity.Infrastructure/Persistence/EntityConfiguration/CustomerConfiguration.cs:33`).
Note the layering: `RegisterRequest` still carries a raw `string Email`
(`MMCA.Common/Source/Core/MMCA.Common.Shared/Auth/RegisterRequest.cs`), and the conversion into the
value object happens inside the domain factory. `[Rubric §9, API & Contract Design]`.
Expand Down
Loading
Loading