Visão geral
Resumo
Os 26 subitens do P12 estão em código no Gryd.IO. O Nexio ainda não compõe o GrydFiles: nenhum projeto em src/ o referencia, então nenhum subitem foi exercitado aqui. A especificação completa continua em grydfiles.html.
| Status | Significa | Subitens | Quantos |
|---|---|---|---|
| Resolvido | Entregue no framework. O Nexio só precisa compor o módulo e usar a porta. | P12d, P12e, P12f, P12g, P12h, P12i, P12j, P12k, P12m, P12n, P12u, P12v, P12x, P12z | 14 |
| Parcial | Entregue, com prova ou cobertura incompleta. | P12b, P12o, P12p | 3 |
| Pendente | Entregue no framework, mas o Nexio precisa fazer algo específico. | P12a, P12c, P12l, P12q, P12r, P12s, P12t, P12w, P12y | 9 |
| Sem solução | Ainda não há solução disponível. | nenhum | 0 |
Planejamento
O que falta no Nexio
| Ação | Subitem | Bloqueia |
|---|---|---|
Compor o módulo: AddGrydFiles, perfis, escopo do consumidor e migrations de GrydFilesDbContext no Migrator | — | Primeira Requisição com anexo |
AddGrydFilesOutbox no host e AddOutboxWorker no Worker | P12a | Primeira Requisição com anexo |
IFileOutcomeHandler do Anexo | P12r | Primeira Requisição com anexo |
IAuditStore durável nos hosts que compõem o módulo | P12s | Primeiro download |
clamd na AWS e AddGrydFilesClamAv | P12c | Primeira Requisição com anexo |
Bucket do GrydFiles com regra de ciclo de vida de _transit/ (expira em um dia) | P12o | Primeira Requisição com anexo |
GrydAuth:Roles:TenantAssignable:Deny com as permissões do módulo | P12l | Primeiro tenant pago |
Adaptador de IFileActorDirectory | P12q | Primeira eliminação |
Rota POST /attachments/{id}/rescan | P12y | Primeira Requisição com anexo |
| Asserções de arquitetura: consumidores da porta e quem resolve cada escopo | P12t, P12w | Primeiro consumidor novo da porta |
Adesão à rota de plataforma, só se houver arquivo de produto: AddGrydFilesPlatformOutbox, AddGrydFilesPlatformAudit, AddGrydFilesPlatformOutcomes | P12u | Primeiro arquivo sem tenant |
Fora dos subitens
Limitações do módulo
| Ponto | Status | Situação | O que fazer |
|---|---|---|---|
| Histórico de tentativas | Parcial | A rota pagina no banco (EfFileScanAttemptReader), mas as demais leituras e o EfStoredFileLock ainda carregam o histórico inteiro com Include(ScanAttempts). Cresce a cada re-scan de acervo. | Item de desempenho quando o acervo crescer. |
| Rotas antigas da trilha | Parcial | A trilha agora é preparada por IAuditTrail e IPlatformAuditTrail. files.audit-trail e files.platform-audit-trail ficam só para entregar mensagens de versões anteriores e saem na próxima major. | Nada para o Nexio, que nunca as usou. |
| Retenção da trilha | Parcial | Nenhum host roda a limpeza do GrydAudit (backend.md). | Decidir quando houver volume. |
| Papel composto antes do Deny | Parcial | TenantAssignable:Deny age ao conceder e não limpa papel que já exista. | Ligar antes de o primeiro tenant compor papéis com essas permissões. |
Detalhe
Os 26 subitens
Abra cada item para ver o motivo, o requisito, o critério de aceite e a evidência, copiados da v1.8 das pendências. A linha Hoje é a conferência de 02/11/2026. A evidência marcada e4eb5a4f descreve o código antes da entrega, e onde o texto diz “Na 10.2.0”, leia v11.0.0.
P12aVeredito entregue depois do commitPendente
- Hoje · 11.1.0
- Entregue. O Nexio precisa compor
AddGrydFilesOutboxno host e terAddOutboxWorkerno processo que drena; sem o primeiro, o host não sobe. - Por que existe
- a transição de veredito é transação do GrydFiles e o efeito do dono mora em outro contexto de persistência; o
Failednão chega a ninguém, e o job de varredura lê sem tenant. - Requisito
- os quatro desfechos (
Failedinclusive) entregues ao dono depois do commit, pelo menos uma vez, com tenant restaurado; o handler decide pelo estado atual. - Critério de aceite
- derrubar entre commit e entrega → entregue ao voltar; duplicata → um efeito; sem tenant → nenhuma leitura.
- Evidência
IFileOutcomeHandlernão existe;ExpectedOwnerScope/ExpectedOwnerKey(GrydFiles.Core/Entities/StoredFile.cs:261–264) e a portaIServerFileStorejá existem;GrydFiles.Infrastructure/Jobs/FileScanJob.cs:107desliga o filtro de tenant.- Na 10.2.0
- Veredito gravado na transação (
RecordVerdictStep) e entregue aIFileOutcomeHandlerdepois do commit. Arquivo sem tenant usa o contrato v2, por adesão:AddGrydFilesPlatformOutcomese.ProductFileOutcomes<T>(), com destino persistido do dono. Dono legadoUnknownnão recebe aviso e pede reconciliação do produto. - Bloqueia
- a primeira Requisição com anexo.
- Onde no Nexio
- GrydFiles
P12bObjeto servido = objeto escaneadoParcial
- Hoje · 11.1.0
- Entregue. Os critérios rodam ponta a ponta só em MinIO/S3; Azure e GCS são provados por testes de contrato dos adaptadores. O S3 é o provedor usado na AWS.
- Por que existe
- a URL de upload continua válida depois do veredito e escreve na chave que é escaneada e servida; o tamanho não é assinado.
- Requisito
- nenhum caminho de escrita alcança o objeto servido depois do veredito; tamanho limitado e assinado.
- Critério de aceite
PUTrepetido depois deAvailable→ o download entrega os bytes dosha256gravado.- Evidência
deltas/T8_plataforma.mdP2.- Na 10.2.0
- A URL escreve em
_transit/, e a promoção é cópia condicional à versão lida (PromoteFromTransitStep). A expiração de_transit/em um dia é regra do bucket do host. - Bloqueia
- a primeira Requisição com anexo.
P12cAntivírus real na v1Pendente
- Hoje · 11.1.0
- Entregue no módulo. Falta a infraestrutura: o Terraform do Nexio não tem
clamd, e o host precisa deAddGrydFilesClamAv. - Por que existe
- sem varredura real, arquivo de terceiro — do portal ou anexado de e-mail por usuário interno — é servido sem ser examinado. O gate de hoje é parcial: o host não sobe sem motor registrado, mas qualquer motor serve, inclusive um que não detecta nada.
- Requisito
- scanner real na v1; host sem motor não sobe; o monitor submete EICAR ao motor registrado, e motor que não detecta fica indisponível — nada liberado, alerta ao operador (RN-GF-05).
- Critério de aceite
- (a) host sem motor registrado não sobe; (b) motor registrado que não detecta EICAR → indisponível, nenhum arquivo liberado, alerta; (c) EICAR →
Infectedno CI contra motor real. - Evidência (
e4eb5a4f) - o host não sobe sem
IFileScanner(GrydFiles/FileScannerRequirement.cs, commit1edb00d0), masUseScanner<T>()registra qualquer motor (GrydFiles/GrydFilesBuilder.cs:44–48); o ClamAV entra porAddGrydFilesClamAv; falta infraestrutura de produção e a conferência do motor com EICAR. - Na 10.2.0
- O host não sobe sem motor nem com
IFileScannerregistrado direto: o módulo consomeIVerifiedFileScanner, e o motor fica só sob chave, atrás deVerifiedFileScanner(EICAR). Alerta emScannerHealthMonitorJob. O clamd de produção é infraestrutura do host. - Bloqueia
- a primeira Requisição com anexo.
- Onde no Nexio
- GrydFiles
P12dRe-scan de acervoResolvido
- Hoje · 11.1.0
- Entregue. Sem ação além de compor o módulo.
- Por que existe
- não há re-scan; a forma anterior tirava o arquivo do download durante a rotina.
- Requisito
- re-scan que não tira
Availabledo download; positivo propaga a todas as referências. - Critério de aceite
- duas referências + EICAR →
Infected, duas referências liberadas, dois vereditos. - Evidência
deltas/T8_plataforma.mdP4.- Na 10.2.0
POST /files/{id}/rescan,ArchiveRescanJobeReleasedFileRescan.- Bloqueia
- a primeira Requisição com anexo.
P12eArquivo parado na fila volta sozinhoResolvido
- Hoje · 11.1.0
- Entregue. Sem ação além de compor o módulo.
- Por que existe
- processo que morre entre o commit da confirmação e o enfileiramento deixa
Scanningsem job; a varredura de consistência é semanal. - Requisito
Scanningsem varredura em andamento volta à fila depois de um limiar, de forma idempotente.- Critério de aceite
- matar o processo entre commit e enfileiramento → veredito sem ação humana.
- Evidência
deltas/T8_plataforma.mdP5.- Na 10.2.0
StalledScanRequeue, com a queda do processo simulada em teste.- Bloqueia
- a primeira Requisição com anexo.
P12fDedupe fora da v1Resolvido
- Hoje · 11.1.0
- Entregue. Sem ação.
- Por que existe
- o dedupe custa campo de posse, índices únicos e a corrida dedupe × expurgo, sem consumidor que precise dele.
- Requisito
- um objeto por registro; criação de referência e expurgo mutuamente exclusivos sobre o mesmo registro.
- Critério de aceite
- nunca referência ativa sobre objeto apagado.
- Evidência
deltas/T8_plataforma.mdP6.- Na 10.2.0
- Dedupe retirado do módulo; a trava do registro cobre a corrida (
EfStoredFileLock). - Bloqueia
- a primeira Requisição com anexo (retirada) · o primeiro expurgo (exclusão mútua).
P12gRetenção é piso, com carência; nome original sai no expurgoResolvido
- Hoje · 11.1.0
- Entregue. Sem ação além de compor o módulo.
- Por que existe
- o expurgo roda sem carência depois da última liberação, e o nome original (dado pessoal) fica na lápide para sempre.
- Requisito
- expurgo só sem referência ativa, com carência vencida,
retainUntilvencido e sem guarda legal; toda lápide sem pedido de pessoa sai sem nome original — os caminhos de job (job:retencao,job:pending-expirado,job:quarentena-vencida,job:tipo-reprovadoejob:objeto-alterado, este a criar com o P12o) e a rejeição de tamanho (rejeicao:tamanho-divergente). - Critério de aceite
- data vencida + referência ativa → não expurga; depois de cada um dos seis caminhos,
originalFileNamenulo. - Evidência
deltas/T8_plataforma.mdP7, P8 e P18 ·GrydFiles.Core/FilePurgeReasons.cs:22–41(cinco motivos, todos mantêm o nome) ·ExpiredUploadSweepJob.cs:171,FilePurger.cs:63(redactFileNamenascefalse).- Na 10.2.0
PurgeEligibilityé a regra única do expurgo automático; carência e retirada do nome emFilePurgeReasons.- Bloqueia
- o primeiro expurgo em produção.
- Onde no Nexio
- GrydFiles
P12hlegalHold por caso, com motivoResolvido
- Hoje · 11.1.0
- Entregue. A tela de guarda é do Nexio e depende do P4.
- Por que existe
- a guarda legal liga e desliga sem motivo; por tipo, congelaria documento fiscal para sempre.
- Requisito
- ligar e desligar exigem motivo e são ato de controle (P4). A guarda por detentor, quem a retira e a repetição sem usuário estão no P12u.
- Critério de aceite
- sem motivo → recusado; a trilha tem autor, motivo e data.
- Evidência
deltas/T8_plataforma.mdP9.- Na 10.2.0
PUT /files/{id}/retentionexige motivo com a guarda (UpdateRetentionHandler).- Bloqueia
- a primeira tela de guarda legal em produção.
P12iReferências ativas de um arquivo por escopo, em processoResolvido
- Hoje · 11.1.0
- Entregue. Por desenho, não há lista aberta dos donos de outro consumidor.
- Por que existe
- a eliminação revoga os anexos e o expurgo falha depois, se há referência de outro dono — fica meio ato.
- Requisito
- consulta em processo das referências ativas de um arquivo, com o escopo de cada uma.
- Critério de aceite
- arquivo com referência de outro dono → eliminação recusada antes de gravar, nenhum anexo revogado. A consulta de pré-condição do P12q responde o mesmo, junto com as demais conferências da eliminação.
- Evidência
deltas/T9_plataforma.mdP4.- Na 10.2.0
IFileStore<TScope>.GetReferencesAsyncdevolve as referências ativas do próprio escopo, eCheckPurgeAsyncresponde se a eliminação passaria. O critério de aceite é atendido: a recusaFILE_STILL_REFERENCED_CONFLICTnomeia os escopos que seguram o arquivo. Por desenho: não há lista aberta, para um consumidor não ver os donos de outro. Sessão de tenant não consulta arquivo de produto.- Bloqueia
- a primeira eliminação em produção.
P12jReferência esperada declarada no processo; a confirmação HTTP não aceita donoResolvido
- Hoje · 11.1.0
- Entregue. Sem ação além de usar a porta.
- Por que existe
- só o armazenamento de servidor declara referência esperada; a confirmação pela
IFileServicenão recebe dono, e aceitar dono na rota de confirmação deixaria o cliente HTTP escolher o dono do arquivo que subiu. - Requisito
- o módulo consumidor declara a referência esperada ao confirmar, em processo; a rota
POST /files/{id}/completenunca aceita dono. A confirmação é idempotente: repetida com a mesma referência esperada devolve sucesso sem efeito novo; com referência esperada diferente → 409FILE_REFERENCE_NOT_ALLOWED_CONFLICT(pedido P5 da T9 — o Anexo repete a chamada quando a resposta se perde). - Critério de aceite
- confirmação em processo com dono → a liberação cria a referência; corpo HTTP com
ownerScope/ownerKey→ ignorado ou recusado, e nenhuma referência nasce dele; duas confirmações iguais → um registro e uma referência esperada; a mesma confirmação com outro dono → 409. - Evidência
GrydFiles.Application/InProcess/ServerFileStore.cs:140(ExpectReference) ·CompleteUploadRequestvazio (GrydFiles.Application/Contracts/FileRequests.cs:50) ·IFileService.CompleteUploadAsyncsem dono (IFileService.cs:45) · Anexo RN-ATT-36.- Na 10.2.0
CompleteUploadAsync(fileId, ownerKey)na porta; a rota confirma com corpo vazio e não declara dono.- Bloqueia
- a primeira Requisição com anexo.
- Onde no Nexio
- Anexo · GrydFiles
P12kReferência esperada só sobre Pending reservado pelo mesmo chamadorResolvido
- Hoje · 11.1.0
- Entregue. Sem ação além de compor o módulo.
- Por que existe
- o identificador do arquivo vem do cliente e nada o amarra a quem reservou: um usuário anexa o arquivo de outro dono e contorna o
CanReaddele; segundo dono estoura sem código de erro. - Requisito
- referência esperada só sobre arquivo em
Pendingreservado pelo mesmo chamador e no mesmo tenant; referência direta sobreAvailablesó pelo mesmo chamador que reservou ou por propagação a partir de referência ativa do mesmoownerScopeno mesmo arquivo, nomeada pelo chamador. Fora disso 409FILE_REFERENCE_NOT_ALLOWED_CONFLICT, nunca erro sem código; arquivo de outro tenant continuaFILE_NOT_FOUND; repetição pelo mesmo chamador e dono é idempotente. - Critério de aceite
- (a) A reserva, B declara a referência → 409 com código, nada gravado; declarar sobre arquivo já com veredito → 409; (b) B pede referência direta sobre
Availableque A subiu, sem referência de origem → 409, nenhuma referência; (c) segundo dono → 409 com código, nunca 500; (d) A repete a confirmação com o mesmo dono → o mesmo estado. - Evidência
GrydFiles.Core/Entities/StoredFile.cs:919aceitaPending,ScanningeFailedsem conferir quem reservou, e:937lançaInvalidOperationExceptionpara segundo dono ·GrydFiles.Application/Services/FileService.cs:527cria referência direta conferindo sóAvailable· o mesmo ataque já está em código num consumidor:GrydNotifications.Application/Features/Attachments/NotificationAttachments.cs:189chamaAddReferenceAsynccom ofileIdrecebido, sem conferir quem o subiu.- Na 10.2.0
StoredFile.MayBeReferencedByeAddReferenceHandler.- Bloqueia
- a primeira Requisição com anexo.
- Onde no Nexio
- GrydFiles
P12lRota HTTP de referência só em escopo gerido pela superfície HTTP; as quatro permissões de arquivo fora de papel de tenantPendente
- Hoje · 11.1.0
- Entregue, por adesão. O Nexio precisa declarar
GrydAuth:Roles:TenantAssignable:Denyantes de haver papel composto, porque a opção não limpa o que já existe. - Por que existe
- (1)
POST /files/{id}/referencesaceitaownerScope/ownerKeylivres, eDELETE …/references/{referenceId}libera referência de qualquer escopo, só comupload:files: prende um arquivo para sempre com dono inventado (e trava a eliminação LGPD) ou solta a referência do documento de outro módulo. (2)upload:files,read:files,retain:filesepurge:filestêm alcance de tenant e nenhum papel semeado as recebe, mas compor papel só barra permissão de plataforma: o administrador do tenant as põe num papel e ganha, pelaIFileServicee pelas rotas, o que oCanReaddo dono deveria decidir — inclusive estender guarda e expurgar. - Requisito
- (1) as duas rotas só criam e liberam referência de
ownerScopeque o registro do escopo declara gerido pela superfície HTTP — nenhum, por padrão; escopo de módulo que consome em processo → 403FILE_OWNER_SCOPE_FORBIDDEN, na criação e na liberação, qualquer que seja a permissão. (2) O produto que autoriza arquivo pelo dono consegue declarar que as quatro permissões do módulo não são concedíveis a papel de tenant — nem semeadas, nem compostas pelo administrador do tenant; a composição é recusada. O mecanismo é do estudo da plataforma. Essas quatro são policies da superfície HTTP e não são o que autoriza o consumidor em processo (P12w). - Critério de aceite
- (a)
POST …/referencescomownerScope = nexio.attachment→ 403 e nenhuma referência; (b)DELETEde uma referênciagryd.report-executionpela rota → 403 e a referência continua ativa; (c) no host do Nexio, compor papel de tenant com qualquer das quatro → recusado, e nenhum papel de tenant, semeado ou composto, tem alguma delas. - Evidência
GrydFiles.API/Controllers/FilesController.cs:26(rotaapi/v{version}/files),:166e:192(referências) ·FileRequests.cs:60–64(OwnerScopelivre) ·FileService.csAddReferenceAsync~514 eReleaseReferenceAsync~542 (libera peloreferenceId, sem olhar o escopo) · as quatro com alcance de tenant e sem papel-semente (GrydAuth.Domain/Authorization/AuthorizationCatalogDefinitions.cs:164–167) · a composição de papel só barra permissão de plataforma (GrydAuth.Application/Authorization/RoleGrantContext.cs:90,:108,GrantCeiling.PlatformScopedOnly). Contrapartida no Nexio: nenhum host expõeGrydFiles.API(asserção 8 da Arquitetura). GrydFiles RN-GF-23.- Na 10.2.0
ReferenceSurfacePolicyeManagedThroughHttpSurface();GrydAuth:Roles:TenantAssignable:Denycobre o catálogo. A opção é de adesão, vazia por padrão, e não limpa papel já composto: o host do Nexio precisa declará-la.- Bloqueia
- (1) a primeira Requisição com anexo · a primeira eliminação em produção; (2) o primeiro tenant pago do Nexio (junto do P5).
- Onde no Nexio
- GrydFiles · Arquitetura
P12mOperação que a trilha nomeia exige autor identificadoResolvido
- Hoje · 11.1.0
- Entregue. Sem ação além de compor o módulo.
- Por que existe
- o contrato em processo não recebe usuário, e o serviço lê o usuário do contexto HTTP. Só a rejeição de tamanho, a liberação de
legalHolde o expurgo manual exigem usuário, e porInvalidOperationException(500); reservar e emitir URL de download passam com usuário nulo — a reserva não se amarra a ninguém (P12k fica impossível) e a trilha grava emissão sem autor. E a liberação de guarda é gravada antes de conferir o usuário. - Requisito
- pela
IFileService, reservar, confirmar, emitir URL de download, expurgar e ligar/desligarlegalHoldexigem autor identificado: o usuário do contexto — ou, só para ligar/desligarlegalHolde para a eliminação, o autor declarado na chamada em processo, gravado marcado como declarado (P12u). Sem usuário no contexto e sem autor declarado → 401FILE_CALLER_UNAUTHORIZEDantes de qualquer efeito; autor declarado diferente do usuário do contexto → 401. Ler metadado (GetAsync) não exige usuário. Ação de sistema é declarada como tal, nunca usuário nulo em silêncio. - Critério de aceite
ReserveAsynceGetDownloadUrlAsyncsem usuário → 401 com o código, nenhum registro e nenhuma URL; desligar guarda sem usuário e sem autor declarado → 401 e a guarda continua ligada;GetAsyncsem usuário e com tenant → devolve o registro; a trilha de download mostra o usuário.- Evidência
IFileService.cs:33, 75(sem usuário) ·FileService.cs:59(ICurrentUserService) · o usuário vem doHttpContext(GrydAuth.Infrastructure/Services/Authentication/CurrentUserService.cs:28) ·GrydFiles.Application/Audit/IFileAuditTrail.cs:30(userIdanulável) ·FileService.cs~314, ~728, ~831 (as três exigências por exceção),:724grava antes de:728–729lançar;ReserveAsync~102 eGetDownloadUrlAsync~403 sem exigência. GrydFiles RN-GF-26.- Na 10.2.0
FileActorPolicyrecusa comFILE_CALLER_UNAUTHORIZEDantes de qualquer efeito.- Bloqueia
- a primeira Requisição com anexo.
- Onde no Nexio
- GrydFiles
P12nLeitura de arquivo sem tenant falha fechadaResolvido
- Hoje · 11.1.0
- Entregue. A exceção restante é a métrica
GetScanQueueAsync. - Por que existe
- o registro de arquivo é de tenant opcional, e o filtro padrão, sem tenant em vigor, devolve os arquivos de todos os tenants.
- Requisito
- o do P2 aplicado ao GrydFiles — leitura sem tenant falha fechada; job que varre todos desliga o filtro só para selecionar candidatos e processa cada arquivo com o tenant dele; nunca entrega veredito com o filtro desligado. Até a entrega, estes jobs são a exceção declarada do Worker do Nexio à regra de não desligar o filtro (asserção 9 da Arquitetura).
- Critério de aceite
- leitura sem tenant, com dois tenants populados → erro ou nenhum arquivo de tenant;
IFileService.GetAsyncsem tenant sobre arquivo de um tenant →FILE_NOT_FOUND; job de varredura processa cada arquivo com o tenant dele (teste com dois tenants). - Evidência
Core/Gryd.Infrastructure/Tenancy/TenantQueryFilterExtensions.cs:170·GrydFiles.Infrastructure/Persistence/GrydFilesDbContext.cs:48(usa a opção padrão) ·StoredFileé de tenant opcional (StoredFile.cs:22) · as seis rotinas do GrydFiles desligam o filtro na execução inteira (FileScanJob.cs:107,ConsistencySweepJob.cs:112,ReferenceReconciliationJob.cs:78,RetentionPurgeJob.cs:98,ScannerHealthMonitorJob.cs:76,ExpiredUploadSweepJob.cs:77).- Na 10.2.0
CandidateScopesnas varreduras e leitura sob o filtro do tenant; única exceção do módulo: a métricaGetScanQueueAsync.- Bloqueia
- a primeira Requisição com anexo.
- Onde no Nexio
- GrydFiles · Arquitetura
P12oQuarentena a partir do objeto lido; leitura do scan com tetoParcial
- Hoje · 11.1.0
- Entregue. A prova ponta a ponta é só em MinIO/S3. O Nexio precisa da regra de ciclo de vida de
_transit/no bucket, que o Terraform ainda não tem. - Por que existe
- (a) a cópia para a quarentena relê a chave que ainda aceita escrita — um
PUTentre o scan positivo e a cópia troca a evidência, e osha256da lápide deixa de ser o do conteúdo guardado; (b) o scan não compara os bytes lidos comsizeBytes: onde o provedor não assina tamanho, umPUTmaior depois da confirmação é escaneado e liberado. - Requisito
- cópia para a quarentena a partir de chave que nenhuma URL alcança, ou condicionada à versão (ETag, generation) lida no scan; condição recusada →
Infected, nunca servido, incidente na trilha. O scan lê no máximosizeBytes + 1bytes; leitura acima desizeBytes, ou que termina antes dele, em arquivo ainda não liberado →PurgedcomFILE_OBJECT_CHANGED_UNPROCESSABLEe motivojob:objeto-alterado. Divergência é só a confirmada (complemento da T8 — RN-GF-17, RN-GF-18): leitura que passou desizeBytes, ou que terminou antes sem erro de E/S e com o tamanho no storage também diferente; leitura interrompida por erro de E/S é tentativa com erro, e o arquivo segue no estado em que estava. No re-scan deAvailable, nuncaPurged: o tamanho divergente é incidente de integridade —Available → Infectedpelo caminho do re-scan positivo (objeto lido copiado para a quarentena,rejectionCode = FILE_OBJECT_CHANGED_UNPROCESSABLE, referências liberadas sem tocar em data e guarda, veredito a cada dono), e o piso da retenção continua valendo. - Critério de aceite
- (1)
PUTmaior, antes ou depois da confirmação → nunca liberado nem servido, nos três provedores; (2)PUTentre o scan positivo e a cópia → objeto da quarentena com osha256gravado, ou cópia recusada e incidente na trilha; (3) re-scan deAvailablecomretainUntilfuturo e leitura acima desizeBytes→Infected, fora do download, incidente na trilha,retainUntilintacto, nenhumPurged; (4) re-scan deAvailablecom a leitura interrompida por erro de E/S → tentativa com erro gravada, arquivo segueAvailable. - Evidência
GrydFiles.Infrastructure/Jobs/FileScanJob.cs:606(MoveToQuarantineAsynclê e regrava a chave original) ·:287(BytesReadsó vai para a tentativa).- Na 10.2.0
ScanReader,ReadIntegrityStepeFileQuarantine. A leitura é em streaming; queda no meio fecha a tentativa como erro. Ponta a ponta só em MinIO/S3; Azure e GCS pelos testes dos adaptadores.- Bloqueia
- a primeira Requisição com anexo.
- Onde no Nexio
- GrydFiles
P12pQuarentena respeita a guarda; Infected fica até o fim da guarda do donoParcial
- Hoje · 11.1.0
- Entregue. Há teste para quarentena esperando
retainUntilmais tarde; não localizei teste nomeado para estender a data de um arquivo já emInfected. - Por que existe
Available → Infectedlibera todas as referências, e a varredura expurga a quarentena depois deQuarantineRetentionDaysolhando sólegalHold— prova sobretainUntilsome; e o infectado de documento fechado precisa durar até o fim da guarda do dono.- Requisito
- expurgo de
Infectedsó quando valem todas:QuarantineRetentionDaysdesde o veredito positivo;retainUntilvencido (modoRetainUntil);legalHolddesligado; modo diferente dePermanent. A transição libera as referências sem tocar emretentionMode,retainUntilelegalHold. O dono pode estender oretainUntilde arquivo emInfected; vence a data mais tarde. - Critério de aceite
- (1)
Infectedhá 400 dias comretainUntilfuturo → não expurga; (2) comlegalHold→ não expurga; (3) re-scan positivo sobreAvailablecomretainUntilfuturo → referências liberadas, data intacta; (4) arquivo comretainUntilem 2031 que viraInfectedem 2027 continua na quarentena até 2031. - Evidência
GrydFiles.Infrastructure/Jobs/ConsistencySweepJob.cs:199(IsQuarantineExpired:Infected, semLegalHolde veredito antes do corte).- Na 10.2.0
PurgeEligibilityrespeita a guarda, e a quarentena fica sob o piso. Sem teste nomeado para estenderretainUntilemInfected.- Bloqueia
- o primeiro expurgo em produção · o primeiro re-scan de acervo em produção.
- Onde no Nexio
- Anexo · GrydFiles
P12qExpurgo antes do fim da guarda só como eliminação; Permanent com dois autores; pré-condição consultável em dois modos, por lista de pares, serializada com a guarda; trilha durávelPendente
- Hoje · 11.1.0
- Entregue. O Nexio precisa registrar o adaptador de
IFileActorDirectory; sem ele, toda eliminação em processo responde 422. - Por que existe
- o expurgo manual não olha
retainUntil: quem tempurge:filesapaga prova sob guarda sem marca;admin:systemcompurge:filesapaga arquivoPermanentcom qualquer motivo; e a trilha registrada por padrão não guarda nada. Do lado do Nexio, a eliminação do Anexo é praticada por pessoa comerase:attachments, revoga o anexo e publicaattachment.erased, e o expurgo é pedido depois do commit pelo job, sem usuário. EmPermanent(SIGNED_CONTRACT) o GrydFiles exigeadmin:systemde pessoa no contexto → a eliminação a pedido do titular é recusada para sempre, depois de o ato ter sido dado por feito: os bytes com dado pessoal ficam. E o expurgo só aceitaAvailable: o arquivo que o re-scan pôs em quarentena não sai nem a pedido do titular. - Requisito
- com
retainUntilfuturo — ou emPermanent, sempre — o expurgo é recusado com 409FILE_RETENTION_NOT_EXPIRED_CONFLICT, salvo o ato de eliminação: marcação explícita (erasure) e motivo. Pela rota, compurge:filese, emPermanent,admin:systemde pessoa autenticada. Em processo, pelo consumidor registrado para o escopo, com a referência do pedido do titular, sem conferir permissão (P12w) e, emPermanent, com dois autores declarados que sejam usuários diferentes — quem registrou o pedido e quem o executou (EV-23; comparação por usuário, sem CPF nem conflito de interesse — risco aceito, EV-24); um autor só, ou os dois iguais → 409FILE_ERASURE_DUAL_CUSTODY_CONFLICT, nada gravado;admin:systemnão é conferido nem substitui o segundo autor nesse caminho. Com usuário no contexto — a execução pela rota, feita por gente —, o executor é o usuário do contexto (gravado empurgedBy) e o outro autor, quem registrou o pedido, vai declarado e isento do 401; a isenção vale só no ato de eliminação, e fora dele a recusa de autor declarado diferente do usuário do contexto continua. Sem usuário no contexto, os dois vêm declarados. Os dois autores são conferidos, não só comparados: usuários existentes, ativos e do tenant do arquivo — id vazio, id inexistente, usuário inativo ou de outro tenant → 422FILE_ERASURE_AUTHOR_INVALID_UNPROCESSABLE, antes de qualquer efeito. Em arquivo de produto o tenant conferido é o de quem pede: arquivo de produto temtenantIdnulo e usuário nenhum pertence a ele, de modo que exigir o tenant do arquivo não teria solução — nenhum autor passaria nunca, e o arquivo seria ineliminável por construção; nesse caso os dois autores são usuários do tenant do consumidor que pede, do mesmo tenant entre si e distintos, conferidos do mesmo jeito e com o mesmo código (emenda à P30 da T8, validação R6). E o caminho em processo em arquivo de produto não passa poradmin:system: autorizam o consumidor registrado para o escopo, a dupla custódia declarada e ao menos um par do próprio escopo com referência ativa na lista do pedido — sem tenant, a referência é o único vínculo entre o consumidor e o arquivo, e sem ela a eliminação é recusada com 403FILE_OWNER_SCOPE_FORBIDDEN. Pela rota, arquivo de produto continua exigindoadmin:systemde pessoa no contexto (emenda à P30 da T8, validação R7). A eliminação alcançaAvailable,InfectedeFailed(ET9-3 — a lápide fica como prova; perícia pede guarda legal por caso, que vence o titular);PendingeScanning→FILE_NOT_AVAILABLE_CONFLICT. Guarda de qualquer detentor →FILE_LEGAL_HOLD_CONFLICTantes de qualquer efeito; referência ativa de outro dono →FILE_STILL_REFERENCED_CONFLICT. O pedido em processo nomeia o escopo do consumidor e um ou mais pares — o arquivo propagado tem mais de uma cópia do mesmo escopo, e tratar a segunda como outro dono fazia arquivo propagado nunca ser eliminado —, e todas as referências nomeadas são liberadas na mesma transação do expurgo; referência do próprio escopo fora da lista também recusa (é o consumidor que diz quais cópias o pedido alcança), e par de outro escopo na lista → 403FILE_OWNER_SCOPE_FORBIDDEN. Consulta de pré-condição em processo, sem efeito, com as mesmas conferências, na mesma ordem e com o mesmo código que o expurgo daria naquele modo — e ela tem dois modos, porque o consumidor precisa dela em dois momentos: no modo registro, quando só existe quem está registrando o pedido, ela confere estado, marcação, motivo, o autor que vier, a lista de pares, referência de outro escopo, guarda de qualquer detentor e trilha durável, e não confere a dupla custódia; no modo execução, com os dois autores conhecidos, confere tudo. O modo vem no pedido, e o modo registro nunca decide a execução. Com um modo só, o pedido do titular sobre arquivoPermanent— justamente o que sempre pede segunda pessoa — é recusado no registro e nunca existe. A consulta não reserva, e por isso ligar e retirar guarda e expurgar se serializam: ligar guarda, em processo ou pela rota, trava o mesmo registro que o expurgo, e o expurgo reavalia as guardas por detentor sob a trava, nunca com o retrato da consulta. Guarda ligada no meio vence: o expurgo respondeFILE_LEGAL_HOLD_CONFLICTdevolvendo o estado — os detentores ativos epurgedAtnulo —, o consumidor mostra o pedido como bloqueado, nunca como concluído, e só publica a eliminação quando a resposta trazpurgedAt. A eliminação apaga o nome original, grava os autores marcados como declarados e o executor de serviço (P12u), é ato de controle (P4) gravado na transação do expurgo, e não acontece sem trilha durável (503FILE_AUDIT_TRAIL_UNAVAILABLE, nada apagado). Idempotente. - Critério de aceite
- (1) sem a marcação, com data futura → 409 e nada apagado; (2) com a marcação e o motivo →
Purged, nome nulo e o ato na trilha com o autor declarado marcado e o motivo — nunca o executor de serviço como autor; (3) eliminação com referência ativa de outro dono →FILE_STILL_REFERENCED_CONFLICT; (4)Permanentcomadmin:systeme motivo, sem a marcação → 409 e nada apagado; (5) eliminação com a trilha padrão registrada → 503 e nada apagado; (6) repetir o pedido já confirmado → nenhuma segunda linha na trilha; (7)Permanentem processo com um autor só, ou dois iguais → 409FILE_ERASURE_DUAL_CUSTODY_CONFLICT, nada apagado e a referência do par ativa; (8)Permanentem processo com dois autores diferentes, sem usuário no contexto e sem permissão de arquivo em lugar nenhum →Purged, lápide com os dois autoresDeclared, o escopo e o executor; (9) guarda de qualquer detentor — consumidor, outro consumidor ou plataforma →FILE_LEGAL_HOLD_CONFLICTantes de qualquer efeito, na consulta e no expurgo; (10) para cada recusa e para o aceite, a consulta de pré-condição responde o mesmo código que o expurgo e não grava nada (teste de propriedade sobre os estados); (11)Infectedem quarentena com ato de eliminação válido →Purged, lápide com a assinatura;Pending→FILE_NOT_AVAILABLE_CONFLICTna consulta e no expurgo; (12) consulta no modo registro com um autor só → aceite, e a mesma no modo execução →FILE_ERASURE_DUAL_CUSTODY_CONFLICT; (13) eliminação com usuário no contexto e o autor do registro declarado, diferente dele →Purged,purgedBy= usuário do contexto, o outro autor gravado como quem registrou, ato na trilha como autenticado, nenhum 401; autor inexistente, inativo ou de outro tenant → 422 e nada apagado; (14) arquivo com dois pares do mesmo escopo, com a lista nomeando os dois →Purgede as duas referências liberadas na mesma transação; a mesma eliminação nomeando um par só →FILE_STILL_REFERENCED_CONFLICTe nada apagado; (15) ligar guarda e expurgo concorrentes sobre o mesmo arquivo → ou a guarda fica ativa e o objeto também, com a recusa trazendo o detentor epurgedAtnulo, ou o registro ficaPurgede o pedido de guarda respondeFILE_PURGED_CONFLICT— nunca guarda ativa sobre objeto apagado, nuncapurgedAtgravado com guarda ativa; (16) retirar guarda e expurgo concorrentes sobre o arquivo cuja última guarda é essa → ou a retirada gravou primeiro e o expurgo, reavaliando sob a trava, apagou, ou o expurgo gravou primeiro e a retirada respondeuFILE_PURGED_CONFLICT; e a recusa do expurgo por guarda nunca lista detentor já retirado (emenda à P32 da T8, validação R6 — sem a trava na retirada, o expurgo recusa trazendo detentor que acabou de sair e o consumidor trata como bloqueado um pedido preso por guarda que já não existe); (17) eliminação de arquivo de produto (tenantIdnulo) com dois autores distintos do tenant de quem pede →Purged; a mesma com um dos autores de outro tenant → 422 e nada apagado. - Evidência (
e4eb5a4f) GrydFiles.Application/Services/FileService.cs:786(sóAvailable),:791–803(Permanentsó comadmin:systemde pessoa),:805(legalHoldcomo bit),:831(sem usuário lançaInvalidOperationException); expurgo sem olharretainUntil(~765–850);IFileServicesem consulta de pré-condição;PurgeFileRequestsem marcação nem autores (FileRequests.cs~103) ·Core/Gryd.Observability/Extensions/ObservabilityServiceExtensions.cs:36,:65(TryAddSingleton<IAuditStore>(NullAuditStore.Instance)) ·StoredFileConfiguration.cs:113(sóExpectedOwnerKeycomo token de concorrência; nemPurgeAsyncnemUpdateRetentionAsynctravam o registro, eLockedFilePurgeexiste só no job) ·FileReferencepor(ownerScope, ownerKey), sem caminho de liberação em lote pelo mesmo escopo.- Na 10.2.0
- Cadeia de conferências em
PurgeLinks,CheckPurgeAsyncem dois modos, dois autores emPermanent. A mesma pessoa nos dois papéis agora gravastored_file.erasure_refused(DEC-GF-19-11). Repetir eliminação já feita respondeFILE_PURGED_CONFLICT, compurgedAt, motivo erejectionCode, nada é apagado de novo e nenhuma linha nova vai à trilha. Fechado: o teste que conta as linhas da trilha na repetição (critério 6) entrou. Diferenças decididas:admin:systemviroupurge-permanent:filesepurge:product-files; semIFileActorDirectoryno host, 422. - Bloqueia
- o primeiro expurgo em produção · a primeira eliminação em produção (com a trilha durável do P4) · a primeira eliminação de
Permanenta pedido de titular. Até lá o Anexo recusa na consulta — nunca revoga o que não conseguirá expurgar. - Onde no Nexio
- Anexo · GrydFiles
P12rO registro do veredito guarda tenant e dono; o handler roda sem usuárioPendente
- Hoje · 11.1.0
- Entregue. O registro do consumidor exige
IFileOutcomeHandler, que o Anexo precisa implementar. - Por que existe
- o veredito negativo zera a referência esperada na mesma transação, e o re-scan positivo libera as referências: sem o dono gravado, ninguém sabe a quem entregar. E o handler relê o arquivo pelo serviço, fora de request, onde não há usuário.
- Requisito
- o registro do veredito a entregar, gravado na transação do veredito, guarda o tenant e o dono (
ownerScopeeownerKeyda referência esperada, ou de cada referência liberada). O handler roda com esse tenant restaurado, sem usuário, relê porGetAsync(que não exige usuário, P12m), não executa comando em nome de pessoa e falha fechado sem tenant. - Critério de aceite
- (1)
Infectedsobre arquivo com referência esperada → a entrega chega ao dono declarado; (2) handler sem usuário e com tenant → relê e conclui. - Evidência
GrydFiles.Core/Entities/StoredFile.cs~528 (MarkInfecteddescarta a referência esperada), ~1156 (lápide); não há registro de veredito a entregar emsrc/.- Na 10.2.0
FileOutcomeRecordguarda tenant e dono; em v2,FileOutcomeRecordV2separa tenant do arquivo e destino do dono.- Bloqueia
- a primeira Requisição com anexo.
- Onde no Nexio
- GrydFiles
P12sTrilha de download durávelPendente
- Hoje · 11.1.0
- Entregue. O host precisa de um
IAuditStoredurável, ou não sobe. Conferir ao compor: o Nexio já usa o GrydAudit, mas ainda não compõe o GrydFiles. - Por que existe
- a emissão de URL de download é registrada pela trilha do módulo, que grava pelo armazenamento de auditoria — e o registrado por padrão não guarda nada: a emissão não fica em lugar nenhum. "Quem baixou o quê" é a primeira pergunta de uma auditoria de vazamento.
- Requisito
- toda emissão de URL de download fica registrada em armazenamento durável e isolado por tenant, com usuário, arquivo e instante; em produção não há emissão sem esse registro. Não é ato de controle (é leitura, fora da transação de um ato), mas usa a mesma trilha corrigida do P4.
- Critério de aceite
- (1) emitir URL grava um registro com usuário e arquivo, lido só pelo tenant dele; (2) ambiente produtivo com a trilha padrão registrada → não emite URL (e a configuração que a registra derruba o arranque de produção, como o scanner falso do P12c).
- Evidência
GrydFiles.Application/Audit/IFileAuditTrail.cs:30·ObservabilityServiceExtensions.cs:36,:65(NullAuditStorepor padrão).- Na 10.2.0
- A emissão vai à trilha na transação, e arquivo sem tenant exige rota durável. Critério (2) fechado: o host sem store durável não sobe (
FileAuditTrailRequirement), a regra vale em todos os ambientes (decisão do Roger, 30/10/2026) e o download de arquivo de tenant sem trilha durável respondeFILE_AUDIT_TRAIL_UNAVAILABLE. Do Nexio: registrarAddGrydAuditnos hosts. - Bloqueia
- o primeiro download em produção — isto é, a primeira Requisição com anexo. A B11 marca a tela de ato de controle; a trilha de download é requisito da RN-GF-12 e vale desde o primeiro download.
- Onde no Nexio
- GrydFiles
P12tA porta de conteúdo no servidor só nos consumidores declaradosPendente
- Hoje · 11.1.0
- Entregue no framework, com seis consumidores declarados. O Nexio repete a asserção nos assemblies dele quando usar a porta.
- Por que existe
- o teste que mantém o conteúdo fora da superfície HTTP olha só o
GrydFiles.API; um assembly novo pode ler bytes pelaIServerFileStoresem ser declarado. - Requisito
- asserção, no repositório do Gryd.IO, de que os assemblies que dependem de
IServerFileStoresão exatamente os consumidores declarados (hojeGrydReports.Application,GrydReports.Infrastructure,GrydNotifications.ApplicationeGrydNotifications.Infrastructure) e de que nenhuma action doGrydFiles.APIrecebe ou devolve conteúdo. Liberar referência por rota do próprio dono, sem mover byte, é admitido. - Critério de aceite
- (1) assembly fora da lista que chama
OpenReadAsync→ o teste falha; (2) action doGrydFiles.APIque devolveStream→ o teste falha. - Evidência
ServerContentStaysOffTheSurfaceTests(só oGrydFiles.API) ·DELETE /reports/{id}chega aReleaseAsync(ReportsController.cs~155 →DeleteReportCommandHandler.cs~61). O Nexio usa a porta: a planilha de importação processada por job lê pelaOpenReadAsynco arquivo já liberado (GrydFiles §Endpoints), com o par da referência ativa do próprio consumidor (P12v); o Anexo não usa — passa pelaIFileService. Pela RN-GF-06, o repositório do Nexio repete a asserção dos consumidores declarados para os assemblies dele — critério do consumidor, não pedido à plataforma.- Na 10.2.0
DeclaredServerPortConsumerslista os seis consumidores do framework.- Bloqueia
- o primeiro consumidor novo da porta.
- Onde no Nexio
- Anexo · GrydFiles · Arquitetura
P12uGuarda por detentor; o autor do ato viaja como dado e fica marcado como declaradoResolvido
- Hoje · 11.1.0
- Entregue. O uso é do Anexo. Arquivo sem tenant depende da adesão à rota de plataforma, se o Nexio usar arquivo de produto.
- Por que existe
- o Anexo liga e retira a guarda por ato de pessoa e o job de repetição refaz o efeito no GrydFiles depois do commit, só com o tenant. No GrydFiles a guarda é um bit: não se sabe quem a pôs, o segundo detentor que precisa do arquivo não fica registrado e perde a proteção quando o primeiro retira; desligar exige
admin:systemde qualquer chamador, e ligar, desligar e expurgar exigem usuário → na repetição, 401. A trilha não distingue quem entrou com sessão de quem foi nomeado por um serviço, é gravada depois do commit, e ligar guarda nem vai à trilha. - Requisito
- (1) guarda por detentor —
legalHoldé derivado das guardas ativas; detentor é o consumidor registrado (peloownerScope) ou a plataforma (rota). Cada detentor liga e retira a sua, no máximo uma ativa por detentor, com autor, tipo de autor, motivo e instante; o módulo não conta casos (o Anexo liga na primeira guarda de caso e retira na última). Ligar sobre guarda de outro detentor acrescenta — as duas ficam ativas —; repetir o mesmo ato → nenhum efeito novo; o arquivo fica preso enquanto houver detentor ativo. (2) Em processo, retirar a sua não exigeadmin:system; retirar sem guarda própria ativa → nenhum efeito. Pela rota,legalHold: falseretira só a da plataforma, comadmin:system; sobre arquivo cuja guarda restante é só de consumidor → 403FILE_OWNER_SCOPE_FORBIDDEN. Resposta e recusa de expurgo listam os detentores;FILE_LEGAL_HOLD_CONFLICTdeixa de valer para ligar. (3) Ligar e retirar guarda e a eliminação em processo aceitam o autor declarado — o usuário que praticou o ato já gravado pelo consumidor —, com o motivo; o autor não é identidade (não concede permissão, não existe na rota HTTP). A trilha e a lápide gravam o tipo de autor —Job·Authenticated·Declared— e, noDeclared, o escopo que declarou e o executor de serviço;purgedBynulo quando e somente quando o tipo éJob; nenhuma leitura mostra autor declarado sem o tipo. Com usuário no contexto, o autor declarado precisa ser ele (401 se diferente), e o ato é gravado comoAuthenticated. (4) O 401 só vale sem usuário no contexto e sem autor declarado. (5) Escopo, autor e trilha conferidos antes de gravar; ligar, retirar e eliminar gravam o registro do ato na mesma transação da mudança, e ligar também vai à trilha, com autor, tipo e motivo (Ata B11, P4). (6) Retirar exige trilha durável (P12q); ligar nunca é recusado por falta de trilha. - Critério de aceite
- (a) guarda da plataforma ativa, consumidor liga em processo → as duas ativas; a plataforma retira pela rota → o arquivo continua preso e o expurgo responde
FILE_LEGAL_HOLD_CONFLICT; (b) guarda de consumidor ativa,PUT legalHold: true→ as duas ativas; o consumidor retira → continua preso pela da plataforma; (c) dois consumidores com guarda, um retira → continua preso; (d)PUT legalHold: falsecomadmin:systemsobre arquivo só com guarda de consumidor → 403, guarda intacta; (e) repetição pelo job, sem usuário, com autor declarado → guarda ligada, trilha e lápide comDeclared, o autor, o escopo e o executor, nenhum 401; expurgo pela rota →Authenticated; job de retenção →Job,purgedBynulo; (f) retirar sem usuário e sem autor → 401 e nada gravado; (g) autor declarado diferente do usuário do contexto → 401 e nada gravado; (h) processo derrubado logo depois do commit de ligar, retirar ou eliminar → o registro do ato existe; ligar → registro com autor e motivo. - Evidência (
e4eb5a4f) GrydFiles.Core/Entities/StoredFile.cs:171(bool LegalHold),:768–777(PlaceLegalHold/ReleaseLegalHoldsem detentor),:272(PurgedBysem tipo) ·GrydFiles.Application/Audit/IFileAuditTrail.cs:44,:71(autor sem tipo nem escopo; sóLegalHoldReleasedAsync, ligar não vai à trilha) ·GrydFiles.Application/Services/FileService.cs:687(retirar exigeadmin:system),:724grava a mudança e:732a trilha depois,:850e:852idem no expurgo ·UpdateFileRetentionRequestsó comRetainUntileLegalHold(FileRequests.cs:82–92), sem detentor, motivo nem autor.- Na 10.2.0
FileLegalHold, por detentor, ePlaceLegalHoldAsync/ReleaseLegalHoldAsync. Em arquivo sem tenant a linha vai na transação, comAddGrydFilesPlatformOutbox.release-hold:filessubstituiadmin:system.- Bloqueia
- a primeira guarda legal ou eliminação em produção.
- Onde no Nexio
- Anexo · GrydFiles
P12vLeitura em processo exige referência do donoResolvido
- Hoje · 11.1.0
- Entregue. O stream é só para frente e precisa ser descartado.
- Por que existe
- a leitura de conteúdo em processo recebe só o arquivo e o tenant e lê qualquer
Availabledo tenant. Sem persistência de notificação, o GrydNotifications anexa sem criar referência e o envio lê os bytes pela porta:POST /notifications/emailcom ofileIdde um arquivo alheio manda o conteúdo por e-mail — exfiltração porfileId, sem que a posse do P12k seja consultada. - Requisito
OpenReadAsyncrecebe o dono que lê — o par (ownerScope,ownerKey) — e só devolve bytes se esse par tem referência ativa no arquivo (a esperada vira ativa na liberação); sem ela, 409FILE_REFERENCE_NOT_ALLOWED_CONFLICTe nenhum byte. O escopo sozinho não basta: outro dono do mesmo escopo já pode ter referência. O anexo de notificação passa a precisar de referência, criada sob a posse do P12k, também sem persistência. No Nexio, quem lê pela porta é o job da planilha de importação, com o par da referência ativa do próprio registro; o Anexo não lê pela porta (P12t).- Critério de aceite
- (1)
OpenReadAsynccom par sem referência ativa → 409 e nenhum byte; (2) e-mail com anexo que outra pessoa subiu, com ou sem persistência da notificação → recusado antes do envio, e nenhum byte sai. - Evidência
GrydFiles.Application/InProcess/ServerFileStore.cs:200(OpenReadAsyncsem dono) ·NotificationAttachments.cs:112(sóGetAsync) e:180(sem persistência não segura referência) ·GrydFilesEmailAttachmentReader.cs:34·NotificationsController.cs:61(POST /notifications/email).- Na 10.2.0
OpenReadAsync(fileId, tenantId, ownerKey)só entrega ao dono com referência ativa. O stream é só para frente e precisa ser descartado.- Bloqueia
- o primeiro e-mail com anexo em produção.
- Onde no Nexio
- Anexo · GrydFiles
P12wOperações em processo fora de request: tenant nomeado e conferido, porta amarrada ao escopo, autor declarado marcado, sem conferência de permissãoPendente
- Hoje · 11.1.0
- Entregue. Falta a décima asserção da Arquitetura no Nexio. Leitura de estado em lote só existirá se o Nexio pedir.
- Por que existe
- o job de repetição do Anexo refaz, sem request e sem usuário: liberar referência, criar referência de cópia, estender retenção, ligar e retirar guarda, pedir o expurgo como eliminação e ler estado em lote. O serviço em processo lê tenant e usuário do contexto HTTP. E sem contrato único para a chamada em processo o consumidor supõe que uma identidade de serviço com
purge:fileso autoriza — mas o GrydFiles não define identidade de serviço e aIFileServicenão confere permissão de módulo; e qualquer assembly do processo que passe o escoponexio.attachmentretira a guarda do Anexo ou pede a eliminação de um anexo, e nenhuma permissão o barraria. - Requisito
- essas operações disponíveis em processo com tenant nomeado, falha fechada sem tenant, todas idempotentes. Contrato único de tenant nas duas portas: a
IFileServicee aIServerFileStorerecebem o tenant nomeado pelo chamador, a partir do registro do próprio consumidor — a repetição depois do commit roda sem request e, lendo do contexto, não teria tenant nenhum. Nomear não é escolher: havendo tenant em vigor, o nomeado precisa ser igual a ele (diferente →FILE_NOT_FOUND); não havendo, o escopo do tenant nomeado é aberto antes da leitura, com o filtro ligado. Isso alcança oIServerFileStore.OpenReadAsync, que hoje desliga os filtros e atende o tenant do parâmetro sem compará-lo com o em vigor — quem tem a porta escolhe o tenant, e é assim que o leitor de anexo de e-mail a chama;IgnoreQueryFilters()sai do caminho de leitura, e desligar o filtro continua valendo só para o job que seleciona candidatos. Contrato em processo — frase única com o GrydFiles (RN-GF-28) e o Anexo: em processo, aIFileServicenão confere permissão de módulo e o GrydFiles não define identidade de serviço; vale o consumidor registrado para o escopo, o ato e o autor declarado marcado.admin:systemsó de pessoa autenticada, só onde a spec o nomeia. TodoownerScopeque chega àIFileServiceou àIServerFileStore— referência esperada, referência direta, guarda, eliminação — só é aceito do consumidor registrado para ele; escopo sem registro → 403FILE_OWNER_SCOPE_FORBIDDENantes de qualquer efeito. E a posse do escopo deixa de ser argumento: o registro do consumidor devolve a ele uma porta amarrada ao escopo dele — é por ela que ele reserva, confirma, declara referência, liga e retira guarda e pede eliminação —, e nas assinaturas dessa porta o escopo não aparece; a porta com escopo por parâmetro deixa de ser alcançável pelo consumidor. Como a plataforma amarra o escopo à porta é decisão dela (Ata A1). O teste de arquitetura passa a conferir quem resolve a porta de cada escopo, e não quem passa o texto dele: os escopos são constantes que o compilador embute em cada chamador, e nenhum teste de dependência entre assemblies vê quem passounexio.attachment— a prova antiga era inverificável. E resolver não é só injetar: chave de texto,Typemontado em execução e reflexão (IServiceProvider.GetService(Type)) alcançam a porta sem deixar dependência entre assemblies — o mesmo defeito da constante embutida, um nível acima —, e com eles a asserção volta a ser inverificável. Por isso a porta de cada escopo só se resolve por um tipo-marca do próprio consumidor: um tipo que mora no assembly registrado para aquele escopo, de modo que resolver a porta obrigue a referenciar esse assembly e a dependência apareça; chave de texto eTypemontado em execução não resolvem porta de escopo nenhum. Qual mecanismo amarra o tipo-marca ao registro é decisão da plataforma (Ata A1). E o teste estático sozinho não fecha — daí o critério (10), em execução (pedido P36 da T8, validação R6; complementa a P33 e substitui a forma do critério (a) dela). Isso é repetido no repositório do produto que registra o escopo — no Nexio, a décima asserção da Arquitetura. Conciliação com o P12m: só ligar e retirar guarda e a eliminação aceitam o autor declarado (P12u); reservar, confirmar e emitir URL continuam exigindo usuário no contexto e não são operação de job; liberar referência, propagar referência a partir de referência ativa do mesmo escopo (cópia, P12k), estender retenção e ler estado não exigem usuário. O executor de serviço vai à trilha como executor, nunca como autor. As quatro permissões de arquivo seguem fora de papel de tenant (P12l), e não são o que autoriza o consumidor. - Critério de aceite
- (1) job de um tenant repete uma liberação e um expurgo pendentes sem request, com a trilha mostrando o autor declarado marcado e o executor de serviço; (2) job sem tenant não toca nada; (3) repetir duas vezes não duplica referência nem trilha; (4) job que tenta reservar ou emitir URL sem usuário → 401 (P12m); (5) eliminação completa em processo, sem usuário e sem
purge:filesem lugar nenhum → aceita; a mesma pela rota, por usuário sempurge:files→ 403; (6) assembly fora do registro que chama aIFileServicecomnexio.attachment→ o teste de arquitetura falha; chamada comownerScopesem registro → 403 e nada gravado; (7) assembly fora do registro que resolve a porta denexio.attachment→ o teste falha, e continua falhando se esse assembly declarar a mesma constante por conta própria; (8) nenhuma assinatura da porta entregue ao consumidor tem parâmetro de escopo, e o consumidor não alcança por caminho nenhum uma porta em que o escopo seja argumento; (9) chamada em processo com tenant nomeado diferente do em vigor →FILE_NOT_FOUNDe nada lido; a mesma sem tenant em vigor → o escopo do nomeado é aberto e a leitura alcança só arquivo dele;IgnoreQueryFilters()= zero no caminho de leitura do repositório de arquivo; (10) em execução, falha na resolução — e nenhuma chamada chega àIFileServicecom aquele escopo — em dois casos: pedir ao contêiner a porta de um escopo não registrado, por tipo-marca de quem não registrou, por chave de texto ou porTypemontado em execução; e pedir a porta de um escopo registrado porTypemontado em execução, por tipo-marca obtido em execução ou pelo tipo-marca de quem não registrou aquela porta. O alvo dos dois é o mesmo — o pedido sem referência estática ao assembly do consumidor, registrado ou não —, e limitar a verificação ao escopo sem registro a deixa um nível abaixo do requisito: reflexão sobre o tipo-marca de um escopo registrado resolve a porta, e nem o (7) nem a metade não registrada a veem. O padrão existe no repositório do framework (JobExecutionAdapters.cs:235,:303eHangfireJobActivator.cs:43,e4eb5a4f) — não é hipótese. É a verificação que o teste estático não dá, porque o que ela reprova não aparece como dependência entre assemblies (emenda à P36 da T8, validação R7). - Evidência (
e4eb5a4f) - a porta em processo tem
StoreAsync,OpenReadAsynceReleaseAsync(GrydFiles.Application/InProcess/IServerFileStore.cs:57, 72, 85) — sem referência de cópia, retenção nemlegalHold; o serviço lê o usuário do HTTP (CurrentUserService.cs:28) ·purge:filessó na policy do controller (GrydFiles.API/Controllers/FilesController.cs:241–242);FileService.csconfere sóadmin:system(:149,:680,:687,:791) · o escopo recebido é aceito sem registro por consumidor (FileService.cs:535) ·NotificationFiles.cs:20eReportFiles.cs:26(escopo como constante que o compilador embute) ·EfStoredFileStore.cs:177–186(InTenantcomIgnoreQueryFilters()sobre o tenant do parâmetro) eGrydFilesEmailAttachmentReader.cs:34. - Na 10.2.0
IFileStore<TScope>eIServerFileStore<TScope>amarrados ao escopo,FileScopeGate, autor declarado marcado,NamedTenantScope. Fechado:GetAsync,AddReferenceAsynceUpdateRetentionAsyncaceitam o tenant nomeado (aditivo, porNamedTenantScope.RunAsync), de modo que o job abre o escopo. Leitura de estado em lote fica até o Nexio pedir: nenhum critério a exige. O critério (10) falha na primeira chamada, não na resolução (DEC-GF-09-1).- Bloqueia
- a primeira liberação por fim de guarda, a primeira guarda legal e a primeira eliminação em produção · o primeiro e-mail com anexo, que é o caminho do
GrydNotifications· e a asserção 9 da Arquitetura, que nomeia oOpenReadAsynccomo exceção declarada enquanto o tenant nomeado não for conferido. - Onde no Nexio
- Anexo · GrydFiles · Arquitetura
P12xCancelar a referência esperada em processoResolvido
- Hoje · 11.1.0
- Entregue. Sem ação além de usar a porta.
- Por que existe
- anexo revogado, ou dono descartado, antes do veredito: a liberação cria a referência de um anexo que já não quer o arquivo.
- Requisito
- o consumidor que confirmou pela
IFileServicecancela em processo a referência esperada de um arquivo (o dono declarado nela). - Critério de aceite
- (1) cancelada antes do veredito → a liberação não cria referência; (2) cancelada depois → a referência já ativa é liberada pelo handler do consumidor; (3) cancelar com outro dono → nada muda.
- Evidência
- o domínio já cancela (
GrydFiles.Core/Entities/StoredFile.cs:954,CancelExpectedReference), mas só aIServerFileStore.ReleaseAsynco alcança (ServerFileStore.cs:272); aIFileServicelibera só porreferenceId(IFileService.cs:99), e a referência esperada não tem um. - Na 10.2.0
ReleaseReferenceAsync(fileId, tenantId, ownerKey)cancela a referência esperada ou libera a ativa.- Bloqueia
- a primeira Requisição com anexo.
- Onde no Nexio
- Anexo · GrydFiles
P12yRe-scan de Failed em processoPendente
- Hoje · 11.1.0
- Entregue no framework. A rota
POST /attachments/{id}/rescané do Nexio. - Por que existe
- os hosts do Nexio não expõem rota do GrydFiles (asserção 8 da Arquitetura), e a tela do Anexo precisa de "verificar de novo" para arquivo em
Failed. - Requisito
- re-scan disponível em processo, com a regra da rota do P12d (
Failedvolta aScanning;Availablegrava a tentativa sem mudar de estado), sob o autor identificado do P12m. - Critério de aceite
POST /attachments/{id}/rescanleva o arquivo deFailedaScanningsem nenhuma rota do GrydFiles exposta.- Evidência
- nem rota nem método de re-scan no código (
GrydFiles.API/Controllers/FilesController.cs— dez rotas;IFileService.cs). - Na 10.2.0
IFileStore<TScope>.RescanAsyncreaproveita o caso de uso da rota. Fechado: o caso de uso exige autor identificado (sem usuário,FILE_CALLER_UNAUTHORIZED); o re-scan automático não passa por ele. A rota/attachments/{id}/rescané do Nexio.- Bloqueia
- a primeira Requisição com anexo.
- Onde no Nexio
- Anexo · GrydFiles · Arquitetura
P12zA documentação do módulo reflete as restriçõesResolvido
- Hoje · 11.1.0
- Entregue. Grep de
IAuditLogServiceemdocs/modules/files: zero ocorrências. - Por que existe
- a documentação do módulo no repositório publica a spec antiga como "GrydFiles — Especificação", com
IAuditLogService.RecordAsync, "três dos quatro caminhos" de expurgo e "a superfície HTTP existe para a SPA"; a referência de API lista as rotas de referência só comupload:files, sem restrição de escopo; e a página de dedupe descreve o que saiu da v1. Quem implementar consumidor pela documentação refaz os ataques de posse, rota de referência e trilha. - Requisito
- a documentação do módulo reflete as restrições das RN-GF-21, 22 (inclusive leitura em processo), 23, 25 e 27 e a trilha da Ata B11 — ou deixa de se apresentar como a especificação e aponta a vigente.
- Critério de aceite
grepna documentação do módulo: zeroIAuditLogService; tabela de rotas com a restrição de escopo e o 403; página de dedupe marcada v2 ou retirada.- Evidência (
e4eb5a4f) docs/modules/files/spec.md(l.127, 476, 493, 599, 707 —IAuditLogServiceem 127, 493 e 707),docs/modules/files/grydfiles-spec.htmlv1.2 (IAuditLogServiceem l.906, 1371, 1558),api-reference.md:32–33,dedupe-and-canonical-resolution.md— a documentação entrou pelo squashd9781a63.- Na 10.2.0
- As restrições estão em
api-reference.md, a página de dedupe saiu e há guia "Upgrading to v10.2.0". Fechado: a menção aIAuditLogServicesaiu da spec e do HTML ("a API antiga de auditoria"); ogrepna documentação do módulo dá zero. - Bloqueia
- antes de a documentação orientar consumidor novo (o Anexo).
- Onde no Nexio
- Anexo · GrydFiles
Origem
Conferência das versões
Comparação feita em 02/11/2026 entre a v1.13 e a v1.15 de grydfiles.html e o código do Gryd.IO. A v1.15 era a mais atual, e a v1.16, vigente, acerta as duas últimas linhas.
| Ponto | v1.13 | v1.15 | Código |
|---|---|---|---|
| Migrations do módulo | cinco | oito | oito (local certa) |
| Permissões de plataforma | três | quatro, com read:product-files | quatro (local certa) |
| Códigos de erro e rotas | 23 e 11 | 23 e 11 | 23 e 11 (as duas certas) |
| Scanner falso | diz que existe | diz que não existe | não existe (local certa) |
IFileScanner registrado direto | escapa da verificação | derruba a subida | derruba, 902ae35e (local certa) |
Host com NullAuditStore | só recusa eliminação e guarda | não sobe | não sobe, FileAuditTrailRequirement (local certa) |
| Histórico de tentativas | paginação em memória | pagina no banco | no banco, EfFileScanAttemptReader (local certa) |
| Arquivo sem tenant na trilha | depende de demanda ao Core | rota de plataforma, por adesão | por adesão (local certa) |
| Como a trilha é preparada | IFileAuditOutbox e consumidor próprio | igual | mudou em 31/10 para IAuditTrail, 1f118f36 (as duas defasadas) |
| Versão citada | v1.13, sem versão do Gryd.IO | Gryd.IO 10.2.0 | 11.1.0. A tag v10.2.0 não existe: esse conteúdo saiu na v11.0.0 (as duas defasadas) |
Método: os 235 identificadores da conformidade da v1.15 (tipos, testes, migrations, códigos de erro e decisões) foram procurados no código, nos testes, na documentação e no backlog do Gryd.IO, e todos existem. Contagens de rotas, códigos e permissões foram conferidas no código. Isso prova que os nomes existem, não que cada regra se comporta como descrito: os testes do Gryd.IO não foram executados.