Pular para o conteúdo principal
Versão: main (dev) 🚧

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[10.10.0] - 2026-06-29

Release única que entrega as três fases de infraestrutura — Email/Cache, Microsoft Entra ID e Bus/Messaging. As subseções abaixo preservam o detalhe por fase. Primeiro consumidor: ITLab.Template.Next (pina 10.10.0).

Added — Event bus and messaging harness

Abstrações de bus de eventos (domínio + notification) + harness genérico de transporte: bus in-process (Channel<T> + BackgroundService) e harness receptor de Azure Service Bus com settlement (Complete/Abandon/DeadLetter), retry/backoff e impersonação de worker. Per D7=Var-A, o harness vive no DevPack como opt-in (AddDevPack*). O publisher ServiceBus (keyed External) e a convenção de roteamento (Args[0]) ficam no consumidor (canon). Ver ADR-011. Primeiro consumidor: ITLab.Template.Next (Fase 3 — Bus/Messaging). EMessageDisposition/MessageHandlingResult/MessagingErrors já existiam e são reusados.

  • Messaging/EventBusTypeenum { InMemory, External }, seleciona o transporte de publicação.
  • Messaging/IDomainEventsBusTask PublishAsync<T>(T, CancellationToken) where T : class, IDomainEvent.
  • Messaging/IDomainEventsBusFactoryIDomainEventsBus Create(EventBusType) (resolução keyed).
  • Messaging/INotificationsBusTask PublishAsync<T>(T, CancellationToken) where T : class, INotificationEvent.
  • Messaging/IDomainEventMessageHandler<in T>Task<MessageHandlingResult> HandleAsync(T, CancellationToken); o resultado dirige o settlement do receiver.
  • Messaging/InMemoryDomainEventsQueue / InMemoryNotificationsQueueChannel<T> unbounded (singleton).
  • Messaging/InMemoryDomainEventsBus / InMemoryNotificationsBus — escrevem o evento na fila correspondente.
  • Messaging/DomainEventsProcessor / NotificationEventsProcessor (BackgroundService) — drenam a fila e republicam via Mediator IPublisher em escopo novo; logam o event id real (GetEventId()), não um Guid aleatório (correção do bug do WG).
  • Messaging/DomainEventsBusFactoryIDomainEventsBusFactory via GetKeyedService<IDomainEventsBus>(busType); lança InvalidOperationException clara quando o transporte (ex.: External) não está registrado.
  • Messaging/RetryBackoffGate<T>SemaphoreSlim + pausa global para backoff transitório (singleton por T).
  • Messaging/ServiceBusEventReceiver<T> (where T : class, IDomainEvent) — ServiceBusProcessor (AutoComplete=false, MaxConcurrentCalls=1, Prefetch=0); (de)serialização System.Text.Json (sem Newtonsoft); valida payload+TenantId (DeadLetter se inválido); troca ICurrentUser para servicebus-worker com o TenantId do evento; resolve IDomainEventMessageHandler<T>; settlement com máx. 5 entregas. Classificação de transitório desacoplada do WG (sem tipos OpenAI): só ServiceBusException.IsTransient + TimeoutException, com hook opcional injetável Func<Exception, TimeSpan?>?. Lógica de decisão extraída em método puro estático DecideDisposition(MessageHandlingResult, int deliveryCount) (testável).
  • Messaging/ServiceBusReceiverHostedService<T> + ServiceBusReceiverQueue<T>IHostedService que chama StartAsync(queue)/StopAsync.
  • Messaging/DevPackMessagingServiceCollectionExtensionsAddDevPackInMemoryEventBus (filas + bus keyed InMemory + INotificationsBus + processors + factory), AddDevPackServiceBusClient (connection string ConnectionStrings:ServiceBus ou DefaultAzureCredential + ServiceBusOptions.Namespace), AddDevPackServiceBusEventReceiver<T>(queueName) (gate + receiver + hosted service).
  • Pacote: Azure.Identity 1.13.2 adicionado (managed identity via DefaultAzureCredential). Azure.Messaging.ServiceBus já presente.
  • Docs: Messaging/README.md (atualizado); ADR-011.
  • Testes: MessagingTests (RetryBackoffGate<T> enter/release/pause; in-memory bus publish→queue; ServiceBusEventReceiver<T>.DecideDisposition — invalid→DeadLetter, transient abandon<5→Abandon, ≥5→DeadLetter, Completed→Complete).

Added — Microsoft Entra ID connector

Conector Entra ID: terceiro scheme de autenticação, opt-in por tenant, que compõe com o dual scheme existente (cookie BFF + Bearer próprio) sem substituir o DefaultProvider (ADR-010). O DevPack ship só a abstração de validação per-tenant + mapeamento de claims + a extensão de registro do scheme; o wiring, o slice SigninWithEntra e o Graph sync ficam no consumidor (template). Primeiro consumidor: ITLab.Template.Next (Fase 2 — Entra ID).

  • Identity/Authentication/EntraId/IEntraTenantValidator + EntraTenantValidationResult (record (bool IsValid, int? AppTenantId, string? FailureReason) + factories Success/Fail) — decide se um token Entra (tenant Entra extraído do iss) é aceito para o tenant da aplicação.
  • Identity/Authentication/EntraId/PerTenantSettingsEntraTenantValidator — impl default lendo IPerTenantSettingsStore por EntraSettingKeys: exige provider habilitado + diretório (iss tid) casando o configurado + (recomendado) audience contra o WebApi.ClientId do tenant, tolerante quando o client id ou o aud não estão presentes. Resolve o tenant da aplicação via ICurrentUser.TenantId (resolvido pré-auth por domínio; ADR-007).
  • Identity/Authentication/EntraId/IEntraClaimsMapper + EntraUserInfo (record (string ObjectId, string? UserName, string? Email, string[] Roles)) + DefaultEntraClaimsMapper — mapeia oid/objectidentifier, name/preferred_username/email/upn, roles + scopes scp (split por espaço).
  • Identity/Authentication/EntraId/IEntraGraphClientFactoryinterface-only (Task<object?> CreateForTenantAsync(int appTenantId, CancellationToken)); a impl concreta com Microsoft.Graph fica no consumidor (DevPack não referencia Microsoft.Graph).
  • Identity/Authentication/EntraId/EntraSettingKeys — constantes das chaves per-tenant: AuthProviderEntraIdEnabled (AuthProvider.EntraID, idêntica à chave canon do template) + família EntraID.* (TenantId, Instance, WebApi.ClientId, WebApi.ClientSecret, WebApi.Scopes, Spa.ClientId, Spa.RedirectUri, Spa.Scopes) + Prefix.
  • Identity/Authentication/EntraId/DevPackEntraIdServiceCollectionExtensions.AddDevPackEntraId(this AuthenticationBuilder, string scheme = IdentitySchemas.MicrosoftEntraID.Name, Action<MicrosoftIdentityOptions>? configure = null) — registra AddMicrosoftIdentityWebApi para o scheme; issuer validator aceita login.microsoftonline.com/{tid}/v2.0 e sts.windows.net/{tid}/; OnTokenValidated resolve IEntraTenantValidator do RequestServices, extrai o tenant Entra do iss e context.Fail(reason) se inválido (encadeia após o handler do Microsoft.Identity.Web). Registra DefaultEntraClaimsMapper via TryAdd. Helper público estático ExtractTenantIdFromIssuer(string? iss).
  • Pacote: Microsoft.Identity.Web 3.8.3 adicionado. Microsoft.IdentityModel.JsonWebTokens bumpado 8.0.1 → 8.8.0 para unificar o grafo de tokens (Identity.Web exige >= 8.8.0; evita NU1605).
  • Docs: Identity/Authentication/EntraId/README.md (novo), Identity/README.md (§ Entra connector); ADR-010.
  • Testes: ExtractTenantIdFromIssuerTests, PerTenantSettingsEntraTenantValidatorTests, DefaultEntraClaimsMapperTests.

