# Release Notes v4.0.0 Data de lançamento: 24/08/2026 ## 🆕 Resumo Versão com **mudanças incompatíveis** no `Tooark.Notifications`, `Tooark.Mediator`, `Tooark.Observability`, `Tooark.Validations`, `Tooark.Securities`, `Tooark.Exceptions`, `Tooark.Extensions`, `Tooark.Enums`, `Tooark.Attributes`, `Tooark.Utils` e `Tooark.Dtos`, com o **pipeline de behaviors** no Mediator e com os **novos pacotes `Tooark.Mediator.EntityFrameworkCore`**, que move a persistência do Entity Framework Core para a esteira do pipeline, **e `Tooark.AspNetCore`**, que concentra o que depende de ASP.NET Core e devolve os demais pacotes à condição de uso geral. No Validations, CPF e CNPJ passaram a exigir dígitos verificadores e o CNPJ passou a aceitar letras, acompanhando o formato alfanumérico vigente desde julho de 2026; validações que estouravam com valor nulo passaram a reprovar a entrada. No Notifications, criar e limpar notificações passou a ser operação protegida, impedindo que código externo fabrique erros em uma entidade ou apague o resultado de uma validação. No Mediator, as opções passaram a ser recebidas pelo padrão Options, requisições com mais de um manipulador falham no registro e `IPipelineBehavior` passou a permitir tratar preocupações transversais fora dos manipuladores. No Observability, o sinalizador global de dados sensíveis foi renomeado para eliminar a ambiguidade com as opções granulares de mesmo nome. Acompanha as correções de segurança do `Tooark.Securities`, que eliminam comportamentos silenciosos (fallback de chave, troca de algoritmo, quebra de round-trip no modo legado), categorizam corretamente os erros de validação JWT e modernizam o dispatch de tokens para o `JsonWebTokenHandler`, que passou a ser alcançável também por `ValidateAsync`, sem a espera bloqueante. No Utils, o idioma atual deixou de ser um estado global do processo, que fazia o idioma de uma requisição vazar para as demais, e a conversão de um upload para `MemoryStream` passou a copiar o conteúdo binário, que antes era lido como texto e devolvia nulo para qualquer arquivo real. O `FileConvert` também recebeu um endurecimento de segurança: a extensão extraída passou a ser validada como extensão, e a conversão passou a ter limite de tamanho, fechando um caminho de travessia de diretório e um de esgotamento de memória. No Attributes, os seis atributos deixaram de gravar a mensagem de erro no próprio atributo — estado que o framework de validação compartilha entre requisições concorrentes e que descartava a mensagem configurada pelo consumidor. No Extensions, os arquivos de tradução passaram a cobrir **todas** as mensagens emitidas pelos pacotes Tooark — antes 73 chaves saíam como texto cru — e o localizador deixou de fixar no cache os parâmetros da primeira consulta feita em um idioma sem arquivo próprio. No Dtos, a última página de uma listagem deixou de ser inalcançável pelos links de navegação, e o tamanho da página ganhou um teto — sem ele, uma única requisição podia pedir todos os registros. Inclui também a atualização das referências do repositório renomeado para `tooark-cs`. Como esta versão já carrega quebras de compatibilidade, foram aplicadas as correções de design que dependiam de uma major e estavam represadas desde a v3.3.4. --- ## ⚠️ Mudanças Incompatíveis (Tooark.Mediator) - **O `Mediator` recebe `IOptions` no lugar de `MediatorOptions`**: o registro de `MediatorOptions` direto no container existia apenas para não quebrar o construtor na v3.3.4 e congelava as opções fora do pipeline de Options. Esse registro foi removido. **O que fazer**: configure por `AddTooarkMediator(options => ...)` ou `services.Configure(...)`; para resolver as opções, use `IOptions`. Registrar `MediatorOptions` diretamente no container deixou de ter efeito — inclusive silenciosamente, então vale conferir quem fazia isso. - **Requisição com mais de um manipulador falha no registro**: uma requisição é processada por um único manipulador, resolvido do container no despacho. Com mais de um registrado, o container devolvia o último e os demais nunca executavam, sem qualquer aviso. `AddTooarkMediator` passa a lançar `Handler.Duplicated`, identificando a requisição e os manipuladores em conflito. **O que fazer**: remover o manipulador duplicado. Notificações seguem aceitando quantos manipuladores forem registrados. ## ⚠️ Mudanças Incompatíveis (Tooark.Validations) - **CPF e CNPJ passaram a exigir dígitos verificadores**: `IsCpf`, `IsCnpj`, `IsCpfCnpj`, `IsCpfRg` e `IsCpfRgCnh` validavam apenas o formato, então `123.456.789-00` era aceito como CPF. Agora o documento precisa também conferir no cálculo do dígito. **O que fazer**: dados já gravados que passavam apenas pelo formato passam a ser reprovados na próxima validação — vale conferir a base antes de subir. Sequências de um único caractere repetido (`111.111.111-11`, `00.000.000/0000-00`) também são rejeitadas: elas satisfazem o módulo 11 e eram aceitas. - **O CNPJ aceita letras**: o formato passou a admitir caracteres alfanuméricos nas doze primeiras posições, conforme o CNPJ alfanumérico vigente desde julho de 2026. Quem persiste CNPJ em coluna numérica ou aplica máscara apenas de dígitos precisa acomodar letras. A caixa é normalizada, então `12.abc.345/01DE-35` é aceito. - **`IsRg` e `IsCnh` passaram a conferir o dígito verificador**, e não apenas o formato. O ecossistema estava dividido: o value object `Rg` reprovava dígito incorreto, por passar pelo `EDocumentType`, enquanto `new Validation().IsRg(...)` aprovava — duas APIs da mesma biblioteca com veredictos diferentes sobre o mesmo documento. **O que fazer**: RG e CNH gravados com dígito incorreto passam a ser reprovados. O dígito do RG continua opcional: informado sem ele, o documento é aceito. - **`AreEquals` e `AreNotEquals` de listas passaram a comparar conteúdo**: comparavam referência, então duas listas de conteúdo idêntico eram reportadas como diferentes. Comparações que dependiam do comportamento antigo mudam de resultado. - **Código de notificação dos documentos com dígito verificador** mudou de `T.VLD.RGX1` para `T.VLD.DOC1`, já que a validação deixou de ser apenas por expressão regular. Vale para todas as validações de documento — `IsCpf`, `IsCnpj`, `IsCnh`, `IsRg`, `IsCpfCnpj`, `IsCpfRg` e `IsCpfRgCnh`. **O que fazer**: ajustar quem filtra notificações por esse código. - **IPv6 comprimido passou a ser válido**: `::1` e `2001:db8::1` eram reprovados porque o padrão exigia a forma completa. Validações que contavam com essa reprovação mudam de resultado. O padrão de `IP` composto acompanha a correção. - **Padrão de URL ancorado no final**: sem a âncora, qualquer sufixo era aceito após uma URL válida. O padrão passou a aceitar explicitamente query string e fragmento, mantendo as URLs legítimas válidas. ## ⚠️ Mudanças Incompatíveis (Tooark.Observability) - **`ObservabilityOptions.DataSensitive` renomeado para `AllowSensitiveData`**: o nome `DataSensitive` designava duas coisas diferentes na mesma árvore de configuração — na raiz, um `bool` que **desliga** a sanitização; em `Tracing`, um objeto cujo `HideQueryParameters` **liga** a sanitização. A chave antiga não é mais lida. **O que fazer**: renomear a chave em `appsettings.json` e no código. A migração esquecida falha para o lado seguro: sem o bind, a propriedade fica no default `false` e a sanitização passa a ser aplicada. ## ⚠️ Mudanças Incompatíveis (Tooark.Notifications) - **Criar e limpar notificações passou a ser protegido**: `AddNotification` (todas as sobrecargas), `AddNotifications(ICollection)` e `Clear()` deixaram de ser públicos. Como `Notification` é a base de `BaseEntity`, `ValueObject` e `Validation`, qualquer código externo podia injetar erros em uma entidade e, principalmente, chamar `entidade.Clear()` e fazer uma entidade inválida parecer válida. Continuam **públicas** as sobrecargas de agregação `AddNotifications(Notification)` e `AddNotifications(params Notification[])`, que compõem notificações já existentes — o cenário legítimo de uso externo. **O que fazer**: mover a chamada para dentro do próprio objeto que notifica, ou agregar via `AddNotifications`. - **`Count` mudou de `long` para `int`**: o valor vem de uma lista em memória e nunca ultrapassa `int.MaxValue`. **O que fazer**: ajustar atribuições explicitamente tipadas como `long` (a conversão implícita para `long` continua válida). - **Mensagem nula, vazia ou em branco gera uma única mensagem**: antes, mensagem nula em `NotificationItem` gerava `Notifications.MessageUnknown` e mensagem vazia via `AddNotification` gerava `Notifications.MessageNullEmpty`. Agora todos os casos geram `Notifications.MessageNullEmpty`, e a constante `NotificationErrorMessages.MessageUnknown` foi **removida** — a biblioteca não emite mais essa chave, então mantê-la só acumularia API sem uso. **O que fazer**: substituir referências à constante por `MessageIsNullOrEmpty` e remover o mapeamento de `Notifications.MessageUnknown` dos arquivos de tradução. - **Mensagem vazia deixou de existir**: `new NotificationItem("")` retornava `Message` vazia; agora retorna `Notifications.MessageNullEmpty`. ## ⚠️ Mudanças Incompatíveis (Tooark.Securities) - **`IJwtTokenService` ganhou `ValidateAsync`**: o handler da Microsoft.IdentityModel expõe a validação apenas de forma assíncrona, então a única sobrecarga existente encapsulava uma espera bloqueante — sem alternativa para quem já está em fluxo assíncrono. `ValidateAsync` passou a ser o caminho direto ao handler e `Validate` virou o encapsulamento síncrono dele, com resultado idêntico. **O que fazer**: quem apenas consome o serviço não precisa mudar nada; quem **implementa** `IJwtTokenService` (dublês de teste artesanais, implementações próprias) precisa implementar o novo membro. Dublês criados com Moq ou similar seguem funcionando sem alteração. - Não há `CreateAsync`: o handler cria o token de forma síncrona, e uma sobrecarga assíncrona ali seria assíncrona apenas na aparência. - `ICryptographyService` segue somente síncrono: `Encrypt` e `Decrypt` são AES em memória, sem operação de I/O que justifique a assinatura assíncrona. ## 🆕 Novo pacote: Tooark.AspNetCore O `Microsoft.AspNetCore.App` não é uma dependência NuGet comum: declará-lo faz a aplicação **exigir o runtime do ASP.NET Core instalado**, e o requisito é contagioso — propaga para todo pacote que referencia, e para quem referencia esses. Até a v3 esse requisito vinha do `Tooark.Extensions`, por causa de **um único arquivo** de trinta linhas, o `ModelStateExtension`. Um aplicativo de console que só usasse o `Tooark.ValueObjects` recebia `Microsoft.AspNetCore.App` no próprio `runtimeconfig.json` e não subia em uma imagem `mcr.microsoft.com/dotnet/runtime`. O `ModelStateExtension` passou para o `Tooark.AspNetCore`, e o efeito foi medido em um aplicativo de console que referencia apenas o `Tooark.ValueObjects`: ```json // antes // depois "frameworks": [ "framework": { { "name": "Microsoft.NETCore.App" }, "name": "Microsoft.NETCore.App" { "name": "Microsoft.AspNetCore.App" } } ] ``` Situação por pacote a partir da v4.0.0: | Pacote | Exige o runtime do ASP.NET Core | | -------------------- | ---------------------------------------------------------------------------------- | | `Tooark.AspNetCore` | **sim** — é o propósito dele | | `Tooark.Dtos` | **sim** — `SearchDto`, `PaginationDto` e `ResponseDto` usam tipos do MVC e do Http | | `Tooark` (agregador) | **sim** — por referenciar os dois acima | | Todos os demais | **não** | **O que fazer**: quem usa `ModelState.GetErrors()` precisa instalar o `Tooark.AspNetCore` e trocar o `using Tooark.Extensions` por `using Tooark.AspNetCore.Extensions`. Quem usa o agregador `Tooark` não muda nada. Os demais deixam de exigir o runtime do ASP.NET Core, o que só amplia onde os pacotes rodam. O `Tooark.Dtos` passou a **declarar** o framework compartilhado, que antes chegava por transitividade do `Tooark.Extensions` — era o caso de dependência usada sem declarar registrado na revisão. Com isso, o pacote avulso `Microsoft.AspNetCore.Http` da linha 2.x saiu do `.csproj`. Se `SearchDto`, `PaginationDto` e `ResponseDto` devem morar no `Tooark.AspNetCore` é decisão da revisão do `Tooark.Dtos`. ## ⚠️ Mudanças Incompatíveis (Tooark.Dtos) - **`SearchDto.PageSize` passou a ter um teto**, definido por `PageSizeMax` e valendo 100 por padrão. Antes o valor era aceito sem limite: `PageSize = long.MaxValue` passava, e uma única requisição podia pedir a base inteira. **O que fazer**: um endpoint que precise de páginas maiores sobrescreve o limite no próprio DTO, com `protected override long PageSizeMax => 5000;`. Use a forma de expressão — uma propriedade automática com inicializador não serve, porque o limite é consultado pelo construtor da classe base. - **`PaginationDto.Next` passou a alcançar a última página.** A condição de parada tinha um `+1` a mais, e a última página nunca aparecia na navegação: com `Total=100` e `PageSize=10`, a página 9 não apontava para a 10. **O que fazer**: nada, mas respostas de API existentes passam a trazer `next` e `nextLink` onde antes vinham nulos. - **`ResponseDto.Errors` e `ResponseDto.Metadata` passaram a ser `IReadOnlyList`**, com coleções que rejeitam alteração. Antes eram `IList` e podiam ser alteradas de fora, inclusive depois da resposta pronta. **O que fazer**: use `SetMetadata` e `AddMetadata`; atribuições tipadas como `IList` param de compilar. - **`ResponseDto(T?, int, HttpRequest)` passou a receber `long`**, acompanhando o `Total` do `PaginationDto`. Compatível em código-fonte; exige recompilação. - **`MetadataDto` deixou de descartar o valor quando a chave é vazia.** Chave e valor passaram a ser independentes: `new MetadataDto("", "azul")` guardava `Value` vazio e agora guarda `"azul"`. - **`SearchOrderDto.OrderBy` passou a ser `string?`**, refletindo que ele nasce sem valor. Antes era declarado não anulável e inicializado com `null!`. - **`AddTooarkDtos` deixou de registrar `IStringLocalizer` como `StringLocalizer`.** O registro sobrescrevia o do `AddJsonStringLocalizer`, e o tipo resolvido passava a depender da ordem das chamadas. **O que fazer**: nada — o localizador resolvido continua sendo o do Tooark, agora independente da ordem. ## ⚠️ Mudanças Incompatíveis (Tooark.Extensions) - **`ModelStateExtension` saiu do pacote** para o novo `Tooark.AspNetCore`. Ver a seção acima. - **O `IDistributedCache` deixou de ser exigido.** `JsonStringLocalizerExtension` e `JsonStringLocalizerFactory` passaram a ter construtor sem parâmetros, e o `AddJsonStringLocalizer` não chama mais `AddDistributedMemoryCache()`. **Por quê**: as traduções passaram a ser indexadas por chave em memória, então a consulta ao cache distribuído virou uma camada na frente de uma busca em dicionário — com um cache remoto, como o Redis, era uma ida à rede por mensagem traduzida para buscar o que já estava no processo. **O que fazer**: quem construía o localizador manualmente remove o argumento; quem depende de `AddDistributedMemoryCache()` registrado como efeito colateral precisa chamá-lo por conta própria. - **O idioma `pt-PT` deixou de ser distribuído.** Ficam `en-US`, `es-ES` e `pt-BR`. O português europeu era idêntico ao brasileiro em todas as chaves, então mantê-lo dobrava o trabalho de tradução sem entregar diferença. **O que fazer**: nada, se `pt-PT` não era usado. Quem o usava passa a receber as mensagens em `en-US`, o idioma padrão, e pode restaurar o comportamento anterior colocando um `Resources/pt-PT.json` próprio na aplicação — o mecanismo de arquivo adicional continua valendo para qualquer idioma. - **`LocalizedString.ResourceNotFound` passou a refletir a realidade.** Era sempre `false`, inclusive para chave inexistente, porque a comparação era feita contra um valor que nunca era nulo. Agora é `true` quando a chave não existe em nenhum dos dois idiomas consultados. **O que fazer**: código que ignorava a propriedade não muda; código que a usava recebia sempre `false` e passa a receber o valor correto. - **Chave inexistente passou a devolver o texto recebido inteiro**, e não apenas o trecho anterior ao primeiro ponto e vírgula. É o comportamento do `ResourceManagerStringLocalizer` do framework, onde `Value` coincide com `Name` quando o recurso não existe. Na prática, um texto que não é chave do Tooark — como as mensagens que o model binding do ASP.NET Core gera — passa pelo localizador sem ser truncado. - **Os conversores de caixa de `StringExtensions` deixaram de devolver nulo.** `FromSnakeToPascalCase`, `FromSnakeToCamelCase`, `FromSnakeToKebabCase`, `FromPascalToSnakeCase`, `FromCamelToSnakeCase` e `FromKebabToSnakeCase` devolviam o próprio valor recebido quando ele era nulo, apesar do retorno declarado como não anulável. Passam a devolver string vazia, e os parâmetros passaram a ser declarados como anuláveis. - **O pacote passou a exigir o runtime do ASP.NET Core**: os tipos do ASP.NET Core vinham de pacotes NuGet avulsos da linha 2.x, que funcionavam em qualquer aplicação; agora vêm do framework compartilhado, declarado por `FrameworkReference`. A mudança elimina a árvore transitiva legada — incluindo o `Newtonsoft.Json` vulnerável — e alinha o pacote à orientação atual para bibliotecas de ASP.NET Core. **O que fazer**: aplicações de console ou worker que consomem o `Tooark.Extensions` (direta ou indiretamente, via `Tooark.ValueObjects`, `Tooark.Entities` ou o agregador `Tooark`) precisam do runtime do ASP.NET Core instalado. Compilação e execução seguem normais; o que muda é o pré-requisito registrado no `runtimeconfig.json`. Em imagens de contêiner, use a base `aspnet` em vez da `runtime`. - **Os arquivos de idioma passaram a ser embutidos no assembly**, em vez de distribuídos como `contentFiles`. O alvo de MSBuild que os READMEs mandavam colar no `.csproj` **deve ser removido**: ele não é mais necessário e é ativamente nocivo, porque copia arquivos de versões antigas que ainda estejam no cache global do NuGet. **O que fazer**: apague o `Target Name="CopyNugetContentFiles"` do seu projeto. A sobrescrita por `Resources/{idioma}.json` continua funcionando igual, mesclada chave a chave sobre o arquivo embutido. ## ⚠️ Mudanças Incompatíveis (Tooark.Exceptions) - **`GetErrorMessages()` e `GetNotifications()` passaram a devolver coleções somente leitura**: devolviam as listas internas, então `excecao.GetErrorMessages().Clear()` esvaziava o erro que a exceção carregava — uma falha desaparecia no meio do tratamento, e o `Message` ficava dessincronizado da lista. É o mesmo defeito de encapsulamento corrigido no `Tooark.Notifications`. O retorno mudou de `IList` para `IReadOnlyList`. **O que fazer**: leitura por índice, `Count` e `foreach` continuam iguais; quem atribuía o retorno a uma variável `IList` precisa trocar o tipo, e quem alterava a coleção deve construir uma nova exceção. - **`Microsoft.AspNetCore.Mvc` deixou de ser dependência**: o pacote não usava nada de ASP.NET Core — o código HTTP sempre veio de `System.Net.HttpStatusCode`, do próprio runtime. Como `Tooark.Exceptions` é dependência de quase todo o ecossistema, o meta-pacote legado do ASP.NET Core 2.x chegava em praticamente todo consumidor. **O que fazer**: aplicações que usavam tipos do ASP.NET Core contando com essa dependência transitiva precisam referenciá-lo explicitamente. ## ⚠️ Mudanças Incompatíveis (Tooark.Enums) - **Conversão de instância nula passou a lançar**: `(int)` e `(string)` sobre um `ECloudProvider`, `EDocumentType` ou `EFileType` nulo estouravam `NullReferenceException` sem contexto. Passam a lançar `InternalServerErrorException` com `Invalid.Parameter;null`, o mesmo erro já usado pelos enums do `Tooark.Observability` — não existe id nem descrição correta para devolver, e devolver zero seria inventar um dado. - **`ECloudProvider` deixou de diferenciar caixa**: `"aws"` resolvia para `None` enquanto `"AWS"` resolvia para `Amazon`, e os outros dois enums já normalizavam. Uma chave de `appsettings.json` escrita em minúsculo virava "nenhum provedor" sem aviso. Os três passaram a normalizar caixa e espaços das extremidades. **O que fazer**: configurações que dependiam da reprovação por caixa mudam de resultado. - **`EDocumentType.RG` e `EDocumentType.CNH` passaram a usar o mesmo cálculo do `Tooark.Validations`**, e não mais implementações próprias. O comportamento de validade é o mesmo; o que muda é a origem do cálculo. - **O pacote passou a depender do `Tooark.Exceptions`**, necessário para o erro de conversão de instância nula. O `Tooark.Exceptions` já chegava por transitividade em quem usa o `Tooark.Enums`, através do `Tooark.Validations`, então a árvore de dependências do consumidor não muda de tamanho. ## ⚠️ Mudanças Incompatíveis (Tooark.Attributes) - **A mensagem de erro deixou de ser gravada em `ErrorMessage`** e passa a vir no `ValidationResult` de cada validação. O framework de validação reaproveita a mesma instância do atributo em todas as validações daquele campo, então gravar nela era escrever em estado compartilhado. **O que fazer**: código que lia `atributo.ErrorMessage` depois de chamar `IsValid` precisa usar `GetValidationResult(valor, contexto)?.ErrorMessage`. Quem consome pelo `Validator`, pelo ModelState ou pelo MVC não muda nada — passa a receber a mensagem correta. - **`ErrorMessage` e `ErrorMessageResourceName` configurados no atributo passaram a ser respeitados.** Antes eram sobrescritos na primeira falha, e a mensagem do consumidor era descartada em definitivo — a instância é cacheada e nunca mais voltava ao valor original. **O que fazer**: quem configurou mensagem própria e se acostumou a receber `Field.Invalid;...` passa a receber a mensagem que configurou. - **Tipo de documento não reconhecido passou a lançar.** `[DocumentValidation("CPFF")]`, um erro de digitação, virava `EDocumentType.None` e **aceitava qualquer valor**, desligando a validação inteira sem aviso. Agora lança `InternalServerErrorException` na primeira validação, com `Attributes.DocumentTypeUnknown;{tipo}`. `None` continua aceito quando pedido explicitamente. **O que fazer**: corrigir o tipo escrito no atributo. - **Link de vídeo sem nenhum provedor habilitado passou a lançar**, em vez de reprovar todo valor. A configuração não tinha interpretação possível: nenhum link poderia ser aceito. - **Os critérios do `PasswordValidationAttribute` passaram a valer como configurados.** Desabilitar todos aplicava silenciosamente a complexidade completa, e comprimento menor que 8 era elevado para 8. Agora "nenhum critério" significa exigir apenas o comprimento — política legítima de frase secreta — e o comprimento informado é respeitado. **O que fazer**: quem passava `length` abaixo de 8 contando com a elevação precisa informar 8; quem desabilitava todos os critérios contando com a complexidade completa precisa habilitá-los. - **Valor composto apenas por espaços passou a ser tratado como ausente**, resultando em `Field.Required` em vez de `Field.Invalid`, acompanhando a mesma correção feita no `Tooark.Notifications`. - **`DocumentValidationAttribute` passou a receber o tipo como texto**: o construtor recebia `EDocumentType`, que é uma classe e portanto **não é um tipo de parâmetro de atributo válido** — nenhuma forma de aplicar o atributo compilava, com `CS0181`. O recurso era público e inutilizável. **O que fazer**: use `[DocumentValidation("CPF")]`; os valores aceitos são `CPF`, `RG`, `CNH`, `CNPJ`, `CPF_CNPJ`, `CPF_RG` e `CPF_RG_CNH`, sem diferenciar caixa. ## ⚠️ Mudanças Incompatíveis (Tooark.Utils) - **`Language.Current` e `Language.CurrentCulture` passaram a acompanhar o fluxo de execução**, e não mais um campo estático do processo. O idioma definido por uma requisição vazava para todas as outras: em uma API multi-idioma sob carga, uma requisição em `pt-BR` podia receber as mensagens no idioma de uma requisição concorrente. Como o `JsonStringLocalizerExtension` do `Tooark.Extensions` consome `Language.Current`, o vazamento alcançava as mensagens traduzidas de validações e notificações de todo o ecossistema. **O que fazer**: nada, se o idioma já era definido por requisição — passa a funcionar. Código que dependia do estado global (definir a cultura em um ponto e lê-la em outra thread, sem propagação de contexto) precisa passar o idioma explicitamente. - **`ILanguage.SetCultureInfo` passou a aceitar nulo** (`string?` e `CultureInfo?`), acompanhando `Language.SetCulture`. **O que fazer**: implementações próprias da interface precisam ajustar a assinatura. - **`Language.Default` deixou de ser campo e passou a ser propriedade**, para refletir a instância corrente de `ILanguage` em vez de congelar o valor da inicialização estática. Compatível em código-fonte; exige recompilação. - **`FileConvert.ToMemoryStream(IFormFile)` passou a copiar o conteúdo binário do arquivo.** Antes, lia o arquivo com `StreamReader.ReadToEnd()` e procurava `"base64,"` no texto — o que devolvia nulo para qualquer upload real e só funcionava se o arquivo enviado fosse um texto contendo uma data URL. **O que fazer**: quem enviava data URL dentro de um arquivo de texto passa a receber os bytes do texto, e não o conteúdo decodificado; use `ToMemoryStream(string)` com o conteúdo do arquivo nesse caso. - **`GetInfo` deixou de exigir que as propriedades sejam `string`**: os valores eram lidos com um cast direto, que estourava `InvalidCastException` para qualquer outro tipo. Passam a ser convertidos com `ToString()`, e valor nulo devolve string vazia em vez de nulo declarado como não anulável. **O que fazer**: nada, exceto para quem dependia de receber `null`. - **`FileConvert.Extension` passou a validar o que devolve.** O retorno era o texto declarado pelo cliente, sem verificação: `data:image/..\..\..\web.config;base64,...` devolvia `..\..\..\WEB.CONFIG`, e um consumidor que montasse o caminho de gravação com esse valor escrevia fora do diretório pretendido. Passam apenas letras e dígitos ASCII, com no máximo dez caracteres. **O que fazer**: tipos MIME que não têm forma de extensão passam a resultar em `null` — `image/svg+xml` devolvia `SVG+XML` e `application/vnd.ms-excel` devolvia `VND.MS-EXCEL`, valores que já não serviam como extensão. Quem precisa mapear tipo MIME para extensão precisa fazer esse mapeamento no próprio consumidor. - **As conversões passaram a ter limite de tamanho**, com o parâmetro `maxBytes` e o padrão `FileConvert.DefaultMaxBytes` (5MB, o mesmo do `FileValid`). Acima do limite é lançada uma `PayloadTooLargeException` (HTTP 413). **O que fazer**: quem converte arquivos maiores que 5MB precisa informar o limite; quem já tratava o retorno nulo precisa passar a tratar também a exceção. - **`ToMemoryStream(string)` passou a exigir o marcador `;base64,` completo**, e lê o conteúdo logo depois dele. Antes bastava a string conter `base64,` em qualquer posição, e o conteúdo era lido a partir da **última** vírgula: `data:text/plain;base64,QUJD,WFla` decodificava `WFla`, e não o trecho que segue o marcador. **O que fazer**: strings que não seguem o formato de data URL passam a resultar em `null`. - **`GenerateString.Hexadecimal` passou a devolver exatamente o tamanho solicitado.** Tamanhos ímpares devolviam um caractere a menos: `Hexadecimal(7)` retornava 6 caracteres. Por consequência, `Token` com comprimento ímpar também passou a bater o tamanho pedido. ## ⚠️ Mudanças Incompatíveis (Tooark.ValueObjects) - **Objeto de valor reprovado passou a devolver string vazia, e não nulo.** Vinte e oito dos 33 objetos declaravam o campo interno como `= null!;` — uma promessa de não-nulo que a própria classe quebrava; os quatro de auditoria já usavam `Guid.Empty` e o `DelimitedString` já usava `string.Empty`. `new Cpf("111").Number` devolvia `null` em uma propriedade `string`, e `ToString()` também: `object.ToString()` é anotado como não anulável, então `$"documento: {cpf}"` e a serialização passavam a depender de sorte. **O que fazer**: código que testava `if (cpf.Number == null)` passa a testar `IsValid`, ou `Number.Length == 0`; comparações com nulo param de encontrar o caso. - **A conversão implícita de saída passou a exigir uma instância.** `string numero = cpfNulo;` estourava `NullReferenceException` dentro do operador, sem indicar qual conversão falhou. Agora lança `InternalServerErrorException` com `Invalid.Parameter;null`, o mesmo contrato já adotado no `Tooark.Enums`. **O que fazer**: nada, se a referência nunca é nula; quem convertia referência possivelmente nula precisa verificar antes. - **`Password` deixou de converter implicitamente para `string` e `ToString()` passou a mascarar.** A senha em texto puro saía sozinha em qualquer interpolação, log estruturado ou serialização — `logger.LogInformation("{Senha}", senha)` gravava `Senha@123` no arquivo de log. `ToString()` devolve `Password.Mask` (`********`) quando há valor, e o valor só sai por `Value`, explicitamente. **O que fazer**: onde a conversão implícita era usada de propósito, troque por `.Value` — a mudança torna o vazamento visível na revisão de código em vez de acidental. - **Os critérios do `Password` passaram a valer como configurados**, pela mesma correção feita no `PasswordValidationAttribute`: os dois passaram a montar o padrão pelo `PasswordPattern` do `Tooark.Validations`, e antes discordavam sobre a mesma senha. **O que fazer**: ver a seção do `Tooark.Attributes` — as regras de migração são as mesmas. - **`RegexValidation.Match` e `NotMatch` passaram a aceitar `string?`** (`Tooark.Validations`), nas quatro sobrecargas, acompanhando o que os objetos de valor já lhes entregavam. Compatível em código-fonte; exige recompilação. ## ⚠️ Mudanças Incompatíveis (conformidade de nulabilidade) - **Dezoito assinaturas declaravam parâmetro não anulável e tratavam a ausência no corpo.** A assinatura mentia: quem passava um `string?` recebia aviso do compilador para um caminho que sempre funcionou, e ou desligava o aviso, ou escrevia um `!` que apagava a checagem em volta. Foram ampliadas para anulável, o que **não quebra chamada existente** — quem passa valor presente compila igual. Alcança `NotificationItem(message, key, code)`, `IsEmailOrEmpty`, `IsEmailDomainOrEmpty`, os construtores de `Email` e `LinkVideo`, e onze métodos privados ou internos. - **A varredura foi feita por detector, não pela lista anterior**, que tinha sete entradas: onze casos faltavam e um era falso positivo. `DocumentDigit.IsRepeated` **não** trata nulo — todos os chamadores garantem valor —, então a assinatura dele estava certa e ficou como está. - **`CryptographyService.Encrypt` e `Decrypt` também ficaram como estão.** Eles _lançam_ para nulo, convertendo-o em `BadRequestException`. Método que recusa nulo está corretamente declarado como não anulável: a guarda é defesa para chamador sem anotação, não tratamento de ausência. - **Comparação textual com valor ausente mudou de resultado.** `StringValidation` tinha duas sobrecargas de `Validate`: a que compara tamanho tratava nulo como vazio, a que compara texto não. Alinhadas, `AreEquals(null, "")` passa a ser **válido** onde antes era inválido, e `AreNotEquals(null, "")` passa a ser inválido. **O que fazer**: nada, salvo para quem dependia de distinguir nulo de vazio nessas comparações — o pacote unifica os dois em todo o resto. ## ⚠️ Mudanças Incompatíveis (Tooark.Validations — padrão de email) - **O padrão de email passou a aceitar nick e rótulo de domínio de um único caractere.** O anterior exigia dois em cada parte, e reprovava endereços legítimos e existentes: `x@google.com`, `a@b.co` e `ab@b.co`. A mudança alcança de uma vez o `RegexPattern.Email`, o `EmailValidationAttribute` do `Tooark.Attributes` e o `Email` do `Tooark.ValueObjects`. **O que fazer**: nada — a validação afrouxou, então nenhum endereço antes aceito passa a ser reprovado. Quem dependia da recusa desses endereços precisa validar por conta própria. - **`RegexPattern.EmailDomain` acompanhou**, pelo mesmo motivo: `@b.co` e `@a.b` eram reprovados. - **O sinal de mais segue bloqueado**, por decisão. `nome+tag@exemplo.com` é válido pelo RFC 5322 e usado por Gmail e Outlook para rotular endereços, mas o pacote mantém a recusa — junto com os demais caracteres especiais no nick. Há teste fixando isso, para a escolha não se perder. - **Efeito colateral resolvido**: `Email.MinLength` vale 6, e o padrão anterior exigia no mínimo sete caracteres (`ab@cd.e`), então a constante nunca era a regra que decidia. Agora ela é. ## ⚠️ Mudanças Incompatíveis (Tooark.Entities) - **Chamada de escrita recusada deixou de sujar a entidade.** Todo `Set*` acumulava a validação na própria entidade antes de lançar, e a notificação nunca era limpa: depois de uma chamada recusada, a entidade ficava inválida em definitivo e **toda chamada seguinte era recusada com a mensagem da primeira**, mesmo estando correta. Uma entidade que falhasse uma vez ao ser excluída nunca mais podia ser excluída. A validação passou a ser feita sem tocar no estado, e a exceção continua sendo `BadRequestException` com as mesmas mensagens. **O que fazer**: código que lia `entidade.Notifications` depois de capturar a exceção precisa ler `ex.GetErrorMessages()`, ou `ex.GetNotifications()` quando precisar do código do erro — a exceção transporta a notificação completa, com o `T.ENT.*` preservado. `ValidateNotDeleted()` segue acumulando de propósito — é o método para checar sem interromper o fluxo. - **`AuditableEntity.SetUpdatedBy` passou a ser `override`, e não `new`.** Declarado com `new`, ele apenas escondia o método virtual de `DetailedEntity`: uma chamada por referência da classe base — a de um repositório genérico, ou a do próprio Entity Framework — executava o método da base e **a versão não era incrementada**, em silêncio. A `VersionedEntity`, que faz a mesma coisa, sempre usou `override` e por isso as duas discordavam. **O que fazer**: nada, mas quem usa `Version` como token de concorrência otimista passa a detectar conflitos que antes escapavam. - **Entidades de tipos diferentes com o mesmo `Id` deixaram de ser iguais.** `Equals` comparava apenas o identificador, então um `Pedido` e um `Produto` com o mesmo `Guid` eram iguais — e um `Distinct()` ou um `HashSet` sobre entidades de tipos diferentes descartava registros. A comparação passou a exigir compatibilidade de tipo, aceitando herança nos dois sentidos para não quebrar com os proxies de carregamento tardio do Entity Framework. **O que fazer**: nada, salvo para quem dependia da comparação apenas por identificador. - **`AuditableEntity.SetDeleted` e `SetRestored` passaram a registrar quem alterou.** Os dois gravavam `DeletedById`/`RestoredById` mas não tocavam em `UpdatedById` nem em `UpdatedAt` — a entidade de auditoria _completa_ era justamente a que perdia o rastro da última alteração, enquanto a `SoftDeletableEntity` o registrava. **O que fazer**: nada; as colunas `updated_by` e `updated_at` passam a mudar também na exclusão e na restauração. - **`FileEntity.FileName`, `Title` e `Link` deixaram de devolver nulo.** Eram declarados `string` com `= null!;`, então uma entidade materializada pelo Entity Framework ou criada pelo construtor vazio devolvia nulo em propriedade não anulável. **O que fazer**: verificações escritas como `== null` deixam de encontrar o caso; use `Length == 0`. - **As chaves de mensagem do pacote mudaram de nome.** `Empty;Id` virou `Field.Empty;Id` e `ChangeBlocked;Id`/`ChangeBlocked;CreatedBy` viraram `Field.ChangeBlocked;Id`/`Field.ChangeBlocked;CreatedBy`, entrando na família `Field.*`. As antigas **não existiam em nenhum arquivo de idioma**, então a chave crua era exibida ao usuário final. **O que fazer**: quem comparava a mensagem pela chave antiga precisa ajustar; em troca, a mensagem passa a chegar traduzida. - **Valor ausente passou a ser reportado como campo obrigatório.** `SetDeleted(null)` e equivalentes devolviam `Notifications.NotificationNull`, uma mensagem interna do framework de notificações. Agora devolvem `Field.Required;DeletedBy`, `Field.Required;UpdatedBy`, `Field.Required;CreatedBy` ou `Field.Required;RestoredBy`, conforme a operação. ## Correções (Tooark.Notifications) - **`NullReferenceException` ao receber coleção nula**: `AddNotifications(ICollection)` e `AddNotifications(params Notification[])` iteravam o argumento sem verificação, quebrando com uma exceção sem contexto — enquanto todos os demais métodos tratavam nulo defensivamente. Agora seguem a mesma regra: argumento nulo registra `Notifications.NotificationNull`. - **Item nulo dentro da coleção contaminava a lista**: um `null` na coleção era adicionado sem verificação e só estourava depois, na leitura de `Codes`/`Keys`/`Messages` ou na montagem do `ResponseDto`, longe da origem. Como `AddNotifications(Notification)` delega para essa sobrecarga, a contaminação se propagava entre notificações. Itens nulos passam a ser ignorados. - **Mensagem composta apenas por espaços virava mensagem vazia**: a guarda usava `IsNullOrEmpty`, então `" "` passava e o `Trim()` do `NotificationItem` produzia `Message` vazia — o consumidor recebia um erro sem texto. Agora `IsNullOrWhiteSpace` cobre o caso. - **Conversão implícita `NotificationItem` → `string` lançava `NullReferenceException`** com instância nula (por exemplo `string texto = lista.FirstOrDefault();`). Passa a retornar string vazia. - **Documentação XML incorreta**: as duas sobrecargas `AddNotification(Type, ...)` descreviam "adiciona uma coleção de notificações" quando adicionam uma única notificação, e `AddNotification(string, string)` documentava os parâmetros em ordem invertida à assinatura. - **Exemplo do README invertia chave e mensagem**: o código de exemplo produzia `Message = "Error"` e `Key = "Ocorreuumerro."`, mas o comentário afirmava o contrário. Quem seguisse o exemplo trocaria os dois campos. ## Modificado (Tooark.Observability) - **`Microsoft.AspNetCore.App` passou a ser declarado por `FrameworkReference`.** O pacote usa `HttpContext` e `HttpRequest` no filtro e no enriquecimento do rastreamento, e esses tipos chegavam por transitividade do `OpenTelemetry.Instrumentation.AspNetCore` — o build dependia do grafo de dependências de terceiro, e uma reorganização lá quebraria a compilação aqui sem alteração nossa. **Não há requisito novo**: verificado que o `runtimeconfig.json` de um consumidor já listava o `Microsoft.AspNetCore.App` antes da mudança, e segue idêntico depois. ## Modificado (Tooark.Notifications) - **Instanciação por reflexão removida**: os itens de notificação eram criados com `Activator.CreateInstance` para um tipo do próprio assembly — boxing de `object[]` a cada notificação, sem verificação em tempo de compilação e com falha só em runtime caso o construtor mudasse. Substituído por `new NotificationItem(...)`. - **Auto-adição deixou de duplicar notificações**: `notificacao.AddNotifications(notificacao)` copiava a própria lista sobre si mesma. Agora é uma operação sem efeito, protegida por `ReferenceEquals` — o que também elimina o risco de alterar a coleção durante a iteração. - **Sanitização da chave completa**: `Key` removia apenas o espaço literal, mantendo tabulação e quebra de linha. Agora remove todos os espaços em branco, e o comportamento está documentado. - **`Code` passou a ser normalizado**: era usado exatamente como recebido, então `" T.ERR "` virava um código com espaços nas pontas, que não casava com nenhum filtro por código. Agora sofre `Trim()`, como já acontecia com a mensagem. Os valores padrão `Unknown` e `T.ERR` viraram constantes, eliminando as strings repetidas entre a assinatura do construtor e a validação. - **Alocações reduzidas na leitura**: `Notifications` reutiliza a mesma coleção somente leitura em vez de criar um wrapper a cada acesso; `Codes`, `Keys` e `Messages` passaram a alocar uma única lista; e a agregação de notificações deixou de fazer a cópia intermediária. - Limpeza do `.csproj`: removido o `ItemGroup` de `Resources\**\*.json`, herdado de outro projeto e sem correspondência neste pacote. - README reescrito para refletir o pacote: normalização de `message`/`key`/`code`, separação entre métodos públicos e protegidos, tratamento de valores nulos, documentação da classe `NotificationErrorMessages` (API pública que não aparecia) e correção do exemplo de uso. ## Adicionado (Tooark.Mediator.EntityFrameworkCore) - **Novo pacote**: move a persistência do Entity Framework Core para o pipeline do Mediator, mantendo os manipuladores livres de `SaveChanges` — a linha repetida em cada manipulador de comando, cujo esquecimento produz um comando que aparenta funcionar sem gravar nada. - `AddTooarkMediatorUnitOfWork()` registra o behavior na posição em que é chamado, respeitando a ordem de execução do pipeline. Duas estratégias: `SaveChanges` (padrão, com a transação implícita do Entity Framework Core) e `Transaction` (transação explícita, para manipuladores que persistem mais de uma vez ou combinam o contexto com outro recurso transacional). - A restrição a `ICommand` mantém as consultas fora da persistência, sem verificação de tipo em tempo de execução. - **Comandos aninhados participam da mesma unidade de trabalho**: apenas o comando mais externo persiste. Sem esse controle, o comando interno gravaria no meio da operação do externo e uma falha posterior deixaria o banco em estado parcial. - Na estratégia `Transaction`, a transação é aberta dentro da estratégia de resiliência do provedor — abrir por fora lança quando há retentativa configurada (`EnableRetryOnFailure`). Em contrapartida, a operação pode ser reexecutada em falha transitória e precisa ser idempotente. - `IUnitOfWork`, no namespace `Tooark.Mediator.EntityFrameworkCore.Interfaces`, não expõe tipos do Entity Framework, permitindo substituir a implementação em testes. - O pacote integra o agregador `Tooark`, coerente com a proposta de instalar todos os recursos de uma vez. ## Adicionado (Tooark.Mediator) - **Pipeline de behaviors**: `IPipelineBehavior` envolve a execução do manipulador, permitindo tratar preocupações transversais — validação, log, transação, cache, autorização — sem repeti-las em cada manipulador. Cada behavior decide se invoca a etapa seguinte; não invocar interrompe o pipeline e devolve a resposta do próprio behavior ao chamador. - Registro explícito por `AddTooarkMediatorBehavior()` ou `AddTooarkMediatorBehavior(Type)`, aceitando behaviors fechados e genéricos abertos. Behaviors **não** são descobertos pelo scan de assemblies: a ordem de execução é semântica e a ordem devolvida pelo scan não é garantida. - Execução na ordem de registro — o primeiro registrado é o mais externo. - O `CancellationToken` é parâmetro obrigatório da etapa seguinte, e não opcional como no delegate equivalente de outras bibliotecas: descartar o token por engano é justamente o tipo de falha silenciosa que esta versão elimina. - Tipo registrado que não implementa a interface, ou behavior genérico aberto que o container não consegue fechar, falham no registro com `Behavior.NotSupported`; behavior que devolve tarefa nula falha com `Behavior.ExecutionFailed`, identificando o tipo. - Aplica-se apenas a requisições; notificações não passam pelo pipeline. O manipulador é resolvido antes da montagem da cadeia, então `Handler.NotFound` continua falhando antes de qualquer behavior executar. Sem behaviors registrados, o despacho permanece sendo a invocação direta do manipulador, sem montar a cadeia. - `IPipelineBehavior` e `RequestHandlerDelegate` ficam no namespace `Tooark.Mediator.Behaviors`, separado dos contratos de manipulador em `Tooark.Mediator.Handlers` — um behavior não é um manipulador, e agrupá-los no mesmo namespace confundiria os dois papéis logo na estreia do recurso. ## Correções (Tooark.Enums) - **`FormatException` na validação de CNH e RG com entrada não numérica**: `EDocumentType.CNH.IsValid("ABCDEFGHIJK")` e `EDocumentType.RG.IsValid("AB.CDE.FGH")` convertiam caractere a caractere com `long.Parse` e `int.Parse` sem verificação. É o mesmo defeito corrigido no `ValidateCnpj` — corrigido no CNPJ e deixado para trás nos outros dois documentos da mesma classe. - **`NullReferenceException` na validação com valor nulo**: `IsValid` de CNH e RG desreferenciava o argumento em `value.Length` e `value.Trim()`. - **Documentação de classe do `EFileType` descrevia outro tipo**: "Representa as ações que podem ser realizadas em uma permissão", copiada de um enumerador de permissões. Os parâmetros das conversões implícitas também herdaram o nome `action`; em `ECloudProvider`, `document`. - **README com exemplo factualmente errado**: afirmava que `EFileType.Image.ToInt()` devolve `1`. Devolve `2` — `Document` é que é 1. - Limpeza do `.csproj`: removido o `ItemGroup` de `Resources\**\*.json`, sem correspondência neste pacote. ## Modificado (Tooark.Enums) - **Cálculo de dígitos de CNH e RG centralizado** em `DocumentDigit`, no `Tooark.Validations`, junto com os de CPF e CNPJ. As implementações estavam duplicadas dentro do `EDocumentType`, que encolheu de 291 para 206 linhas. - **Contrato de `ToRegex()` e `IsValid` documentado**: são camadas distintas e complementares — a primeira valida a forma, a segunda o conteúdo. Chamada isolada, `IsValid` aceita o valor com ou sem máscara e nunca lança para entrada inválida. - **Arquivos organizados em regiões**, seguindo o padrão que o `EProcessorType` e o `EProtocolOtlp` do `Tooark.Observability` já usavam para esse mesmo formato de classe: valores, construtor, propriedades privadas, métodos privados e a região de métodos, sobrescritas e operadores implícitos. No `EFileType`, o construtor foi movido para antes das propriedades privadas, que era a única das três classes fora dessa ordem. - Metadados NuGet: `PackageTags` ampliadas para melhorar a descoberta no nuget.org. - README revisado: seções de instalação e de funcionamento, as conversões implícitas documentadas (são a API mais usada do pacote e não apareciam), tabelas de valores com id e descrição, o comportamento de valor não reconhecido explicitado, e os links relativos de licença e de dependência trocados por absolutos. ## Correções (Tooark.Validations) - **`NullReferenceException` com valor nulo**: as validações de tamanho e conteúdo de string, as de tamanho de lista e as comparações de objeto desreferenciavam o valor sem verificação. Uma biblioteca de validação interrompia o fluxo justamente ao receber o valor inválido que deveria reprovar. Valor nulo passou a ser tratado como vazio e reprovado normalmente, e a comparação de objetos usa a igualdade estática, que aceita nulos dos dois lados. - **`AreEquals` de listas comparava referência**: `AreEquals(new[] { 1, 2 }, new[] { 1, 2 }, "x")` reprovava duas listas idênticas. O teste existente passava a mesma instância nos dois argumentos, então a comparação por referência acertava por acidente e o defeito nunca aparecia. Passou a usar `SequenceEqual`. - **`Join` estourava com elemento nulo**: verificava se a coleção era nula, mas não os elementos, e acessava `IsValid` de cada um. Elementos nulos passaram a ser ignorados, como já ocorria em `AddNotifications`. - **`RegexMatchTimeoutException` escapava da validação**: o tempo limite de 300 ms protegia contra padrões suscetíveis a backtracking excessivo, mas convertia a proteção em exceção não tratada — uma entrada patológica derrubava a operação em vez de ser reprovada. O tempo limite atingido passou a significar "não corresponde ao padrão". - **`ValidateCnpj` do `EDocumentType` estouraria com o CNPJ alfanumérico**: convertia caractere a caractere com `int.Parse`, então a primeira letra lançaria `FormatException` no lugar de reprovar o documento. ## Modificado (Tooark.Validations) - **Cálculo de dígitos verificadores centralizado** em `DocumentDigit`, no `Tooark.Validations`: existiam duas implementações, uma no `Tooark.Enums` (usada pelos value objects, que já conferia os dígitos) e nenhuma no `Tooark.Validations`. Agora há uma só, pública, também utilizável fora da validação. - **Expressões regulares em cache próprio**: o cache do `Regex` estático guarda 15 combinações e o pacote oferece 48 padrões — em uso real as entradas se expulsavam entre si e o padrão era recompilado a cada validação. O cache por padrão, opções e tempo limite elimina a recompilação e vale também para padrões da aplicação. - `MatchFunc` deixou de ser campo de instância e virou método estático: era um delegate carregado por cada instância de `Validation`, que é a base de toda entidade e value object do ecossistema. - README revisado: a família `LinkVideoValidation` — `IsLinkVideo`, `IsLinkVideoYouTube`, `IsLinkVideoVimeo` e `IsLinkVideoDailymotion` — **não aparecia em lugar nenhum** do README, sem seção, exemplo ou menção na lista de métodos; `IsNotNull` também faltava na lista. Ganharam seção própria, exemplo e entrada. A tabela de códigos de erro passou a listar os códigos reais com sufixo, incluir o `T.VLD.DOC1` e explicitar que as validações por expressão regular compartilham o `T.VLD.RGX1`. Foram acrescentadas a seção de instalação, a documentação da classe pública `ValidationErrorMessages` e as seções finais no índice; os links relativos de licença e de dependência viraram absolutos. - Documentação XML: a classe parcial de `LinkVideoValidation` era descrita como "Classe de validação de Protocol", copiada do `ProtocolValidation`, e `ValidationErrorMessages` era o único tipo público do pacote sem ``. - **`DocumentDigit` ganhou `IsRg` e `IsCnh`**, passando a concentrar o cálculo dos quatro documentos suportados. As implementações viviam no `Tooark.Enums`, o que fazia o mesmo documento ter dois veredictos possíveis dependendo da API usada. - Arquivo `NetworklValidation.cs` renomeado para `NetworkValidation.cs`. ## Correções (Tooark.Mediator e Tooark.Mediator.Abstractions) - **`Unit.Task` alocava uma tarefa a cada acesso**: a propriedade executava `Task.FromResult` em toda leitura, contrariando a própria documentação do membro, que prometia uma tarefa pré-construída. Como `Unit.Task` é o retorno recomendado de todo manipulador de comando sem resposta, a alocação acontecia por requisição. A tarefa passou a ser criada uma única vez. - **Publicação paralela abandonava manipuladores já iniciados**: quando um manipulador falhava antes de devolver a tarefa (validação síncrona ou tarefa nula), a exceção interrompia a iteração — as tarefas já em execução ficavam sem observação, com a falha delas desaparecendo, e os manipuladores seguintes nem chegavam a iniciar, contrariando a semântica documentada da estratégia paralela. Agora todos são iniciados e aguardados, e a primeira falha continua sendo propagada ao chamador. - Documentação XML: as variantes sem resposta de `ICommand` e `IRequest` usavam a tag `` sem o atributo `name`, inválida em interface não genérica — o conteúdo se perdia na documentação gerada. - **Âncoras do índice do README do `Tooark.Mediator.Abstractions` não resolviam**: os títulos das seções têm emoji, então `#instalação`, `#componentes` e as demais apontavam para lugar nenhum. Corrigidas. - Documentação do README do `Tooark.Mediator.Abstractions`: `PublishAsync` era documentado com o parâmetro `notification`, divergente da assinatura (`notify`); `Unit.Value` e `Unit.Task` apareciam sem descrição; a versão da dependência `Tooark.Exceptions` estava em branco; e o exemplo de uso do `Unit` foi reescrito para mostrar o curto-circuito síncrono, que é onde a tarefa reutilizada importa. - Arquivo `Enums/ENotificationStrategy.cs` renomeado para `ENotifyStrategy.cs`, alinhando ao tipo que declara. ## Modificado (Tooark.Mediator e Tooark.Mediator.Abstractions) - **Metadados NuGet revisados nos dois pacotes**: `Summary` e `PackageDescription` estavam em português enquanto o `Description` publicado estava em inglês; ficaram todos em inglês. As `PackageTags` foram ampliadas (`tooark`, `mediatr`, `command`, `query`, `notification` e, no `Tooark.Mediator`, `pipeline` e `behaviors`), melhorando a descoberta no nuget.org. ## Correções (Tooark.Observability) - **Conversões implícitas de `EProcessorType` e `EProtocolOtlp` estouravam `NullReferenceException`** com instância nula. O caminho é alcançável no uso normal, já que `OtlpOverrideOptions.Protocol` e `OtlpOverrideOptions.ExportProcessorType` são anuláveis por design. Passam a lançar `Invalid.Parameter;null`, o mesmo erro que essas classes já emitiam para valores inválidos. - Documentação de `DataSensitiveOptions.HideQueryParameters` atualizada: ainda descrevia a remoção do atributo `http.target`, comportamento substituído na v3.3.4 pela remoção de `url.query` e da query em `url.full`. - **Opções de métricas não apareciam no README**: `ExportIntervalMilliseconds` e `ProcessMetricsEnabled` eram configuráveis, mas não estavam documentadas — quem lia o README não sabia que existiam. Incluídas, com o padrão de 60 s e a redução para 5 s quando o OTLP efetivo tem `ServerlessOptimized` habilitado. - README do `Tooark.Observability` revisado: `AllowSensitiveData` na tabela de opções e nos exemplos de `appsettings.json`, descrição de `HideQueryParameters` corrigida também no texto e na tabela, e nota explícita de que `AllowSensitiveData = true` desliga a sanitização globalmente e faz as opções granulares de `Tracing.DataSensitive` serem ignoradas — precisamente a ambiguidade que motivou o rename. - **`AddTooarkObservability` era descrito como se fizesse mais que `AddTooarkOpenTelemetry`**: a tabela dizia "Configura Observability + OpenTelemetry", sugerindo registros adicionais. O método apenas delega — são equivalentes, e a documentação passou a dizer isso. Na mesma tabela de `ObservabilityOptions` faltavam os sub-objetos (`Tracing`, `Metrics`, `Logging`, `Otlp`) e os três callbacks `ConfigureTracing`/`ConfigureMetrics`/`ConfigureLogging`, que só existiam nos exemplos. ## Correções (Tooark.Exceptions) - **`NullReferenceException` com lista ou notificação nula**: os construtores desreferenciavam o argumento no inicializador da classe base, antes de qualquer verificação. Uma biblioteca de exceções estourava com NRE sem contexto justamente ao ser construída de forma incorreta. Entrada nula passou a registrar `Exceptions.ErrorsNullOrEmpty`. - **Lista ou notificação vazia produzia exceção sem erro nenhum**: `GetErrorMessages()` vinha vazio e o `Message` caía no texto genérico do .NET (_"Exception of type '...' was thrown."_). Quem devolvia `ex.GetErrorMessages()` na resposta HTTP entregava um erro com corpo vazio. As coleções passaram a ter sempre ao menos um item. - **Item nulo era tratado de dois jeitos na mesma exceção**: `GetErrorMessages()` guardava o `null` — que estourava no consumidor — enquanto `GetNotifications()` já convertia para a chave de mensagem ausente. As duas leituras passaram a coincidir posição a posição. - **`FormatException` crua no construtor de formatação**: um formato incompatível com os parâmetros substituía a exceção pedida por uma `FormatException`, trocando o erro real por um erro de apresentação — mensagens com chaves literais, como JSON, caíam nesse caso. A mensagem passou a ser mantida como recebida. - **Mensagem formatada duas vezes**: `string.Format` era executado no inicializador da base e novamente no corpo do construtor, para produzir a mesma string. Agora ocorre uma única vez. - Documentação do `GetInfoException` dizia "Função virtual" para um método `override`, e destoava do padrão das demais exceções. ## Modificado (Tooark.Exceptions) - **Construtores da classe base passaram de `public` para `protected`**: `TooarkException` é abstrata, então construtores públicos eram inalcançáveis e sugeriam uma instanciação que nunca existiu. - **Suporte a exceção interna**: todas as exceções ganharam `(string message, Exception innerException)`, permitindo preservar a causa raiz ao traduzir um erro de infraestrutura. Atenção à resolução de sobrecarga: `new NotFoundException("Falha: {0}", umaExcecao)` passa a ligar ao construtor de exceção interna, e não ao de formatação — para formatar usando uma exceção como argumento, converta antes (por exemplo `ex.Message`). - **Mensagens conhecidas centralizadas** em `ExceptionErrorMessages`, no mesmo formato de chave de tradução já usado pelo `Tooark.Notifications`: `Exceptions.MessageNullEmpty` e `Exceptions.ErrorsNullOrEmpty`. - **Construção centralizada**: os cinco construtores públicos passaram a delegar para um único construtor privado, que é o único ponto que define o estado da exceção. - Metadados NuGet: `PackageTags` ampliadas para melhorar a descoberta no nuget.org. - README revisado: acrescentadas a seção de instalação e um exemplo de **extensão da hierarquia** — a classe base é abstrata com construtores protegidos, e criar uma exceção própria herdando dela é o ponto de extensão do pacote, que não estava documentado. O exemplo de uso com `Notification` **não compilava mais**, porque chamava `AddNotification` de fora do objeto e o método virou protegido nesta major; as garantias de coleção somente leitura e nunca vazia, o construtor de exceção interna, a tabela de tratamento de nulos e a classe `ExceptionErrorMessages` passaram a ser documentados; a linha do 413 passou a nomear `RequestEntityTooLarge`, que é o membro real de `HttpStatusCode`, com nota sobre o nome do RFC; e os links relativos de licença e de dependência foram trocados por absolutos, que resolvem no nuget.org. ## Correções (Tooark.Securities — Criptografia) - **`Encrypt` com `CBCUnsafe` quebrava o round-trip**: o modo legado (IV zero) existe apenas para descriptografar dados antigos, mas o `Encrypt` caía silenciosamente em GCM — `Decrypt(Encrypt(x))` falhava sempre. Agora criptografar nesse modo lança `Options.Cryptography.AlgorithmDecryptOnly;CBCUnsafe`. - **`SecretBase64` sozinho era impossível**: o construtor exigia `Secret` mesmo quando a chave Base64 (documentada como usada diretamente) estava configurada. A validação passou a ser condicional: `Secret` **ou** `SecretBase64` obrigatório; quando ambos são informados, `SecretBase64` tem prioridade (documentado). - **Chave Base64 inválida trocava de chave silenciosamente**: `SecretBase64` fora de 32 bytes era ignorado e a chave era derivada do `Secret` — dados criptografados com chave diferente da configurada. Agora Base64 inválido (`Options.Cryptography.SecretBase64Invalid`) ou tamanho errado (`Options.Cryptography.SecretBase64InvalidSize`) falham no startup. - **Exceções cruas no `Decrypt`**: Base64 inválido vazava `FormatException` e padding/tag incorretos vazavam `CryptographicException`. Uniformizado em `BadRequestException("Cryptography.InvalidCipherText")`, o que também elimina a distinção de erros que alimenta padding oracle no modo CBC. ## Correções (Tooark.Securities — JWT) - **`NullReferenceException` no startup sem `Algorithm` configurado**: o campo interno era nulo; agora tem default real (`ES256`, documentado). - **Algoritmo inválido caía silenciosamente em ES256**: um typo trocava a classe do algoritmo (simétrico → assimétrico) sem aviso. Valores desconhecidos agora lançam `Options.Jwt.AlgorithmNotSupported;{valor}`; doc atualizada com a lista completa (incluindo ES256/384/512, que faltavam). - **`NullLoggerFactory` global podia desligar o logging da aplicação**: o registro `TryAddSingleton` no DI do JWT, se executado antes de `AddLogging` (console/worker), impedia o registro do logging real. Removido: o logger virou parâmetro opcional do construtor com `NullLogger` interno. - **Audience/issuer inválidos reportavam `InternalServerError`**: faltava tratamento para `SecurityTokenException` — agora mapeiam para `Token.Invalid`, como todo token inválido. - **Token válido sem as claims esperadas estourava como erro interno**: `UserTokenDto` usava `First()`; com `FirstOrDefault`, claims ausentes resultam em campos vazios, permitindo validar tokens emitidos por outros fluxos. ## Modificado (Tooark.Securities) - **`PublicKey` sem fallback para a chave privada**: atribuir `PublicKey = null` copiava a chave privada para o campo da pública (semanticamente inválido — PKCS#8 não é SPKI — e dependente da ordem de binding). Null agora significa "não configurado". - **Fail-fast unificado para algoritmos assimétricos**: o check `Options.Jwt.KeysNotConfigured` valia só para RSA/PSS; com ECDsa, a configuração sem chaves passava pelo startup e falhava no primeiro uso. O check agora roda antes dos branches, para todas as famílias. - **Tamanho mínimo de secret HMAC (RFC 7518)**: HS256 exige ≥32 bytes, HS384 ≥48, HS512 ≥64, validado no startup com `Options.Jwt.SecretTooShort;{mínimo}` — antes, secrets curtos passavam pela construção e falhavam na primeira assinatura com erro obscuro do IdentityModel. **Atenção**: configurações com secrets entre 16 e 31 bytes que funcionavam passam a falhar no startup — essas configurações são vulneráveis a força-bruta da chave HMAC (tokens forjáveis) e devem ser corrigidas. O README já documentava esses mínimos; o código passou a cumpri-los. - **Dispatch de tokens modernizado para `JsonWebTokenHandler`** (handler atual da Microsoft.IdentityModel): `Create` e `Validate` usam instância estática reutilizável (sem alocação de handler por chamada), com mapeamento de falhas preservando os códigos existentes (`Token.Expired`, `Token.InvalidSignature`, `Token.Invalid`, `InternalServerError`). `UserTokenDto` ganhou a sobrecarga `UserTokenDto(JsonWebToken)`; o construtor legado `UserTokenDto(JwtSecurityToken)` permanece — API pública 100% compatível e interoperabilidade entre handlers coberta por teste. - **README do `Tooark.Securities` complementado na revisão final**: `Issuers` e `Audiences` — os arrays que permitem aceitar múltiplos emissores e destinatários na validação — não apareciam em lugar nenhum, embora sejam lidos pelo serviço; `JwtOptions` e `CryptographyOptions` ganharam tabela de propriedades com os valores padrão reais (o `ExpirationTime` é 5 minutos, e os exemplos mostravam 60 sem dizer que era customização); ficou documentado que `PrivateKey` e `PublicKey` aceitam o PEM completo, já que o setter remove os delimitadores e as quebras de linha; e a versão da dependência `Tooark.Exceptions`, que estava em branco, foi preenchida. - Decisões deliberadas (documentadas, sem mudança): derivação de chave via SHA256 mantida por compatibilidade de dados (recomendação: `SecretBase64` com chave aleatória); CBC sem HMAC mantido por compat (GCM é o default); migração completa do tipo público legado `JwtSecurityToken` fica para uma futura major. - README do Tooark.Securities revisado para coerência com o pacote: chave de configuração corrigida de `Expires` para `ExpirationTime` (a chave documentada não fazia bind e a expiração caía no default silenciosamente), `SecretBase64` documentado, `CBCUnsafe` marcado como somente-descriptografia, mínimos HMAC em bytes com nota da RFC, nota do algoritmo padrão/validação, exemplos de criptografia usando `ICryptographyService` + `AddTooarkCryptography`, `ICryptographyService` na tabela de interfaces, tabela de códigos de erro completa com as novas mensagens e dependências multi-target. ## 🔒 Segurança (Tooark.Utils) O `FileConvert` recebe conteúdo enviado pelo cliente e devolvia partes dele sem verificação. Três pontos foram endurecidos, todos confirmados por execução contra o pacote compilado. - **Travessia de diretório pela extensão**: `Extension` devolvia o texto entre a barra e o ponto e vírgula da data URL, tal como recebido. `data:image/..\..\..\inetpub\wwwroot\shell.aspx;base64,...` devolvia `..\..\..\INETPUB\WWWROOT\SHELL.ASPX`, e um `Path.Combine` com esse valor escapava do diretório do inquilino. Também passavam `B/C` (barra), `