Plataforma · Gryd.IO

Status do GrydFiles

O que o módulo já entrega, o que está parcial e o que o Nexio ainda precisa fazer para usá-lo.

26 subitens: 14 resolvidos, 3 parciais, 9 pendentes Gryd.IO 11.1.0 · develop 8fd9f172 · 02/11/2026

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.

StatusSignificaSubitensQuantos
ResolvidoEntregue 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, P12z14
ParcialEntregue, com prova ou cobertura incompleta.P12b, P12o, P12p3
PendenteEntregue no framework, mas o Nexio precisa fazer algo específico.P12a, P12c, P12l, P12q, P12r, P12s, P12t, P12w, P12y9
Sem soluçãoAinda não há solução disponível.nenhum0

Planejamento

O que falta no Nexio

AçãoSubitemBloqueia
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 WorkerP12aPrimeira Requisição com anexo
IFileOutcomeHandler do AnexoP12rPrimeira Requisição com anexo
IAuditStore durável nos hosts que compõem o móduloP12sPrimeiro download
clamd na AWS e AddGrydFilesClamAvP12cPrimeira Requisição com anexo
Bucket do GrydFiles com regra de ciclo de vida de _transit/ (expira em um dia)P12oPrimeira Requisição com anexo
GrydAuth:Roles:TenantAssignable:Deny com as permissões do móduloP12lPrimeiro tenant pago
Adaptador de IFileActorDirectoryP12qPrimeira eliminação
Rota POST /attachments/{id}/rescanP12yPrimeira Requisição com anexo
Asserções de arquitetura: consumidores da porta e quem resolve cada escopoP12t, P12wPrimeiro consumidor novo da porta
Adesão à rota de plataforma, só se houver arquivo de produto: AddGrydFilesPlatformOutbox, AddGrydFilesPlatformAudit, AddGrydFilesPlatformOutcomesP12uPrimeiro arquivo sem tenant

Fora dos subitens

Limitações do módulo