Added — Email abstractions and options

Contratos universais de email (serviço, renderer de template, repositório de template) + options tipadas. O DevPack ship só os contratos + options; os concretos (SMTP/SendGrid, renderer HTML, repositório de arquivos, layouts) vivem no consumidor. Ver ADR-009. Primeiro consumidor: template ITLab.Template.Next (Fase 1 — Email/Cache/Templates).

  • Notifications/Email/IEmailService — contrato de envio: três overloads SendAsync (destinatário único, múltiplos, múltiplos + attachments) + SendFromTemplateAsync(templateKey, IDictionary<string, object> data, subject, string[] destination, int? tenantId = null, IEnumerable<EmailAttachment>? attachments = null, CancellationToken).
  • Notifications/Email/IEmailTemplateRendererstring Render(string templateContent, IDictionary<string, object?> data, string? layoutContent = null). Engine de renderização é do consumidor.
  • Notifications/Email/IEmailTemplateRepositoryTask<string?> GetTemplateAsync(string templateKey, int? tenantId = null, CancellationToken ct = default). Email é HTML-only; sem acoplamento a um enum de canal de entrega.
  • Dtos/EmailResultrecord (bool Success, string? MessageId = null, string? ErrorMessage = null), resultado de envio.
  • AppSettings/EmailOptions (IAppOptions, seção EmailOptions) — Provider (Smtp | SendGrid), DefaultFromAddress, DefaultSenderName, Smtp, SendGrid; props private init. Nested SmtpOptions (DeliveryMethod, Host, Port, EnableSsl, Username, Password, PickupDirectoryLocation) e SendGridOptions (ApiKey).
  • Docs: Notifications/Email/README.md (novo), Notifications/README.md, Dtos/README.md, AppSettings/README.md; ADR-009.

Nota: Dtos/EmailAttachment (record (Stream Content, string FileName, string? ContentType = null)) já existia no DevPack e é reusado por estes contratos — não é uma adição desta release.

[10.7.1] - 2026-06-22

Added — Per-tenant settings store + secret protection

Nota: a 10.7.0 foi publicada por engano sem este conteúdo (Fase 2 re-versionada). Esta entrega saiu como 10.7.1.

Entradas de versões ≤ 10.7.1 abaixo preservam a estrutura plana herdada (acumuladas sob [Unreleased] em releases anteriores); não foram reorganizadas nesta mudança.

Substrato universal para configuração de auth por-tenant (Fase 3 do roadmap de multi-tenancy). Esta entrega ship só o substrato; o consumidor (template) habilita apenas o provider local — Entra/LDAP ficam para depois. Ver ADR-008. Primeiro consumidor: template ITLab.Template.Next (ADR-012).

  • Abstractions/ISecretProtectorProtect/Unprotect simétricos para cifrar segredos em repouso.
  • Abstractions/DataProtectionSecretProtector — impl default sobre ASP.NET Core Data Protection (IDataProtectionProvider, shared framework — sem pacote novo); purpose fixo.
  • Abstractions/SecretProtectorServiceCollectionExtensions.AddDevPackSecretProtector() — registra AddDataProtection() + ISecretProtector (via TryAdd).
  • Identity/Settings/IPerTenantSettingsStore — contrato string-valued para settings por-tenant: GetAsync, GetByPrefixAsync, SetAsync(..., mustProtect), InvalidateCache. Secrets cifrados em repouso e retornados decifrados. Interface no DevPack (universal; seam para ler config fora de um handler, ex. OnTokenValidated por-tenant); impl é do consumidor.
  • Recipe (docs): schemes de auth são estáticos (registrados na startup) → validação por-tenant nos eventos (OnTokenValidated) lendo o store. IPerTenantAuthSchemeResolver deliberadamente não promovido (recipe primeiro — ver ADR-008).
  • Decisão: cifragem no store (em SetAsync/GetAsync), não num SaveChangesInterceptor — evita dupla cifragem e estado de entidade (ver ADR-008 §3).
  • Docs: Abstractions/README.md (§ secret protection), Identity/Settings/README.md (novo), ADR-008.
  • Testes: SecretProtectorTests.

Added (10.6.0 — domain tenant resolution + glue middleware)

Ativa a resolução de tenant por domínio/subdomínio como caminho de primeira classe, opt-in por config. Os contributors concretos + a glue middleware + as options eram o plumbing que faltava sobre as abstrações da 10.4.0. Ver ADR-007. Primeiro consumidor: template ITLab.Template.Next (ADR-011).

  • Identity/MultiTenancy/HttpTenantResolveContributorBase — base abstrata para contributors que leem o HttpContext (resolvido via ITenantResolveContext.GetHttpContext()); captura/loga exceções para não quebrar o pipeline.
  • Identity/MultiTenancy/DomainTenantResolveContributor — resolve o tenant do host na ordem OriginRefererRequest.Host, casando contra MultiTenancyOptions.AlternateDomains (overrides literais, primeiro) e DomainFormats (padrões {0} via FormattedStringValueExtracter). Logger opcional.
  • Identity/MultiTenancy/MultiTenancyOptionsExtensions.AddDomainTenantResolver(this TenantResolveOptions, string[] domainFormats, List<AlternateDomainOption> alternateDomains, IServiceProvider? serviceProvider = null) — insere o domain contributor no início da lista de resolvers.
  • AppSettings/MultiTenancyOptions (IAppOptions, seção MultiTenancyOptions) — EnableDomainValidation (default false), DefaultDevelopmentTenant, DomainFormats, AlternateDomains, ExcludedPaths.
  • AppSettings/AlternateDomainOptionHost + Tenant (override literal host→tenant).
  • Middleware/TenantResolutionMiddleware + Extensions/ApplicationBuilderExtensions.UseDevPackTenantResolution() — glue genérica: gate em EnableDomainValidation, skip de anônimos/ExcludedPaths, resolve host → ITenantAccessValidator.GetTenantIdByDomainNameAsyncValidateAccessAsynccross-check contra ICurrentUser.TenantId. Status: 401 (sem tenant no token / host não resolve / divergência host-vs-token), 404 (host sem tenant), 403 (sem membership). No-op quando desabilitada. Deve rodar após UseDevPackIdentityContext() e antes de UseAuthorization().
  • Docs: Identity/MultiTenancy/README.md (§ resolução por domínio), AppSettings/README.md, Middleware/README.md; ADR-007.
  • Testes: DomainTenantResolveContributorTests, TenantResolutionMiddlewareTests.

