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]
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.ApplyDevPackIdentityConventions—IdentityDbContext<TUser,TRole,int>+ convenções DevPack: move as 7 tabelas de Identity para um schema dedicado (defaultidentity, configurável viaIdentitySchema) sem o prefixoAspNet(identity.Users,identity.Roles,identity.UserRoles,identity.UserClaims,identity.UserLogins,identity.UserTokens,identity.RoleClaims); mapeiaAuditTimestamp [Owned]; aplica query filter de soft-delete no user.Identity/Authentication/Bearer/ITokenService+DefaultJwtTokenService— emite access token JWT assimétrico (RSA/ECDSA) com headerkid, issuer/audience deIdentityTokenOptions,TimeProviderinjetado. RecordAccessToken(Value, ExpiresAt).Identity/Authentication/Bearer/ISigningKeyProvider+EphemeralRsaSigningKeyProvider(RSA em memória, dev) +FileSigningKeyProvider(PEM em disco, prod).kidestável derivado do módulo público;PublicKeysexpõe só 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 (Revokeidempotente,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/IdentityTokenOptions—Issuer/Audience/AccessTokenLifetimeMinutes/RefreshTokenLifetimeDays/SigningKeyPemPath(seçãoAuthentication:Token).Identity/DevPackIdentityServiceCollectionExtensions—AddDevPackSigningKey(pemPath, isDevelopment)(ephemeral em dev, PEM em prod, falha-cedo fora de dev sem chave),AddDevPackTokenService(configuration),AddDevPackDualScheme(selector)(só oPolicySchemeSmartcomForwardDefaultSelector;AddCookie/AddJwtBearerpermanecem explícitos no consumidor — ADR-001).Identity/IdentitySchemas.Bff+IdentitySchemas.Smart— constantes de scheme do cookie BFF e do scheme composto.- Pacote:
Microsoft.IdentityModel.JsonWebTokens8.0.1 (pinado na versão queMicrosoft.AspNetCore.Authentication.JwtBearer10.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>— substituiDbContextOptions<TDbContext>por EF InMemory isolado, registraTestAuthenticationHandlercomo esquema default, expõeCreateClientWithRoles(...)eCreateAnonymousClient(), hookConfigureAdditionalServices(IServiceCollection).TestAuthenticationHandler— lê roles do headerX-Test-Rolese produzClaimsPrincipalautenticado.IsolatedInMemoryProviderExtensions.UseIsolatedInMemoryProvider(string)— InMemory comIServiceProviderinterno isolado, evita o erro "Services for database providers 'X', 'InMemory' have been registered".CurrentUserBuilder/DateTimeProviderBuilder/ObjectArrayExtensionsmigrados detests/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/DocumentGuards—Guard.Against.InvalidCpfeGuard.Against.InvalidCnpjretornam o valor digit-only normalizado em sucesso (compatível com inicialização de value object). Internamente delegam paraDocumentHelper.Middleware/DevPackIdentityContextMiddleware+Extensions/ApplicationBuilderExtensions.UseDevPackIdentityContext()— bridge entreHttpContext.UsereAsyncLocalCurrentUserAccessor. Sem isso,RoleBasedPermissionEvaluatorrecebeUserRoles = []em produção. Mapper default extrai claimsNameIdentifier/sub,Name/name/preferred_username, eRole/role. Override via overload comFunc<HttpContext, UserInfo?>para layouts JWT custom.Extensions/ResultHttpExtensions.ToHttpResult(...)— bridgeResult<T>/Result→Microsoft.AspNetCore.Http.IResultpara Minimal APIs. Mapping default: Validation→400, NotFound→404, Unauthorized→403, Conflict→409, Failure/Problem→500. AceitaerrorMapperopcional.Extensions/MinimalApiPaginatedExtensions.MapPaginatedGet<TQuery,TDto>— encapsula bind individual depageNumber/pageSize/orderBy/order/filter/paginated, dispatcha via Mediator e renderiza comToHttpResult. Resolve gotcha do[AsParameters]sobrePaginatedQueryBase<T>.Domain/AuditedSoftDeletableEntity<TKey>: ctorprotected AuditedSoftDeletableEntity(TKey id) : base(id) {}— paridade comAuditedEntity<TKey>. Permiteprivate Pedido(Guid id, ...) : base(id)sem precisar deId = 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,ValidationErrormovidos do namespace raizITLab.Template.DevPackparaITLab.Template.DevPack.Core. Foldersrc/.../Results/renomeado parasrc/.../Core/. Update consumerusingdirectives. Marcado breaking por convenção; nenhum consumidor de produção existia no momento da release.DispatchDomainEventsInterceptorctor mudou de(IPublisher publisher)para(IServiceProvider serviceProvider). ResolveIPublisherper-save viaIServiceProvider.CreateAsyncScope(). Permite que o interceptor Singleton coexista com handlers Mediator Scoped (default DevPack) sem violar container validation. Regression test adicionado emtests/.../InterceptorsTests.cs.
Removed (10.3.0 — BREAKING)
ITLab.Template.DevPack.IResultremovido (sem uso real). Quem usavausing IResult = ITLab.Template.DevPack.IResult;para evitar colisão comMicrosoft.AspNetCore.Http.IResultpode 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" canonizandoApplication/Common/Authorization/{Project}Permissions.cs. Subseção "Permission convention: endpoint vs command" explicita queRequireAuthorizationé 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" (configuraropt.ServiceLifetime = ServiceLifetime.ScopedemAddMediatorquando handlers consomemIAppDbContext); 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 comILogger). EsclareceIDomainEvent : Mediator.INotification.Identity/README.md: seção "Authentication scheme — production checklist" (JWT Bearer / Entra ID / Cookie / pipeline order incluindoapp.UseDevPackIdentityContext()da 10.3.0). Nota sobrePlaceholderAuthenticationHandlerser scaffold do template, não publicado no DevPack.Infrastructure/EfCore/README.md: seção "EF migrations CLI" com--project+--startup-projecte cross-ref ao CLAUDE.md do template.- CI: smoke-test-convention-check via Roslyn em
tools/DocsSmokeCheck/— extrai blocos```csharpdos READMEs de áreas e os compila contra a DLL recém-buildada do DevPack. Suporta fence```csharp-noverifypara snippets ilustrativos.- Baseline pragmática: snippets de "Como usar" / wiring / chamadas em corpo de método ficaram marcados como
csharp-noverifypor padrão, pois referenciam símbolos do consumidor (Order,services,MyAppPermissions). Blocos auto-suficientes (definições de tipo) ficam comocsharppara 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).Mediatorfoi removido dos common usings da tool por colidir comITLab.Template.DevPack.Cqrs; blocos que precisam dele declaramusing Mediator;explicitamente. CONTRIBUTING.mddocumenta as convenções e o checklist do autor.
- Baseline pragmática: snippets de "Como usar" / wiring / chamadas em corpo de método ficaram marcados como
Changed
-
Cqrs/README.md: exemplo canônico mudou deICommand<Result<TResponse>>paraICommand<TResponse>— o wrappingResult<>é aplicado automaticamente pela assinatura do handler. "What NOT to do" no CLAUDE.md ratifica. -
Data/README.md: exemplo canônico mudou derecord ListOrdersQuery : PaginatedQueryBase<...>paraclass ListOrdersQuery : PaginatedQueryBase<...>— base é classe abstrata, records C# não herdam de classes não-record (CS8865/CS0115). Cross-ref aMapPaginatedGet(10.3.0). -
LLM-readable docs shipped inside the nupkg at
devpack-docs/. A pasta incluillms.txt(índice formato llmstxt.org),CLAUDE.md,DEVPACK.md,CHANGELOG.md, os 4 ADRs emadrs/, e os READMEs por área emareas/{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óximadotnet 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
.xmldoc da DLL é referência de tipo (IntelliSense), mas falta conhecimento operacional ("quando uso X vs Y?", "qual é o padrão para Z?"). Odevpack-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 aPackageReferencedefensiva deSystem.Security.Cryptography.Xml 10.0.7(mesmo comNoWarn="NU1510") durantedotnet 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 oRestoreEnablePackagePruning=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.yenquanto a base for .NET 10; quando .NET 11 for adotado, o versionamento passa para11.x.y.
Release temático: **auditoria com value object IANA-aware + role-based authorization
- ergonomia de DI**. Acompanhada por dois ADRs novos:
- ADR-003 —
AuditTimestampvalue object com timezone por timestamp. - ADR-004 — Role-based permission resolution.
Security
- Pin transitivo de
System.Security.Cryptography.Xml10.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 viaMicrosoft.AspNetCore.Identity.EntityFrameworkCore. Consumidores herdam automaticamente; o pin noDirectory.Packages.propsdo consumidor pode ser removido (era a recomendação da Fase 1 do templateITLab.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 viaRestoreEnablePackagePruning=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/NormalizeDateTimeOffsetBehaviorremovido. Pipeline de behaviors passa a ser Logging → Validation (Normalize não é mais registrado porAddDevPackBehaviors()). Com a migração de auditoria paraAuditTimestamp(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/NormalizeDateTimeOffsetAttributeremovido. Era consumido apenas pelo behavior removido.Domain/IAuditable: contrato reescrito em torno deAuditTimestamp.- Removidas:
Created(DateTimeOffset),LastModified(DateTimeOffset?),LastModifiedBy(string?),SetCreatedProperties(string, DateTimeOffset),SetUpdatedProperties(string, DateTimeOffset). - Adicionadas / renomeadas:
CreatedAt(AuditTimestamp),UpdatedAt(AuditTimestamp?),ModifiedBy(string?— renomeado deLastModifiedBy),SetCreatedAudit(string?, AuditTimestamp),SetUpdatedAudit(string?, AuditTimestamp). - Atribuição de usuário preservada (
CreatedBypermanece,LastModifiedByrenomeado paraModifiedBy).
- Removidas:
Domain/AuditedEntity/AuditedEntity<TKey>: campos públicos migrados conformeIAuditable.Domain/ISoftDeletable: contrato reescrito em torno deAuditTimestamp.- Removidas:
DeletedOnUtc(DateTimeOffset?),SoftDelete(string, DateTimeOffset). - Adicionada / mantida:
DeletedAt(AuditTimestamp?),DeletedBy(string?, mantido),SoftDelete(string?, AuditTimestamp).
- Removidas:
Domain/AuditedSoftDeletableEntity<TKey>: ajustado ao novoISoftDeletable.Identity/BaseIdentityUser: ajustado ao novoIAuditable/ISoftDeletable. Mesmas migrações de tipo.LastModifiedBy→ModifiedBy.Identity/BaseIdentityRole: ajustado ao novoIAuditable. Mesmas migrações.Infrastructure/EfCore/Interceptors/AuditableEntityInterceptor: construtor reescrito.- Antes:
(ICurrentUser currentUser, TimeProvider dateTime). - Depois:
(IServiceProvider serviceProvider). ResolveICurrentUserpor chamada viaIServiceProvider.CreateScope()— evita captura de scoped service em singleton interceptor. Constrói umaAuditTimestampcomDateTime.UtcNow+currentUser?.LocalTimeZonee populaCreatedBy/ModifiedBycomcurrentUser?.UserName.
- Antes:
Infrastructure/EfCore/Interceptors/SoftDeleteInterceptor: construtor reescrito.- Antes:
(ICurrentUser currentUser, TimeProvider dateTime). - Depois:
(IServiceProvider serviceProvider, TimeProvider dateTime). Mesmo padrão de scope-por-chamada; preservaDeletedByno entity viacurrentUser?.UserName.
- Antes:
Added
Domain/AuditTimestamp— value objectrecord (DateTime UtcDateTime, string? LocalTimeZone)que encapsula o par "instante UTC + IANA timezone". Cada propriedade de auditoria (CreatedAt,UpdatedAt,DeletedAt) carrega seu próprioAuditTimestamp— criação, atualização e deleção podem ocorrer em fusos diferentes e ficam independentemente reconstruíveis. Métodos:AuditTimestamp.Now(string? localTimeZone)eToLocal()que devolveDateTimeOffsetaplicando 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}_UtcDateTimee{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 deIPermissionEvaluatorque combinaICurrentUser.UserRoles+IRolePermissionResolver. Cache por-instância (Scoped lifetime).Authorization/DevPackAuthorizationExtensions.AddDevPackRoleBasedPermissionEvaluator<TResolver>()— registration conveniente.Infrastructure/EfCore/DevPackEfCoreInterceptorBuilder+ extensionservices.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) eISaveChangesInterceptor(consumido porAddDbContextviaprovider.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 deIDateTimeProvider. 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 deIDateTimeProviderque converte UTC paraICurrentUser.LocalTimeZone(IANA) para os membrosUserLocal*. MembrosSystem*usamTimeProvider.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 deICurrentUser).Abstractions/DateTimeProviderServiceCollectionExtensions—AddDevPackUtcDateTimeProvider()(Singleton) eAddDevPackLocalDateTimeProvider()(Scoped) para registro conveniente. Pré-requisito comum:services.AddSingleton(TimeProvider.System).
Migration notes
- Leituras de auditoria: substituir
entity.Created(DateTimeOffset) porentity.CreatedAt.UtcDateTime(DateTimeUTC) ouentity.CreatedAt.ToLocal()(DateTimeOffsetreconstruído na timezone original). Mesmo padrão paraLastModified→UpdatedAt,DeletedOnUtc→DeletedAt. - Atribuição de usuário:
CreatedByeDeletedBymantêm nome.LastModifiedByfoi renomeado paraModifiedBy— consumidores que liamentity.LastModifiedBydevem trocar paraentity.ModifiedBy. - Migrações EF:
- Cada propriedade de auditoria vira duas colunas:
{Name}_UtcDateTime(datetime2) e{Name}_LocalTimeZone(nvarchar(64), nullable). Por exemplo,CreatedAtse tornaCreatedAt_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>(...)emOnModelCreating— perde queryability dos campos individuais.
- Cada propriedade de auditoria vira duas colunas:
AuditableEntityInterceptorregistro: continua sendoservices.AddScoped<AuditableEntityInterceptor>()no DI —IServiceProvideré resolvido automaticamente. Sem mudança no chamador.SoftDeleteInterceptorregistro: idem —services.AddScoped<SoftDeleteInterceptor>(). As dependências (IServiceProvider,TimeProvider) são resolvidas pelo DI.TimeProvider.Systemdeve estar registrado (services.AddSingleton(TimeProvider.System)).- Pipeline de behaviors: remover qualquer registro manual de
NormalizeDateTimeOffsetBehaviorse aplicável;AddDevPackBehaviors()cuida do par Logging → Validation automaticamente. - Permissões role-based: substituir implementação custom de
IPermissionEvaluatorporservices.AddDevPackRoleBasedPermissionEvaluator<MyRolePermissionResolver>()quando o caso de uso for "permissões via roles".
[2.1.1] - 2026-05-01
Fixed
Data/QueryFilter: o operadorcontainsagora emite(prop != null && prop.Contains(value))em vez deprop.Contains(value). O comportamento anterior lançavaNullReferenceExceptionem LINQ-to-Objects quando a propriedade alvo eranull— limitação documentada na 2.1.0 via o testeParseFilter_StringContains_OnNullProperty_*. Em LINQ-to-EF Core o comportamento já era silencioso (SQL absorveNULL); o fix alinha as duas trilhas de execução. Inclui o operador composto[any] containsque usa o mesmo caminho.tests/.../Data/QueryFilterTests.cs: o testeParseFilter_StringContains_OnNullProperty_ThrowsNullReferenceExceptionfoi renomeado paraParseFilter_StringContains_OnNullProperty_ShouldExcludeWithoutThrowing, refletindo o comportamento corrigido. O fixture passou a manterBob.Notes = nullpara exercitar a null-safety em todos os cenários decontains.
[2.1.0] - 2026-05-01
Added
tests/.../Helpers/PermissionsGeneratorTests.cs: 18 testes cobrindoBuildDictionary(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) eGenerate(dispatch para cada modo).tests/.../Identity/MultiTenancy/TenantResolverTests.cs: 7 testes cobrindo o pipeline de contributors — sem contributors (Unresolved), resolução porTenantIdOrName, resolução porHandledflag, 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 comand/or, parênteses, propriedades aninhadas via dot notation, comparação contranullem 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/QueryFilteremiteprop.Contains(value)sem null-guard. Em LINQ-to-Objects (uso in-memory ou em testes) isso lançaNullReferenceExceptionquando a propriedade tem valornull. Em LINQ-to-EF Core o comportamento é silencioso — o SQL gerado absorveNULLcorretamente. Comportamento documentado no testeParseFilter_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 namespaceITLab.Template.DevPack.Guards. As classesObjectGuards,DateTimeOffsetGuardseDateOnlyGuardsforam extraídas deExtensions/GuardExtensions.cs(que era arquivo único com três classes coabitando) para arquivos individuais sob a nova pasta.Guards/README.mddocumenta a área seguindo o padrão das demais.DateOnlyGuardspassa a estar documentada — não havia entrada anterior em README algum.
Changed
- (Breaking, cosmético) Namespace dos guards mudou de
ITLab.Template.DevPack.ExtensionsparaITLab.Template.DevPack.Guards. Consumidores externos precisam adicionarusing ITLab.Template.DevPack.Guards;em arquivos que usemGuard.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.csmovido da raiz desrc/paraDtos/NameValue.cs; namespace atualizado deITLab.Template.DevPackparaITLab.Template.DevPack.Dtos. Mesma justificativa do bullet anterior.Dtos/README.mdagora listaNameValueeNameValue<T>. Helpers/README.md: corrigidosEPermissionsGeneratorTypemodos (Json/TsUnion/TsShapeem vez deFlat/Hierarchical/FlatWithPrefix), exemplo dePermissionsGeneratorreescrito sobreGenerate(...)real, e adicionada nota explicando queDateTimeOffsetHelpercoabita emDateTimeHelper.cs(mesmo namespaceHelpers).Dtos/README.md: tabela de Componentes ampliada para listarOptionDto<TValue>,OptionDto<TValue, TAdditional>,GenericKeyValueDto<TValue>eGenericKeyValueDto<TValue, TAdditional>(anteriormente omitidos), além de separarBaseKeyValueDtoem variantes<TValue>e<TValue, TAdditional>. Anotada a divergência arquivo↔classeScheduledJobConfig×ScheduledJobConfiguration.cs.Notifications/README.md: snippet sinaliza explicitamente queINotificationServiceé 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.mdeInfrastructure/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 deObjectGuardseDateTimeOffsetGuardsda tabela de Componentes (movidas paraGuards/); nota cross-reference adicionada.DEVPACK.md: nova entrada paraITLab.Template.DevPack.Guardsno mapa de namespaces.README.mdraiz: inventário de READMEs refleteGuards/README.md.
Removed
Extensions/GuardExtensions.cs: substituído por três arquivos sobGuards/.src/ITLab.Template.DevPack/NameValue.csda raiz: movido paraDtos/.Directory.Packages.props: removidas pinagens dead deClosedXMLeSendGrid(pacotes haviam sido removidos do.csprojem 2.0.0).tests/ITLab.Template.DevPack.Tests/Inspector.cs: teste exploratório comtry/catchsilencioso. Tinha um[Fact] FinalInspectque sempre passava sem assertions úteis (carregavaAsp.Versioning.Httpemtry { } 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.csna raiz do repositório: código exploratório referenciando pacotes removidos (Swashbuckle.AspNetCore,Asp.Versioning.ApiExplorer). Não estava em qualquerslnx/csproj.docs/devpack-inventory.mdinterno (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.Mediatorrenomeado paraITLab.Template.DevPack.Cqrs. A pastasrc/ITLab.Template.DevPack/Mediator/foi movida parasrc/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 doisICommand/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 pastaMessaging/reservada para integração com message broker (Service Bus). O termo "Cqrs" é específico, idiomático e sem colisão. Vejadocs/adrs/002-rename-mediator-to-cqrs.md.Migração: substituir
using ITLab.Template.DevPack.Mediator;porusing 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.mdraiz eCqrs/README.mdatualizados para refletir o novo nome de namespace e a ausência de colisão comMediator(martinothamar).Messaging/README.mdreescrito: 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 descreveEMessageDisposition,MessageHandlingResulteMessagingErrors.
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êmeoEntityHelper.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 viaArgumentNullException.ThrowIfNulle cobertura emDomainEventHelperTests(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.Abstractions3.0.2 (Mediatorby martinothamar) replaces MediatR. Source-generated CQRS dispatch withValueTaskeverywhere.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.csandAddDevPackBehaviors()extension — registers the trio Logging → Validation → Normalize in the recommended order.
Changed
Mediator/ICommand,ICommand<TResponse>,ICommandHandler<...>,IQuery<TResponse>,IQueryHandler<TQuery, TResponse>,INotificationEvent,IMediatorHandler,MediatorHandlerrewritten on top ofMediator(martinothamar). Public type names preserved.- Breaking: handler
Handlesignatures now returnValueTask<TResponse>instead ofTask<TResponse>. Pipelinenextdelegate is nowMessageHandlerDelegate<TMessage, TResponse>taking(message, ct)instead ofRequestHandlerDelegate<TResponse>taking(). - Breaking:
IMediatorHandler.PublishEvent<T>returnsValueTaskinstead ofTaskand accepts aCancellationTokenparameter. - Breaking:
MediatorHandlerconstructor takesMediator.IPublisherinstead ofMediatR.IMediator. Domain/IDomainEventnow extendsMediator.INotificationinstead ofMediatR.INotification. Public surface preserved.Infrastructure/EfCore/Interceptors/DispatchDomainEventsInterceptorconstructor takesMediator.IPublisher.DispatchDomainEventsreturnsValueTaskand accepts aCancellationToken.Behaviors/ValidationPipelineBehavior,RequestLoggingPipelineBehavior,NormalizeDateTimeOffsetBehaviorrewritten on top ofMediator.IPipelineBehavior<,>. Behavior semantics preserved.CoreConsts.csmoved toCoreConsts/CoreConsts.csto align with the per-area subdirectory convention; namespace unchanged.Identity/README.md: addedBaseIdentityUser,BaseIdentityRole,CurrentUserExtensions, andIdentityProviderNameto the Componentes table.Data/README.md: addedQueryFilterto the Componentes table and a usage example.PackageTags: replacedmediatrwithmediator.
Removed
MediatRNuGet dependency.MediatR.ContractsNuGet dependency.MagicMapperNuGet dependency. The DevPack no longer ships any mapping infrastructure; products use Mapperly or hand-written extension methods.Data/GroupConverter<T>(theAutoMapper.ITypeConverteradapter for grouped query results). It was not consumed byGroupableList<T>— server-side grouping is implemented inGroupableList<T>via reflection over dynamic LINQ projections.Data/GroupKey.SplitGroupconstant — only consumed by the removedGroupConverter<T>.Swagger/directory and theConfigureSwaggerOptionsandSwaggerDefaultValuesFiltertypes. Consumers must migrate toMicrosoft.AspNetCore.OpenApiand a UI of choice (e.g.Scalar.AspNetCore).Extensions/SwaggerExtensions.csand theUseSwaggerAndUIhelper: replaced by directMapOpenApi()/MapScalarApiReference()calls in consumer projects.Swashbuckle.AspNetCoreNuGet dependency.Asp.Versioning.MvcandAsp.Versioning.Mvc.ApiExplorerNuGet dependencies. Consumers needing API versioning on Minimal APIs can installAsp.Versioning.Httpdirectly.ClosedXMLNuGet dependency: no public types or internal usage found; removed to reduce package footprint.SendGridNuGet dependency: no public types or internal usage found; removed to reduce package footprint.
Migration notes for consumers
- Replace
using MediatR;withusing Mediator;in endpoint code that injects the mediator. Handler files and command/query records useusing ITLab.Template.DevPack.Cqrs;(renamed from.Mediatorin 2.0.1) for the wrappers. - Replace
services.AddMediatR(typeof(...))withservices.AddMediator(opt => opt.Namespace = "MyApp.Mediator";)from theMediator.SourceGeneratorpackage (added in the consumer project, not in the DevPack). - Replace
services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(...))calls withservices.AddDevPackBehaviors(). - Replace
Task<TResponse>handler return types withValueTask<TResponse>. - Adapt pipeline behavior implementations to
IPipelineBehavior<TMessage, TResponse>fromMediatornamespace, withwhere TMessage : IMessageconstraint andMessageHandlerDelegate<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()withservices.AddOpenApi()/app.MapOpenApi()/app.MapScalarApiReference()(the latter requires theScalar.AspNetCoreNuGet package). - For permissions, register
IPermissionEvaluator(your implementation) and callservices.AddDevPackAuthorization().
[1.0.0] - 2026-04-23
Added
- Initial release