PontoStatusSituaçãoO que fazer
Histórico de tentativasParcialA 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 trilhaParcialA 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 trilhaParcialNenhum host roda a limpeza do GrydAudit (backend.md).Decidir quando houver volume.
Papel composto antes do DenyParcialTenantAssignable: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 AddGrydFilesOutbox no host e ter AddOutboxWorker no 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 Failed não chega a ninguém, e o job de varredura lê sem tenant.
Requisito
os quatro desfechos (Failed inclusive) 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
IFileOutcomeHandler não existe; ExpectedOwnerScope/ExpectedOwnerKey (GrydFiles.Core/Entities/StoredFile.cs:261–264) e a porta IServerFileStore já existem; GrydFiles.Infrastructure/Jobs/FileScanJob.cs:107 desliga o filtro de tenant.
Na 10.2.0
Veredito gravado na transação (RecordVerdictStep) e entregue a IFileOutcomeHandler depois do commit. Arquivo sem tenant usa o contrato v2, por adesão: AddGrydFilesPlatformOutcomes e .ProductFileOutcomes<T>(), com destino persistido do dono. Dono legado Unknown nã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
PUT repetido depois de Available → o download entrega os bytes do sha256 gravado.
Evidência
deltas/T8_plataforma.md P2.
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 de AddGrydFilesClamAv.
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 → Infected no CI contra motor real.
Evidência (e4eb5a4f)
o host não sobe sem IFileScanner (GrydFiles/FileScannerRequirement.cs, commit 1edb00d0), mas UseScanner<T>() registra qualquer motor (GrydFiles/GrydFilesBuilder.cs:44–48); o ClamAV entra por AddGrydFilesClamAv; 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 IFileScanner registrado direto: o módulo consome IVerifiedFileScanner, e o motor fica só sob chave, atrás de VerifiedFileScanner (EICAR). Alerta em ScannerHealthMonitorJob. 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 Available do 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.md P4.
Na 10.2.0
POST /files/{id}/rescan, ArchiveRescanJob e ReleasedFileRescan.
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 Scanning sem job; a varredura de consistência é semanal.
Requisito
Scanning sem 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.md P5.
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.md P6.
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, retainUntil vencido 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-reprovado e job: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, originalFileName nulo.
Evidência
deltas/T8_plataforma.md P7, P8 e P18 · GrydFiles.Core/FilePurgeReasons.cs:22–41 (cinco motivos, todos mantêm o nome) · ExpiredUploadSweepJob.cs:171, FilePurger.cs:63 (redactFileName nasce false).
Na 10.2.0
PurgeEligibility é a regra única do expurgo automático; carência e retirada do nome em FilePurgeReasons.
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.md P9.
Na 10.2.0
PUT /files/{id}/retention exige 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.md P4.
Na 10.2.0
IFileStore<TScope>.GetReferencesAsync devolve as referências ativas do próprio escopo, e CheckPurgeAsync responde se a eliminação passaria. O critério de aceite é atendido: a recusa FILE_STILL_REFERENCED_CONFLICT nomeia 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 IFileService nã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}/complete nunca aceita dono. A confirmação é idempotente: repetida com a mesma referência esperada devolve sucesso sem efeito novo; com referência esperada diferente → 409 FILE_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) · CompleteUploadRequest vazio (GrydFiles.Application/Contracts/FileRequests.cs:50) · IFileService.CompleteUploadAsync sem 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 CanRead dele; segundo dono estoura sem código de erro.
Requisito
referência esperada só sobre arquivo em Pending reservado pelo mesmo chamador e no mesmo tenant; referência direta sobre Available só pelo mesmo chamador que reservou ou por propagação a partir de referência ativa do mesmo ownerScope no mesmo arquivo, nomeada pelo chamador. Fora disso 409 FILE_REFERENCE_NOT_ALLOWED_CONFLICT, nunca erro sem código; arquivo de outro tenant continua FILE_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 Available que 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:919 aceita Pending, Scanning e Failed sem conferir quem reservou, e :937 lança InvalidOperationException para segundo dono · GrydFiles.Application/Services/FileService.cs:527 cria referência direta conferindo só Available · o mesmo ataque já está em código num consumidor: GrydNotifications.Application/Features/Attachments/NotificationAttachments.cs:189 chama AddReferenceAsync com o fileId recebido, sem conferir quem o subiu.
Na 10.2.0
StoredFile.MayBeReferencedBy e AddReferenceHandler.
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:Deny antes de haver papel composto, porque a opção não limpa o que já existe.
Por que existe
(1) POST /files/{id}/references aceita ownerScope/ownerKey livres, e DELETE …/references/{referenceId} libera referência de qualquer escopo, só com upload: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:files e purge:files tê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, pela IFileService e pelas rotas, o que o CanRead do dono deveria decidir — inclusive estender guarda e expurgar.
Requisito
(1) as duas rotas só criam e liberam referência de ownerScope que o registro do escopo declara gerido pela superfície HTTP — nenhum, por padrão; escopo de módulo que consome em processo → 403 FILE_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 …/references com ownerScope = nexio.attachment → 403 e nenhuma referência; (b) DELETE de uma referência gryd.report-execution pela 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 (rota api/v{version}/files), :166 e :192 (referências) · FileRequests.cs:60–64 (OwnerScope livre) · FileService.cs AddReferenceAsync ~514 e ReleaseReferenceAsync ~542 (libera pelo referenceId, 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õe GrydFiles.API (asserção 8 da Arquitetura). GrydFiles RN-GF-23.
Na 10.2.0
ReferenceSurfacePolicy e ManagedThroughHttpSurface(); GrydAuth:Roles:TenantAssignable:Deny cobre 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 legalHold e o expurgo manual exigem usuário, e por InvalidOperationException (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/desligar legalHold exigem autor identificado: o usuário do contexto — ou, só para ligar/desligar legalHold e 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 → 401 FILE_CALLER_UNAUTHORIZED antes 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
ReserveAsync e GetDownloadUrlAsync sem 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; GetAsync sem 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 do HttpContext (GrydAuth.Infrastructure/Services/Authentication/CurrentUserService.cs:28) · GrydFiles.Application/Audit/IFileAuditTrail.cs:30 (userId anulável) · FileService.cs ~314, ~728, ~831 (as três exigências por exceção), :724 grava antes de :728–729 lançar; ReserveAsync ~102 e GetDownloadUrlAsync ~403 sem exigência. GrydFiles RN-GF-26.
Na 10.2.0
FileActorPolicy recusa com FILE_CALLER_UNAUTHORIZED antes 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.GetAsync sem 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
CandidateScopes nas varreduras e leitura sob o filtro do tenant; única exceção do módulo: a métrica GetScanQueueAsync.
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 PUT entre o scan positivo e a cópia troca a evidência, e o sha256 da lápide deixa de ser o do conteúdo guardado; (b) o scan não compara os bytes lidos com sizeBytes: onde o provedor não assina tamanho, um PUT maior 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áximo sizeBytes + 1 bytes; leitura acima de sizeBytes, ou que termina antes dele, em arquivo ainda não liberado → Purged com FILE_OBJECT_CHANGED_UNPROCESSABLE e motivo job:objeto-alterado. Divergência é só a confirmada (complemento da T8 — RN-GF-17, RN-GF-18): leitura que passou de sizeBytes, 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 de Available, nunca Purged: o tamanho divergente é incidente de integridade — Available → Infected pelo 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) PUT maior, antes ou depois da confirmação → nunca liberado nem servido, nos três provedores; (2) PUT entre o scan positivo e a cópia → objeto da quarentena com o sha256 gravado, ou cópia recusada e incidente na trilha; (3) re-scan de Available com retainUntil futuro e leitura acima de sizeBytes → Infected, fora do download, incidente na trilha, retainUntil intacto, nenhum Purged; (4) re-scan de Available com a leitura interrompida por erro de E/S → tentativa com erro gravada, arquivo segue Available.
Evidência
GrydFiles.Infrastructure/Jobs/FileScanJob.cs:606 (MoveToQuarantineAsync lê e regrava a chave original) · :287 (BytesRead só vai para a tentativa).
Na 10.2.0
ScanReader, ReadIntegrityStep e FileQuarantine. 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 retainUntil mais tarde; não localizei teste nomeado para estender a data de um arquivo já em Infected.
Por que existe
Available → Infected libera todas as referências, e a varredura expurga a quarentena depois de QuarantineRetentionDays olhando só legalHold — prova sob retainUntil some; e o infectado de documento fechado precisa durar até o fim da guarda do dono.
Requisito
expurgo de Infected só quando valem todas: QuarantineRetentionDays desde o veredito positivo; retainUntil vencido (modo RetainUntil); legalHold desligado; modo diferente de Permanent. A transição libera as referências sem tocar em retentionMode, retainUntil e legalHold. O dono pode estender o retainUntil de arquivo em Infected; vence a data mais tarde.
Critério de aceite
(1) Infected há 400 dias com retainUntil futuro → não expurga; (2) com legalHold → não expurga; (3) re-scan positivo sobre Available com retainUntil futuro → referências liberadas, data intacta; (4) arquivo com retainUntil em 2031 que vira Infected em 2027 continua na quarentena até 2031.
Evidência
GrydFiles.Infrastructure/Jobs/ConsistencySweepJob.cs:199 (IsQuarantineExpired: Infected, sem LegalHold e veredito antes do corte).
Na 10.2.0
PurgeEligibility respeita a guarda, e a quarentena fica sob o piso. Sem teste nomeado para estender retainUntil em Infected.
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 tem purge:files apaga prova sob guarda sem marca; admin:system com purge:files apaga arquivo Permanent com 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 com erase:attachments, revoga o anexo e publica attachment.erased, e o expurgo é pedido depois do commit pelo job, sem usuário. Em Permanent (SIGNED_CONTRACT) o GrydFiles exige admin:system de 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ó aceita Available: o arquivo que o re-scan pôs em quarentena não sai nem a pedido do titular.
Requisito
com retainUntil futuro — ou em Permanent, sempre — o expurgo é recusado com 409 FILE_RETENTION_NOT_EXPIRED_CONFLICT, salvo o ato de eliminação: marcação explícita (erasure) e motivo. Pela rota, com purge:files e, em Permanent, admin:system de pessoa autenticada. Em processo, pelo consumidor registrado para o escopo, com a referência do pedido do titular, sem conferir permissão (P12w) e, em Permanent, 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 → 409 FILE_ERASURE_DUAL_CUSTODY_CONFLICT, nada gravado; admin:system nã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 em purgedBy) 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 → 422 FILE_ERASURE_AUTHOR_INVALID_UNPROCESSABLE, antes de qualquer efeito. Em arquivo de produto o tenant conferido é o de quem pede: arquivo de produto tem tenantId nulo 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 por admin: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 403 FILE_OWNER_SCOPE_FORBIDDEN. Pela rota, arquivo de produto continua exigindo admin:system de pessoa no contexto (emenda à P30 da T8, validação R7). A eliminação alcança Available, Infected e Failed (ET9-3 — a lápide fica como prova; perícia pede guarda legal por caso, que vence o titular); Pending e Scanning → FILE_NOT_AVAILABLE_CONFLICT. Guarda de qualquer detentor → FILE_LEGAL_HOLD_CONFLICT antes 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 → 403 FILE_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 arquivo Permanent — 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 responde FILE_LEGAL_HOLD_CONFLICT devolvendo o estado — os detentores ativos e purgedAt nulo —, o consumidor mostra o pedido como bloqueado, nunca como concluído, e só publica a eliminação quando a resposta traz purgedAt. 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 (503 FILE_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) Permanent com admin:system e 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) Permanent em processo com um autor só, ou dois iguais → 409 FILE_ERASURE_DUAL_CUSTODY_CONFLICT, nada apagado e a referência do par ativa; (8) Permanent em 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 autores Declared, o escopo e o executor; (9) guarda de qualquer detentor — consumidor, outro consumidor ou plataforma → FILE_LEGAL_HOLD_CONFLICT antes 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) Infected em quarentena com ato de eliminação válido → Purged, lápide com a assinatura; Pending → FILE_NOT_AVAILABLE_CONFLICT na 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 → Purged e as duas referências liberadas na mesma transação; a mesma eliminação nomeando um par só → FILE_STILL_REFERENCED_CONFLICT e 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 e purgedAt nulo, ou o registro fica Purged e o pedido de guarda responde FILE_PURGED_CONFLICT — nunca guarda ativa sobre objeto apagado, nunca purgedAt gravado 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 respondeu FILE_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 (tenantId nulo) 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 (Permanent só com admin:system de pessoa), :805 (legalHold como bit), :831 (sem usuário lança InvalidOperationException); expurgo sem olhar retainUntil (~765–850); IFileService sem consulta de pré-condição; PurgeFileRequest sem marcação nem autores (FileRequests.cs ~103) · Core/Gryd.Observability/Extensions/ObservabilityServiceExtensions.cs:36, :65 (TryAddSingleton<IAuditStore>(NullAuditStore.Instance)) · StoredFileConfiguration.cs:113 (só ExpectedOwnerKey como token de concorrência; nem PurgeAsync nem UpdateRetentionAsync travam o registro, e LockedFilePurge existe só no job) · FileReference por (ownerScope, ownerKey), sem caminho de liberação em lote pelo mesmo escopo.
Na 10.2.0
Cadeia de conferências em PurgeLinks, CheckPurgeAsync em dois modos, dois autores em Permanent. A mesma pessoa nos dois papéis agora grava stored_file.erasure_refused (DEC-GF-19-11). Repetir eliminação já feita responde FILE_PURGED_CONFLICT, com purgedAt, motivo e rejectionCode, 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:system virou purge-permanent:files e purge:product-files; sem IFileActorDirectory no 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 Permanent a 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 (ownerScope e ownerKey da referência esperada, ou de cada referência liberada). O handler roda com esse tenant restaurado, sem usuário, relê por GetAsync (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) Infected sobre 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 (MarkInfected descarta a referência esperada), ~1156 (lápide); não há registro de veredito a entregar em src/.
Na 10.2.0
FileOutcomeRecord guarda tenant e dono; em v2, FileOutcomeRecordV2 separa 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 IAuditStore durá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 (NullAuditStore por 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 responde FILE_AUDIT_TRAIL_UNAVAILABLE. Do Nexio: registrar AddGrydAudit nos 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 pela IServerFileStore sem ser declarado.
Requisito
asserção, no repositório do Gryd.IO, de que os assemblies que dependem de IServerFileStore são exatamente os consumidores declarados (hoje GrydReports.Application, GrydReports.Infrastructure, GrydNotifications.Application e GrydNotifications.Infrastructure) e de que nenhuma action do GrydFiles.API recebe 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 do GrydFiles.API que devolve Stream → o teste falha.
Evidência
ServerContentStaysOffTheSurfaceTests (só o GrydFiles.API) · DELETE /reports/{id} chega a ReleaseAsync (ReportsController.cs ~155 → DeleteReportCommandHandler.cs ~61). O Nexio usa a porta: a planilha de importação processada por job lê pela OpenReadAsync o arquivo já liberado (GrydFiles §Endpoints), com o par da referência ativa do próprio consumidor (P12v); o Anexo não usa — passa pela IFileService. 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
DeclaredServerPortConsumers lista 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:system de 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 (pelo ownerScope) 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 exige admin:system; retirar sem guarda própria ativa → nenhum efeito. Pela rota, legalHold: false retira só a da plataforma, com admin:system; sobre arquivo cuja guarda restante é só de consumidor → 403 FILE_OWNER_SCOPE_FORBIDDEN. Resposta e recusa de expurgo listam os detentores; FILE_LEGAL_HOLD_CONFLICT deixa 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, no Declared, o escopo que declarou e o executor de serviço; purgedBy nulo 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 como Authenticated. (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: false com admin:system sobre 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 com Declared, o autor, o escopo e o executor, nenhum 401; expurgo pela rota → Authenticated; job de retenção → Job, purgedBy nulo; (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/ReleaseLegalHold sem detentor), :272 (PurgedBy sem 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 exige admin:system), :724 grava a mudança e :732 a trilha depois, :850 e :852 idem no expurgo · UpdateFileRetentionRequest só com RetainUntil e LegalHold (FileRequests.cs:82–92), sem detentor, motivo nem autor.
Na 10.2.0
FileLegalHold, por detentor, e PlaceLegalHoldAsync/ReleaseLegalHoldAsync. Em arquivo sem tenant a linha vai na transação, com AddGrydFilesPlatformOutbox. release-hold:files substitui admin: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 Available do tenant. Sem persistência de notificação, o GrydNotifications anexa sem criar referência e o envio lê os bytes pela porta: POST /notifications/email com o fileId de um arquivo alheio manda o conteúdo por e-mail — exfiltração por fileId, sem que a posse do P12k seja consultada.
Requisito
OpenReadAsync recebe 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, 409 FILE_REFERENCE_NOT_ALLOWED_CONFLICT e 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) OpenReadAsync com 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 (OpenReadAsync sem 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:files o autoriza — mas o GrydFiles não define identidade de serviço e a IFileService não confere permissão de módulo; e qualquer assembly do processo que passe o escopo nexio.attachment retira 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 IFileService e a IServerFileStore recebem 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 o IServerFileStore.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, a IFileService nã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:system só de pessoa autenticada, só onde a spec o nomeia. Todo ownerScope que chega à IFileService ou à IServerFileStore — referência esperada, referência direta, guarda, eliminação — só é aceito do consumidor registrado para ele; escopo sem registro → 403 FILE_OWNER_SCOPE_FORBIDDEN antes 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 passou nexio.attachment — a prova antiga era inverificável. E resolver não é só injetar: chave de texto, Type montado 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 e Type montado 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:files em lugar nenhum → aceita; a mesma pela rota, por usuário sem purge:files → 403; (6) assembly fora do registro que chama a IFileService com nexio.attachment → o teste de arquitetura falha; chamada com ownerScope sem registro → 403 e nada gravado; (7) assembly fora do registro que resolve a porta de nexio.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_FOUND e 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 à IFileService com 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 por Type montado em execução; e pedir a porta de um escopo registrado por Type montado 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, :303 e HangfireJobActivator.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, OpenReadAsync e ReleaseAsync (GrydFiles.Application/InProcess/IServerFileStore.cs:57, 72, 85) — sem referência de cópia, retenção nem legalHold; o serviço lê o usuário do HTTP (CurrentUserService.cs:28) · purge:files só na policy do controller (GrydFiles.API/Controllers/FilesController.cs:241–242); FileService.cs confere só admin:system (:149, :680, :687, :791) · o escopo recebido é aceito sem registro por consumidor (FileService.cs:535) · NotificationFiles.cs:20 e ReportFiles.cs:26 (escopo como constante que o compilador embute) · EfStoredFileStore.cs:177–186 (InTenant com IgnoreQueryFilters() sobre o tenant do parâmetro) e GrydFilesEmailAttachmentReader.cs:34.
Na 10.2.0
IFileStore<TScope> e IServerFileStore<TScope> amarrados ao escopo, FileScopeGate, autor declarado marcado, NamedTenantScope. Fechado: GetAsync, AddReferenceAsync e UpdateRetentionAsync aceitam o tenant nomeado (aditivo, por NamedTenantScope.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 o OpenReadAsync como 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 IFileService cancela 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ó a IServerFileStore.ReleaseAsync o alcança (ServerFileStore.cs:272); a IFileService libera só por referenceId (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 (Failed volta a Scanning; Available grava a tentativa sem mudar de estado), sob o autor identificado do P12m.
Critério de aceite
POST /attachments/{id}/rescan leva o arquivo de Failed a Scanning sem 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>.RescanAsync reaproveita 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 IAuditLogService em docs/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ó com upload: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
grep na documentação do módulo: zero IAuditLogService; 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 — IAuditLogService em 127, 493 e 707), docs/modules/files/grydfiles-spec.html v1.2 (IAuditLogService em l.906, 1371, 1558), api-reference.md:32–33, dedupe-and-canonical-resolution.md — a documentação entrou pelo squash d9781a63.
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 a IAuditLogService saiu da spec e do HTML ("a API antiga de auditoria"); o grep na 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.

Pontov1.13v1.15Código
Migrations do módulocincooitooito (local certa)
Permissões de plataformatrêsquatro, com read:product-filesquatro (local certa)
Códigos de erro e rotas23 e 1123 e 1123 e 11 (as duas certas)
Scanner falsodiz que existediz que não existenão existe (local certa)
IFileScanner registrado diretoescapa da verificaçãoderruba a subidaderruba, 902ae35e (local certa)
Host com NullAuditStoresó recusa eliminação e guardanão sobenão sobe, FileAuditTrailRequirement (local certa)
Histórico de tentativaspaginação em memóriapagina no bancono banco, EfFileScanAttemptReader (local certa)
Arquivo sem tenant na trilhadepende de demanda ao Corerota de plataforma, por adesãopor adesão (local certa)
Como a trilha é preparadaIFileAuditOutbox e consumidor próprioigualmudou em 31/10 para IAuditTrail, 1f118f36 (as duas defasadas)
Versão citadav1.13, sem versão do Gryd.IOGryd.IO 10.2.011.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.