Added (10.5.0 — multi-tenant persistence: global query filter + stamping interceptor)

Lado de persistência da tenancy (a resolução/contributors já existiam na 10.4.0). Ver ADR-006. Primeiro consumidor: template ITLab.Template.Next (ADR-009, canon claim-based row-level).

  • Infrastructure/EfCore/MultiTenantModelBuilderExtensions.ApplyMultiTenantQueryFilters(this ModelBuilder, IMultiTenantDbContext) — varre o model e aplica, por reflexão, uma única lambda de query filter por entidade combinando tenant + soft-delete (switch de 4 vias isMultiTenant × isSoftDeletable). Pula entidades com filtro já declarado (GetDeclaredQueryFilters().Count > 0) — preserva o filtro de soft-delete das convenções de Identity e dá escape hatch (filtro explícito numa entidade IMultiTenant a remove do sweep).
  • Infrastructure/EfCore/IMultiTenantDbContextint? CurrentTenantId { get; }. O query filter referencia a instância do DbContext por meio desta interface: o EF Core só re-avalia o filtro por query quando o predicado referencia o contexto; um Func<int?>/objeto capturado é funcletizado para constante e congela no modelo cacheado. Consumidor: public int? CurrentTenantId => _currentUser.TenantId; + modelBuilder.ApplyMultiTenantQueryFilters(this).
  • Infrastructure/EfCore/Interceptors/MultiTenantEntityInterceptor — write-side: stampa IMultiTenant.TenantId no insert (reflexivo, via ObjectHelper.TrySetProperty) e lança InvalidOperationException em modify/delete cross-tenant. No-op quando não há tenant corrente (null/0 = host/global). Resolve ICurrentUser de escopo por-call (Singleton).
  • Infrastructure/EfCore/DevPackEfCoreInterceptorBuilder.WithMultiTenantInterceptor() — registra o interceptor. Ordem recomendada: após WithSoftDeleteInterceptor(), antes de WithDispatchDomainEventsInterceptor().
  • Testing/Authentication/TestAuthenticationHandler — passa a emitir um claim tenant quando o header X-Test-Tenant está presente (consts TenantHeader/TenantClaimType), permitindo testes de isolamento multi-tenant. Sem o header, comportamento inalterado.

Changed (10.5.0)

  • Middleware/DevPackIdentityContextMiddleware.DefaultMap — passa a derivar UserInfo.TenantId do claim tenant (ou tid) do principal, em vez de hardcodar 0. Retrocompatível: ausência/valor não-numérico → 0. Mapper custom (Func<HttpContext, UserInfo?>) permanece como escape hatch.
  • Docs: Infrastructure/EfCore/README.md, Infrastructure/EfCore/Interceptors/README.md, Identity/README.md, Identity/MultiTenancy/README.md; ADR-006.

Added (10.4.0 — Identity store, self-issued tokens, JWKS, dual scheme)

Blocos universais para apps com Identity local que emitem os próprios tokens. Ver ADR-005. Consumido pelo template ITLab.Template.Next (ADR-006, dual-scheme BFF cookie + Bearer JWKS como novo default).

  • Infrastructure/EfCore/BaseIdentityDbContext<TUser,TRole> + ModelBuilderIdentityExtensions.ApplyDevPackIdentityConventionsIdentityDbContext<TUser,TRole,int> + convenções DevPack: move as 7 tabelas de Identity para um schema dedicado (default identity, configurável via IdentitySchema) sem o prefixo AspNet (identity.Users, identity.Roles, identity.UserRoles, identity.UserClaims, identity.UserLogins, identity.UserTokens, identity.RoleClaims); mapeia AuditTimestamp [Owned]; aplica query filter de soft-delete no user.
  • Identity/Authentication/Bearer/ITokenService + DefaultJwtTokenService — emite access token JWT assimétrico (RSA/ECDSA) com header kid, issuer/audience de IdentityTokenOptions, TimeProvider injetado. Record AccessToken(Value, ExpiresAt).
  • Identity/Authentication/Bearer/ISigningKeyProvider + EphemeralRsaSigningKeyProvider (RSA em memória, dev) + FileSigningKeyProvider (PEM em disco, prod). kid estável derivado do módulo público; PublicKeys expõe material público.
  • Identity/Authentication/Bearer/JwksDocumentBuilder — JWKS (RFC 7517) das chaves públicas (RSA/ECDSA, use=sig); nunca emite material privado.
  • Identity/RefreshToken (abstract) + IRefreshTokenStore + RefreshTokenGenerator — refresh token persistido só como hash; estado de rotação (Revoke idempotente, ReplacedByTokenId) e contrato de store (add/find-active/rotate/revoke-all).
  • Identity/ServiceClient (abstract) — cliente de máquina para client-credentials: ClientId, secret BCrypt (VerifySecret/HashSecret), GrantedRoles, ToClaimsIdentity.
  • AppSettings/IdentityTokenOptionsIssuer/Audience/AccessTokenLifetimeMinutes/RefreshTokenLifetimeDays/SigningKeyPemPath (seção Authentication:Token).
  • Identity/DevPackIdentityServiceCollectionExtensionsAddDevPackSigningKey(pemPath, isDevelopment) (ephemeral em dev, PEM em prod, falha-cedo fora de dev sem chave), AddDevPackTokenService(configuration), AddDevPackDualScheme(selector) (só o PolicyScheme Smart com ForwardDefaultSelector; AddCookie/AddJwtBearer permanecem explícitos no consumidor — ADR-001).
  • Identity/IdentitySchemas.Bff + IdentitySchemas.Smart — constantes de scheme do cookie BFF e do scheme composto.
  • Pacote: Microsoft.IdentityModel.JsonWebTokens 8.0.1 (pinado na versão que Microsoft.AspNetCore.Authentication.JwtBearer 10.0.1 resolve transitivamente, para unificar o grafo do consumidor sem upgrade forçado).
  • Docs: Identity/README.md § Self-issued tokens & dual scheme.

Added (10.3.0 — code + namespace migration)

  • ITLab.Template.DevPack.Testing (novo pacote auxiliar publicável): ship como nupkg separado.
    • WebApplicationFactoryBase<TProgram, TDbContext> — substitui DbContextOptions<TDbContext> por EF InMemory isolado, registra TestAuthenticationHandler como esquema default, expõe CreateClientWithRoles(...) e CreateAnonymousClient(), hook ConfigureAdditionalServices(IServiceCollection).
    • TestAuthenticationHandler — lê roles do header X-Test-Roles e produz ClaimsPrincipal autenticado.
    • IsolatedInMemoryProviderExtensions.UseIsolatedInMemoryProvider(string) — InMemory com IServiceProvider interno isolado, evita o erro "Services for database providers 'X', 'InMemory' have been registered".
    • CurrentUserBuilder / DateTimeProviderBuilder / ObjectArrayExtensions migrados de tests/ITLab.Template.DevPack.Testing (mesmo nome de assembly; o projeto interno foi promovido a pacote publishable).
  • Helpers/DocumentHelper — validação e normalização de CPF/CNPJ brasileiros: IsCpfValid, IsCnpjValid, NormalizeCpf, NormalizeCnpj. Cobre all-equal, formatação, null/empty, comprimento e mod 11. Pesos do CNPJ corretos (não copiados de CPF).
  • Guards/DocumentGuardsGuard.Against.InvalidCpf e Guard.Against.InvalidCnpj retornam o valor digit-only normalizado em sucesso (compatível com inicialização de value object). Internamente delegam para DocumentHelper.
  • Middleware/DevPackIdentityContextMiddleware + Extensions/ApplicationBuilderExtensions.UseDevPackIdentityContext() — bridge entre HttpContext.User e AsyncLocalCurrentUserAccessor. Sem isso, RoleBasedPermissionEvaluator recebe UserRoles = [] em produção. Mapper default extrai claims NameIdentifier/sub, Name/name/preferred_username, e Role/role. Override via overload com Func<HttpContext, UserInfo?> para layouts JWT custom.
  • Extensions/ResultHttpExtensions.ToHttpResult(...) — bridge Result<T> / ResultMicrosoft.AspNetCore.Http.IResult para Minimal APIs. Mapping default: Validation→400, NotFound→404, Unauthorized→403, Conflict→409, Failure/Problem→500. Aceita errorMapper opcional.
  • Extensions/MinimalApiPaginatedExtensions.MapPaginatedGet<TQuery,TDto> — encapsula bind individual de pageNumber/pageSize/orderBy/order/filter/paginated, dispatcha via Mediator e renderiza com ToHttpResult. Resolve gotcha do [AsParameters] sobre PaginatedQueryBase<T>.
  • Domain/AuditedSoftDeletableEntity<TKey>: ctor protected AuditedSoftDeletableEntity(TKey id) : base(id) {} — paridade com AuditedEntity<TKey>. Permite private Pedido(Guid id, ...) : base(id) sem precisar de Id = id; no corpo.
  • Domain/DomainEvent.GetKeys(): XML doc com <example> de override completo (OrderApprovedEvent : DomainEvent).

Changed (10.3.0 — BREAKING namespace migration)

  • Result, Result<T>, Error, ErrorType, ValidationError movidos do namespace raiz ITLab.Template.DevPack para ITLab.Template.DevPack.Core. Folder src/.../Results/ renomeado para src/.../Core/. Update consumer using directives. Marcado breaking por convenção; nenhum consumidor de produção existia no momento da release.
  • DispatchDomainEventsInterceptor ctor mudou de (IPublisher publisher) para (IServiceProvider serviceProvider). Resolve IPublisher per-save via IServiceProvider.CreateAsyncScope(). Permite que o interceptor Singleton coexista com handlers Mediator Scoped (default DevPack) sem violar container validation. Regression test adicionado em tests/.../InterceptorsTests.cs.

Removed (10.3.0 — BREAKING)

  • ITLab.Template.DevPack.IResult removido (sem uso real). Quem usava using IResult = ITLab.Template.DevPack.IResult; para evitar colisão com Microsoft.AspNetCore.Http.IResult pode remover o alias.

Added (10.2.4 — docs/CI)

  • CLAUDE.md (raiz): prefácio "Escopo deste documento (princípio G-01)" estabelecendo onde mora cada regra (uso da biblioteca = aqui; convenções específicas do template = ITLab.Template.Next/CLAUDE.md). Seção "What NOT to do" consolida os anti-padrões mais frequentes.
  • Authorization/README.md: subseção "Permissions catalogue location" canonizando Application/Common/Authorization/{Project}Permissions.cs. Subseção "Permission convention: endpoint vs command" explicita que RequireAuthorization é default e [RequiresPermission] é opt-in para regras contextuais.
  • Behaviors/README.md: subseção "Validator placement convention" (validators ao lado do Command/Query + AddValidatorsFromAssembly).
  • Cqrs/README.md: seção "Handler lifetime" (configurar opt.ServiceLifetime = ServiceLifetime.Scoped em AddMediator quando handlers consomem IAppDbContext); seção "Domain events" cross-link com Domain README.
  • Core/README.md: tabela "Error factory methods" com mapping HTTP default (Validation→400, NotFound→404, Unauthorized→403, Conflict→409, Failure/Problem→500).
  • Domain/README.md: seção "Domain events policy" (quando criar evento + handler obrigatório MSG0005 + placeholder pattern com ILogger). Esclarece IDomainEvent : Mediator.INotification.
  • Identity/README.md: seção "Authentication scheme — production checklist" (JWT Bearer / Entra ID / Cookie / pipeline order incluindo app.UseDevPackIdentityContext() da 10.3.0). Nota sobre PlaceholderAuthenticationHandler ser scaffold do template, não publicado no DevPack.
  • Infrastructure/EfCore/README.md: seção "EF migrations CLI" com --project + --startup-project e cross-ref ao CLAUDE.md do template.
  • CI: smoke-test-convention-check via Roslyn em tools/DocsSmokeCheck/ — extrai blocos ```csharp dos READMEs de áreas e os compila contra a DLL recém-buildada do DevPack. Suporta fence ```csharp-noverify para snippets ilustrativos.
    • Baseline pragmática: snippets de "Como usar" / wiring / chamadas em corpo de método ficaram marcados como csharp-noverify por padrão, pois referenciam símbolos do consumidor (Order, services, MyAppPermissions). Blocos auto-suficientes (definições de tipo) ficam como csharp para guardar regressões — atualmente cobrem o A-02 (ICommand<T>/IQuery<T>), A-03 (class : PaginatedQueryBase<T>) e C-01 (override GetKeys()).
    • CI gate é strict (continueOnError: false). Mediator foi removido dos common usings da tool por colidir com ITLab.Template.DevPack.Cqrs; blocos que precisam dele declaram using Mediator; explicitamente.
    • CONTRIBUTING.md documenta as convenções e o checklist do autor.

Changed

  • Cqrs/README.md: exemplo canônico mudou de ICommand<Result<TResponse>> para ICommand<TResponse> — o wrapping Result<> é aplicado automaticamente pela assinatura do handler. "What NOT to do" no CLAUDE.md ratifica.

  • Data/README.md: exemplo canônico mudou de record ListOrdersQuery : PaginatedQueryBase<...> para class ListOrdersQuery : PaginatedQueryBase<...> — base é classe abstrata, records C# não herdam de classes não-record (CS8865/CS0115). Cross-ref a MapPaginatedGet (10.3.0).

  • LLM-readable docs shipped inside the nupkg at devpack-docs/. A pasta inclui llms.txt (índice formato llmstxt.org), CLAUDE.md, DEVPACK.md, CHANGELOG.md, os 4 ADRs em adrs/, e os READMEs por área em areas/{Namespace}/README.md (preserva hierarquia — ex.: areas/Identity/MultiTenancy/README.md). Resolve no consumidor via {NuGetPackageRoot}/itlab.template.devpack/{version}/devpack-docs/.

  • Empacotamento via <None Pack="true" PackagePath="devpack-docs/..."> no .csproj. Os arquivos MD reusam a fonte de verdade do repositório (sem duplicação) — editar o MD original atualiza fonte e o conteúdo do package na próxima dotnet pack.

  • Tamanho adicional do nupkg: ~30 KB compridos (39 arquivos MD totalizando ~135 KB descomprimidos). Insignificante perto dos 105 KB da DLL e 385 KB do XML doc.

    Motivação: o .xml doc da DLL é referência de tipo (IntelliSense), mas falta conhecimento operacional ("quando uso X vs Y?", "qual é o padrão para Z?"). O devpack-docs/ cobre esse gap, version-pinned com a release exata do package consumido — elimina drift estrutural entre a doc e o binário.

[10.2.2] - 2026-05-02

Fixed

  • ITLab.Template.DevPack.csproj: adicionada <RestoreEnablePackagePruning>false</RestoreEnablePackagePruning> no <PropertyGroup> raiz. Sem essa property, o NuGet 10.x SDK prunava a PackageReference defensiva de System.Security.Cryptography.Xml 10.0.7 (mesmo com NoWarn="NU1510") durante dotnet pack, fazendo com que o pin de segurança introduzido na 10.2.0 não chegasse ao nuspec publicado — verificado empiricamente nas releases 10.2.0 e 10.2.1, ambas sem a dependência no nuspec. A 10.2.2 carrega corretamente <dependency id="System.Security.Cryptography.Xml" version="10.0.7" /> — consumidores herdam o hardening transitivamente sem precisar de override local.
  • NoWarn="NU1510" removido do <PackageReference Include="System.Security.Cryptography.Xml"> — redundante após o RestoreEnablePackagePruning=false (warning não dispara mais).

[10.2.0] - 2026-05-01

Nota de versionamento. A partir desta release, o esquema do DevPack alinha o primeiro dígito ao framework target (.NET 10 → DevPack 10.x). Esta versão é o sucessor direto da 2.1.1 — o conteúdo é o mesmo que estava sendo desenvolvido como "10.2.0" em rascunhos/relatórios, agora rotulado como 10.2.0 ("segunda minor para .NET 10"). Versões 2.x continuam disponíveis para projetos legados que ainda não migraram. Releases futuras seguirão 10.x.y enquanto a base for .NET 10; quando .NET 11 for adotado, o versionamento passa para 11.x.y.

Release temático: **auditoria com value object IANA-aware + role-based authorization

  • ergonomia de DI**. Acompanhada por dois ADRs novos:
  • ADR-003AuditTimestamp value object com timezone por timestamp.
  • ADR-004 — Role-based permission resolution.

Security

  • Pin transitivo de System.Security.Cryptography.Xml 10.0.7. Endurece o graph contra os advisories de severidade alta GHSA-37gx-xxp4-5rgx e GHSA-w3x6-4m5h-cxqf, presentes nas versões 9.x e 10.0.0–10.0.6 do pacote — puxado transitivamente via Microsoft.AspNetCore.Identity.EntityFrameworkCore. Consumidores herdam automaticamente; o pin no Directory.Packages.props do consumidor pode ser removido (era a recomendação da Fase 1 do template ITLab.Template.Next).

    Errata pós-publicação: o nuspec dos pacotes 10.2.0 e 10.2.1 publicados não carregou esta dependência por causa de pruning automático do NuGet 10.x SDK durante dotnet pack. Resolvido em 10.2.2 via RestoreEnablePackagePruning=false (entrada dedicada no [10.2.2] abaixo). Consumidores de 10.2.0/10.2.1 devem atualizar para 10.2.2+ e remover pin defensivo local.

Removed (Breaking)

  • Behaviors/NormalizeDateTimeOffsetBehavior removido. Pipeline de behaviors passa a ser Logging → Validation (Normalize não é mais registrado por AddDevPackBehaviors()). Com a migração de auditoria para AuditTimestamp (UTC + IANA), o behavior perdeu razão de existir. Projetos que precisem de normalização de fuso em campos de request devem implementar comportamento próprio na camada de aplicação.
  • Attributes/NormalizeDateTimeOffsetAttribute removido. Era consumido apenas pelo behavior removido.
  • Domain/IAuditable: contrato reescrito em torno de AuditTimestamp.
    • Removidas: Created (DateTimeOffset), LastModified (DateTimeOffset?), LastModifiedBy (string?), SetCreatedProperties(string, DateTimeOffset), SetUpdatedProperties(string, DateTimeOffset).
    • Adicionadas / renomeadas: CreatedAt (AuditTimestamp), UpdatedAt (AuditTimestamp?), ModifiedBy (string? — renomeado de LastModifiedBy), SetCreatedAudit(string?, AuditTimestamp), SetUpdatedAudit(string?, AuditTimestamp).
    • Atribuição de usuário preservada (CreatedBy permanece, LastModifiedBy renomeado para ModifiedBy).
  • Domain/AuditedEntity / AuditedEntity<TKey>: campos públicos migrados conforme IAuditable.
  • Domain/ISoftDeletable: contrato reescrito em torno de AuditTimestamp.
    • Removidas: DeletedOnUtc (DateTimeOffset?), SoftDelete(string, DateTimeOffset).
    • Adicionada / mantida: DeletedAt (AuditTimestamp?), DeletedBy (string?, mantido), SoftDelete(string?, AuditTimestamp).
  • Domain/AuditedSoftDeletableEntity<TKey>: ajustado ao novo ISoftDeletable.
  • Identity/BaseIdentityUser: ajustado ao novo IAuditable/ISoftDeletable. Mesmas migrações de tipo. LastModifiedByModifiedBy.
  • Identity/BaseIdentityRole: ajustado ao novo IAuditable. Mesmas migrações.
  • Infrastructure/EfCore/Interceptors/AuditableEntityInterceptor: construtor reescrito.
    • Antes: (ICurrentUser currentUser, TimeProvider dateTime).
    • Depois: (IServiceProvider serviceProvider). Resolve ICurrentUser por chamada via IServiceProvider.CreateScope() — evita captura de scoped service em singleton interceptor. Constrói uma AuditTimestamp com DateTime.UtcNow + currentUser?.LocalTimeZone e popula CreatedBy/ModifiedBy com currentUser?.UserName.
  • Infrastructure/EfCore/Interceptors/SoftDeleteInterceptor: construtor reescrito.
    • Antes: (ICurrentUser currentUser, TimeProvider dateTime).
    • Depois: (IServiceProvider serviceProvider, TimeProvider dateTime). Mesmo padrão de scope-por-chamada; preserva DeletedBy no entity via currentUser?.UserName.

Added

  • Domain/AuditTimestamp — value object record (DateTime UtcDateTime, string? LocalTimeZone) que encapsula o par "instante UTC + IANA timezone". Cada propriedade de auditoria (CreatedAt, UpdatedAt, DeletedAt) carrega seu próprio AuditTimestamp — criação, atualização e deleção podem ocorrer em fusos diferentes e ficam independentemente reconstruíveis. Métodos: AuditTimestamp.Now(string? localTimeZone) e ToLocal() que devolve DateTimeOffset aplicando a timezone (fallback silencioso para UTC se a timezone for desconhecida ao runtime). Anotado com [Owned] — EF Core auto-mapeia como propriedade owned (duas colunas por padrão: {Property}_UtcDateTime e {Property}_LocalTimeZone), sem configuração manual no consumidor para o caso comum.
  • Authorization/IRolePermissionResolver — contrato para resolução de permissões a partir de role names.
  • Authorization/RoleBasedPermissionEvaluator — implementação default de IPermissionEvaluator que combina ICurrentUser.UserRoles + IRolePermissionResolver. Cache por-instância (Scoped lifetime).
  • Authorization/DevPackAuthorizationExtensions.AddDevPackRoleBasedPermissionEvaluator<TResolver>() — registration conveniente.
  • Infrastructure/EfCore/DevPackEfCoreInterceptorBuilder + extension services.AddDevPackEfCoreInterceptors() — fluent builder para registrar interceptors EF Core. Suporta .WithAuditableEntityInterceptor(), .WithSoftDeleteInterceptor(), .WithDispatchDomainEventsInterceptor() e .WithInterceptor<TInterceptor>() para customizados. Cada interceptor é registrado como singleton sob dois canais: tipo concreto (para testes) e ISaveChangesInterceptor (consumido por AddDbContext via provider.GetServices<ISaveChangesInterceptor>()). A ordem de registro é preservada e determina a ordem de execução. Forma legada de registro manual continua suportada — adição não-quebrante.
  • Abstractions/UtcDateTimeProvider — implementação default de IDateTimeProvider. Todos os 11 membros do contrato resolvem para UTC (system e user-local idênticos; UserTimeZone == TimeZoneInfo.Utc). Lifetime: Singleton. Indicado para hosts sem user context (workers, batch).
  • Abstractions/LocalDateTimeProvider — implementação de IDateTimeProvider que converte UTC para ICurrentUser.LocalTimeZone (IANA) para os membros UserLocal*. Membros System* usam TimeProvider.GetLocalNow() (timezone do servidor). Fallback silencioso para UTC quando a timezone do usuário é null, vazio ou desconhecida ao runtime — nunca lança exceção. Lifetime: Scoped (depende de ICurrentUser).
  • Abstractions/DateTimeProviderServiceCollectionExtensionsAddDevPackUtcDateTimeProvider() (Singleton) e AddDevPackLocalDateTimeProvider() (Scoped) para registro conveniente. Pré-requisito comum: services.AddSingleton(TimeProvider.System).

Migration notes

  • Leituras de auditoria: substituir entity.Created (DateTimeOffset) por entity.CreatedAt.UtcDateTime (DateTime UTC) ou entity.CreatedAt.ToLocal() (DateTimeOffset reconstruído na timezone original). Mesmo padrão para LastModifiedUpdatedAt, DeletedOnUtcDeletedAt.
  • Atribuição de usuário: CreatedBy e DeletedBy mantêm nome. LastModifiedBy foi renomeado para ModifiedBy — consumidores que liam entity.LastModifiedBy devem trocar para entity.ModifiedBy.
  • Migrações EF:
    • Cada propriedade de auditoria vira duas colunas: {Name}_UtcDateTime (datetime2) e {Name}_LocalTimeZone (nvarchar(64), nullable). Por exemplo, CreatedAt se torna CreatedAt_UtcDateTime + CreatedAt_LocalTimeZone. Ajuste seu schema migration.
    • Para projetos que preferem coluna única por timestamp, é possível usar builder.Property(e => e.CreatedAt).HasConversion<string>(...) em OnModelCreating — perde queryability dos campos individuais.
  • AuditableEntityInterceptor registro: continua sendo services.AddScoped<AuditableEntityInterceptor>() no DI — IServiceProvider é resolvido automaticamente. Sem mudança no chamador.
  • SoftDeleteInterceptor registro: idem — services.AddScoped<SoftDeleteInterceptor>(). As dependências (IServiceProvider, TimeProvider) são resolvidas pelo DI. TimeProvider.System deve estar registrado (services.AddSingleton(TimeProvider.System)).
  • Pipeline de behaviors: remover qualquer registro manual de NormalizeDateTimeOffsetBehavior se aplicável; AddDevPackBehaviors() cuida do par Logging → Validation automaticamente.
  • Permissões role-based: substituir implementação custom de IPermissionEvaluator por services.AddDevPackRoleBasedPermissionEvaluator<MyRolePermissionResolver>() quando o caso de uso for "permissões via roles".

[2.1.1] - 2026-05-01

Fixed

  • Data/QueryFilter: o operador contains agora emite (prop != null && prop.Contains(value)) em vez de prop.Contains(value). O comportamento anterior lançava NullReferenceException em LINQ-to-Objects quando a propriedade alvo era null — limitação documentada na 2.1.0 via o teste ParseFilter_StringContains_OnNullProperty_*. Em LINQ-to-EF Core o comportamento já era silencioso (SQL absorve NULL); o fix alinha as duas trilhas de execução. Inclui o operador composto [any] contains que usa o mesmo caminho.
  • tests/.../Data/QueryFilterTests.cs: o teste ParseFilter_StringContains_OnNullProperty_ThrowsNullReferenceException foi renomeado para ParseFilter_StringContains_OnNullProperty_ShouldExcludeWithoutThrowing, refletindo o comportamento corrigido. O fixture passou a manter Bob.Notes = null para exercitar a null-safety em todos os cenários de contains.

[2.1.0] - 2026-05-01

Added

  • tests/.../Helpers/PermissionsGeneratorTests.cs: 18 testes cobrindo BuildDictionary (classe plana, hierárquica, vazia, campos não-string), GenerateJson (outputs indented), GenerateTypeScriptUnion (flatten recursivo, filtro de empty values, default type name), GenerateTypeScriptShape (objeto literal, hierarquia, default name) e Generate (dispatch para cada modo).
  • tests/.../Identity/MultiTenancy/TenantResolverTests.cs: 7 testes cobrindo o pipeline de contributors — sem contributors (Unresolved), resolução por TenantIdOrName, resolução por Handled flag, short-circuit do primeiro contributor que resolve, fallback para o segundo quando o primeiro não resolve, propagação de exceções e ordem de execução.
  • tests/.../Data/QueryFilterTests.cs: 30 testes cobrindo todos os operadores DSL (eq, ne, gt, lt, ge, le, contains, in, [any] contains), composição com and/or, parênteses, propriedades aninhadas via dot notation, comparação contra null em propriedades nullable, e validação de inputs inválidos (formato, operador desconhecido, property inexistente). Inclui também um teste explícito que documenta uma limitação conhecida — ver seção abaixo.

Known limitations (documented by tests)

  • Data/QueryFilter emite prop.Contains(value) sem null-guard. Em LINQ-to-Objects (uso in-memory ou em testes) isso lança NullReferenceException quando a propriedade tem valor null. Em LINQ-to-EF Core o comportamento é silencioso — o SQL gerado absorve NULL corretamente. Comportamento documentado no teste ParseFilter_StringContains_OnNullProperty_ThrowsNullReferenceException. Fix candidato (em release futura): emitir (prop != null && prop.Contains(value)) para alinhar comportamento in-memory e database.

[2.0.2] - 2026-05-01

Added

  • Guards/ nova área dedicada com namespace ITLab.Template.DevPack.Guards. As classes ObjectGuards, DateTimeOffsetGuards e DateOnlyGuards foram extraídas de Extensions/GuardExtensions.cs (que era arquivo único com três classes coabitando) para arquivos individuais sob a nova pasta. Guards/README.md documenta a área seguindo o padrão das demais. DateOnlyGuards passa a estar documentada — não havia entrada anterior em README algum.

Changed

  • (Breaking, cosmético) Namespace dos guards mudou de ITLab.Template.DevPack.Extensions para ITLab.Template.DevPack.Guards. Consumidores externos precisam adicionar using ITLab.Template.DevPack.Guards; em arquivos que usem Guard.Against.NotEqual(...), Guard.Against.MustBeQuarterHourRounded(...), Guard.Against.NotFirstDayOfMonth(...) etc. Aceito como patch pelo mesmo critério aplicado em 2.0.1: DevPack ainda não tem consumidores externos; correção pré-publicação ampla.
  • (Breaking, cosmético) NameValue.cs movido da raiz de src/ para Dtos/NameValue.cs; namespace atualizado de ITLab.Template.DevPack para ITLab.Template.DevPack.Dtos. Mesma justificativa do bullet anterior. Dtos/README.md agora lista NameValue e NameValue<T>.
  • Helpers/README.md: corrigidos EPermissionsGeneratorType modos (Json/TsUnion/TsShape em vez de Flat/Hierarchical/FlatWithPrefix), exemplo de PermissionsGenerator reescrito sobre Generate(...) real, e adicionada nota explicando que DateTimeOffsetHelper coabita em DateTimeHelper.cs (mesmo namespace Helpers).
  • Dtos/README.md: tabela de Componentes ampliada para listar OptionDto<TValue>, OptionDto<TValue, TAdditional>, GenericKeyValueDto<TValue> e GenericKeyValueDto<TValue, TAdditional> (anteriormente omitidos), além de separar BaseKeyValueDto em variantes <TValue> e <TValue, TAdditional>. Anotada a divergência arquivo↔classe ScheduledJobConfig × ScheduledJobConfiguration.cs.
  • Notifications/README.md: snippet sinaliza explicitamente que INotificationService é contrato do consumidor, não do DevPack; adicionado bullet em "O que NÃO faz".
  • Identity/MultiTenancy/README.md: snippet final reescrito — currentUser.SetTenant(...) não existe; adapter de aplicação do tenant (claim, accessor custom, etc.) é responsabilidade do consumidor.
  • Infrastructure/README.md e Infrastructure/EfCore/README.md: nota de navegação destacando que são containers para sub-namespaces, com link direto para o nível com conteúdo real.
  • Extensions/README.md: removidas entradas de ObjectGuards e DateTimeOffsetGuards da tabela de Componentes (movidas para Guards/); nota cross-reference adicionada.
  • DEVPACK.md: nova entrada para ITLab.Template.DevPack.Guards no mapa de namespaces.
  • README.md raiz: inventário de READMEs reflete Guards/README.md.

Removed

  • Extensions/GuardExtensions.cs: substituído por três arquivos sob Guards/.
  • src/ITLab.Template.DevPack/NameValue.cs da raiz: movido para Dtos/.
  • Directory.Packages.props: removidas pinagens dead de ClosedXML e SendGrid (pacotes haviam sido removidos do .csproj em 2.0.0).
  • tests/ITLab.Template.DevPack.Tests/Inspector.cs: teste exploratório com try/catch silencioso. Tinha um [Fact] FinalInspect que sempre passava sem assertions úteis (carregava Asp.Versioning.Http em try { } catch { }). Sua remoção reduz a contagem de testes em 1 (de 458 para 457) — sem perda de cobertura efetiva, já que o teste não verificava nada.
  • script.cs na raiz do repositório: código exploratório referenciando pacotes removidos (Swashbuckle.AspNetCore, Asp.Versioning.ApiExplorer). Não estava em qualquer slnx/csproj.
  • docs/devpack-inventory.md interno (snapshot pré-Fase 0): conteúdo substituído por pointer ao inventário canônico mantido fora do repositório do DevPack.

[2.0.1] - 2026-05-01

Changed (Breaking)

  • Namespace ITLab.Template.DevPack.Mediator renomeado para ITLab.Template.DevPack.Cqrs. A pasta src/ITLab.Template.DevPack/Mediator/ foi movida para src/ITLab.Template.DevPack/Cqrs/. Nenhum tipo público teve nome alterado (ICommand, IQuery, ICommandHandler, IQueryHandler, INotificationEvent, IMediatorHandler, MediatorHandler).

    Motivação: o nome anterior colidia visualmente com o namespace raiz Mediator (martinothamar). Quando um arquivo do consumidor importava ambos, o IntelliSense mostrava dois ICommand/IQuery/INotification (um nosso, um da biblioteca), gerando ambiguidade prejudicial — especialmente para agentes de IA. A escolha alternativa "Messaging" foi descartada porque o DevPack já tem uma pasta Messaging/ reservada para integração com message broker (Service Bus). O termo "Cqrs" é específico, idiomático e sem colisão. Veja docs/adrs/002-rename-mediator-to-cqrs.md.

    Migração: substituir using ITLab.Template.DevPack.Mediator; por using ITLab.Template.DevPack.Cqrs; em todos os arquivos do consumidor. Não há mudança em assinaturas, comportamento ou dependências.

Changed

  • DEVPACK.md, CLAUDE.md, README.md raiz e Cqrs/README.md atualizados para refletir o novo nome de namespace e a ausência de colisão com Mediator (martinothamar).
  • Messaging/README.md reescrito: o anterior foi sobrescrito durante a tentativa intermediária de renomeação que conflitou com a pasta existente; o conteúdo novo segue o mesmo padrão das outras áreas e descreve EMessageDisposition, MessageHandlingResult e MessagingErrors.

Fixed

  • Helpers/DomainEventHelper.TrySetTenantId: removido type check tautológico (is not IDomainEvent) e variável local de nome enganoso (multiTenantEntity), herdados de copy-paste do gêmeo EntityHelper.TrySetTenantId(IEntity). O comportamento funcional permanece idêntico — o código sempre executou o caminho feliz; a estrutura apenas escondia um null guard sob pattern matching desnecessário, gerando confusão sobre a intenção do método. Adicionado null check explícito via ArgumentNullException.ThrowIfNull e cobertura em DomainEventHelperTests (4 cenários: contexto sem usuário, tenant igual, tenant diferente, argumento nulo).

[2.0.0] — Phase 0 — Foundational refactor

This release replaces the foundational libraries to align the DevPack with the state-of-the-art .NET 10 stack. Each substitution is a clean cut: the previous library is removed entirely. There is no transition window. Consumers must migrate in one step.

Added

  • Mediator.Abstractions 3.0.2 (Mediator by martinothamar) replaces MediatR. Source-generated CQRS dispatch with ValueTask everywhere.
  • Authorization/ directory: new policy-based permission system.
    • IPermissionEvaluator — agnostic permission resolution contract.
    • PermissionAuthorizationRequirement, PermissionAuthorizationHandler, PermissionAuthorizationPolicyProvider — ASP.NET Core authorization plumbing for dynamic permission policies (RequireAuthorization("Pedidos.Create")).
    • RequiresPermissionAttribute — declarative permission marker for Commands and Queries (multiple form an AND).
    • AuthorizationBehavior<TRequest, TResponse> — mediator pipeline behavior that enforces [RequiresPermission] before the handler runs, with per-type reflection cache.
    • DevPackAuthorizationExtensions.AddDevPackAuthorization() — DI registration helper.
  • Authorization/README.md — full area documentation with usage example, dual endpoint+behavior convention, naming convention.
  • Behaviors/DevPackBehaviorsExtensions.cs and AddDevPackBehaviors() extension — registers the trio Logging → Validation → Normalize in the recommended order.

Changed

  • Mediator/ICommand, ICommand<TResponse>, ICommandHandler<...>, IQuery<TResponse>, IQueryHandler<TQuery, TResponse>, INotificationEvent, IMediatorHandler, MediatorHandler rewritten on top of Mediator (martinothamar). Public type names preserved.
  • Breaking: handler Handle signatures now return ValueTask<TResponse> instead of Task<TResponse>. Pipeline next delegate is now MessageHandlerDelegate<TMessage, TResponse> taking (message, ct) instead of RequestHandlerDelegate<TResponse> taking ().
  • Breaking: IMediatorHandler.PublishEvent<T> returns ValueTask instead of Task and accepts a CancellationToken parameter.
  • Breaking: MediatorHandler constructor takes Mediator.IPublisher instead of MediatR.IMediator.
  • Domain/IDomainEvent now extends Mediator.INotification instead of MediatR.INotification. Public surface preserved.
  • Infrastructure/EfCore/Interceptors/DispatchDomainEventsInterceptor constructor takes Mediator.IPublisher. DispatchDomainEvents returns ValueTask and accepts a CancellationToken.
  • Behaviors/ValidationPipelineBehavior, RequestLoggingPipelineBehavior, NormalizeDateTimeOffsetBehavior rewritten on top of Mediator.IPipelineBehavior<,>. Behavior semantics preserved.
  • CoreConsts.cs moved to CoreConsts/CoreConsts.cs to align with the per-area subdirectory convention; namespace unchanged.
  • Identity/README.md: added BaseIdentityUser, BaseIdentityRole, CurrentUserExtensions, and IdentityProviderName to the Componentes table.
  • Data/README.md: added QueryFilter to the Componentes table and a usage example.
  • PackageTags: replaced mediatr with mediator.

Removed

  • MediatR NuGet dependency.
  • MediatR.Contracts NuGet dependency.
  • MagicMapper NuGet dependency. The DevPack no longer ships any mapping infrastructure; products use Mapperly or hand-written extension methods.
  • Data/GroupConverter<T> (the AutoMapper.ITypeConverter adapter for grouped query results). It was not consumed by GroupableList<T> — server-side grouping is implemented in GroupableList<T> via reflection over dynamic LINQ projections.
  • Data/GroupKey.SplitGroup constant — only consumed by the removed GroupConverter<T>.
  • Swagger/ directory and the ConfigureSwaggerOptions and SwaggerDefaultValuesFilter types. Consumers must migrate to Microsoft.AspNetCore.OpenApi and a UI of choice (e.g. Scalar.AspNetCore).
  • Extensions/SwaggerExtensions.cs and the UseSwaggerAndUI helper: replaced by direct MapOpenApi() / MapScalarApiReference() calls in consumer projects.
  • Swashbuckle.AspNetCore NuGet dependency.
  • Asp.Versioning.Mvc and Asp.Versioning.Mvc.ApiExplorer NuGet dependencies. Consumers needing API versioning on Minimal APIs can install Asp.Versioning.Http directly.
  • ClosedXML NuGet dependency: no public types or internal usage found; removed to reduce package footprint.
  • SendGrid NuGet dependency: no public types or internal usage found; removed to reduce package footprint.

Migration notes for consumers

  • Replace using MediatR; with using Mediator; in endpoint code that injects the mediator. Handler files and command/query records use using ITLab.Template.DevPack.Cqrs; (renamed from .Mediator in 2.0.1) for the wrappers.
  • Replace services.AddMediatR(typeof(...)) with services.AddMediator(opt => opt.Namespace = "MyApp.Mediator";) from the Mediator.SourceGenerator package (added in the consumer project, not in the DevPack).
  • Replace services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(...)) calls with services.AddDevPackBehaviors().
  • Replace Task<TResponse> handler return types with ValueTask<TResponse>.
  • Adapt pipeline behavior implementations to IPipelineBehavior<TMessage, TResponse> from Mediator namespace, with where TMessage : IMessage constraint and MessageHandlerDelegate<TMessage, TResponse> for the next delegate.
  • For mapping, replace AutoMapper/MagicMapper usage with Mapperly ([Mapper] partial classes) or hand-written extension methods.
  • For OpenAPI, replace services.AddSwaggerGen() / app.UseSwagger() / app.UseSwaggerUI() with services.AddOpenApi() / app.MapOpenApi() / app.MapScalarApiReference() (the latter requires the Scalar.AspNetCore NuGet package).
  • For permissions, register IPermissionEvaluator (your implementation) and call services.AddDevPackAuthorization().

[1.0.0] - 2026-04-23

Added

  • Initial release