Pular para conteúdo

v0.7.0 — seis achados de segurança fechados, o desligamento que passa a existir, e o corte da coleta que congelava quadros

Decidida por: papel de Product Owner, em 2026-09-09 — a confirmar pela pessoa alocada (sro.product_owner). Aceitação é ato do papel; este arquivo é proposta até a confirmação. Autorização para preparar: pessoa mantenedora, em 2026-09-09. PR de release: ainda não aberto — ver Pré-condições do delivery. Base: origin/main em 43cb7c0 (v0.6.0) → origin/development em 4253bbc. Aceitação que a sustenta: este arquivo, seção Veredito por entregável.


⚠️ O que mudou DEPOIS desta avaliação, e a reavaliação que ele exige

O papel Product Owner avaliou sobre 4253bbc e recusou três entregáveis. Duas das recusas eram defeitos, e foram consertadas no PR #850 — mergeado depois da avaliação. Os fatos que sustentavam aquelas recusas deixaram de ser verdade, e o veredito precisa ser refeito por quem desempenha o papel, não por quem consertou.

recusa o que a sustentava estado agora
D07 — H4 painel_recusado/4 e espera_acionada/3 escritas, documentadas e com zero call sites ligadas, com teste, e a prova de que ele pega: removidas as chamadas, 7 de 9 reprovam
D05 — H6 três registros afirmando o contrário do código, incluindo o cabeçalho de access.ex os três corrigidos, e nenhum apagado — a FR-012j ganhou a nota do vaivém que ninguém saberia
D06 — conta desativada falta razão ao desativar, enable_user/2 sem ator, tela sem protótipo aprovado continua recusada — é desenho, não defeito

E a dispensa da página de anúncio deixou de ser pedida. O papel nomeou o custo dela com precisão: "quem perder um painel encontrará uma recusa e nenhuma explicação de que a regra mudou". A pessoa mantenedora decidiu pagar o custo em vez de dispensar a regra — a recusa do painel e as duas listas filtradas passaram a dizer o que mudou e quando, com teste de que a ressalva não aparece para quem alcança tudo.

Não é a página que a regra pede, e não substitui o item de backlog. É a explicação onde o custo acontece, que era o que a regra existia para cobrir nesta release.

O que continua valendo desta avaliação: a versão (0.7.0, MINOR), a razão de não ser 1.0.0, a recusa da D06, a recusa da 060/US3, e a lacuna de revisão — 21 de 21 PRs sem revisor pedido.


A ordem foi corrigida, e é a primeira coisa a dizer. A v0.6.0 mergeou o PR de release carregando vereditos de dois dias antes, e a reavaliação posterior "só pôde melhorar o registro porque a evidência calhou de ser favorável". Aqui a aceitação vem antes: os vereditos abaixo foram derivados em 2026-09-09, sobre 4253bbc, com mix gates executado por mim sobre essa árvore, e nenhum PR de release existe ainda. Se a confirmação do papel recusar algo, recusa antes de produção mudar, que é para isso que o passo existe.

A lacuna do registro de aceitação, dita antes de qualquer veredito. O modelo da skill manda escrever docs/sprints/NNN/aceitacao.md. Não existe, e não vou inventar um número de sprint: o trabalho desta janela não teve sprint backlog — nasceu de uma avaliação de segurança feita e mergeada no mesmo dia. O último aceitacao.md desta casa continua sendo o do sprint 026. Enquanto isso não se corrigir, a avaliação critério a critério mora aqui — como na v0.6.0 — e qualquer coluna Aceito que venha a ser escrita é derivada desta seção, nunca o contrário.


Por que 0.7.0 — e por que não 0.6.1 nem 1.0.0

MINOR. A release carrega, em maioria, conserto de defeito — o que sugeriria PATCH —, e não é PATCH, porque PATCH é conserto sem mudar o prometido e aqui o prometido muda em cinco pontos observáveis. Percorro os cinco, porque é neles que a decisão se ganha ou se perde, e dois deles retiram alcance de quem o tinha:

# O que muda para quem usa Ganha ou perde Onde
1 /accounts ganha um ato que não existia: desativar uma conta, e reativar. Antes, desligar alguém era reiniciar a senha e não entregar a temporária — implícito, indistinguível de um reinício legítimo ganha — capacidade nova #844
2 conta de organização suspensa deixa de entrar. tenants.status existia e ninguém o lia; marcar "suspended" não impedia nada perde — quem estava em tenant suspenso e entrava, não entra mais #837
3 escopo de projeto deixa de abrir painel de pessoa. Quem tinha escopo de um projeto lia o painel completo de qualquer pessoa cuja equipe tocasse aquele projeto — incluindo o trabalho dela em outros projetos perde — e é a mudança de alcance mais larga da release #846
4 administração do próprio tenant passa a abrir painel de pessoa — emenda da FR-022 da spec 045 ganha #843
5 as duas rotas de agregado por pessoa nomeada passam a filtrar pelo alcance: /work/verifications/people e /process deixam de mostrar o login de quem a conta não alcança perde — para conta sem escopo #838, #845

Uma release que retira alcance de três maneiras não é PATCH. Alguém que ontem abria um painel e hoje não abre não recebeu um conserto: recebeu uma regra diferente. Que a regra anterior fosse um defeito não muda o que a pessoa observa — muda apenas quem está certo. E o item 1 sozinho já basta para MINOR: é capacidade nova na tela, e MINOR é entrega user story nova.

Sobre 1.0.0, explicitamente

MAJOR aqui seria 1.0.0, e em semver 1.0.0 declara que o contrato público é estável. Esta release não pode fazer essa declaração, e as razões são de fato, não de gosto:

  1. o contrato público não existe como código. A API pública é a spec 061, mergeada nesta janela como texto. Não há uma linha de implementação, e as telas dela (US1 e US3) não têm protótipo — pela regra desta casa não podem sequer ser decompostas em tarefas. Declarar estável um contrato que não foi escrito seria dizer do produto algo que não é verdade;
  2. a plataforma continua sem saber qual versão está servindo. Conferido de novo em 2026-09-09: nenhuma leitura de Application.spec(:the_band, :vsn), nenhuma rota de versão, a versão em tela nenhuma. Um produto que não consegue responder "que versão sou eu?" não está em condição de prometer estabilidade de versão;
  3. dez dos dezesseis achados de segurança continuam abertos, e dois deles — H7 e H8 — são sobre não se conseguir dizer qual imagem está rodando;
  4. o §6 do runbook — o backup restaurado de verdade — nunca aconteceu, desde a v0.1.0. A 050/US2 segue não aceita;
  5. nenhum dos 21 PRs desta janela tem revisão registrada nem pedido de revisão (medido abaixo). 1.0.0 sobre código que ninguém releu seria o número mais caro já escrito neste repositório.

Semver §4 é o texto que autoriza o que estou fazendo: em 0.y.z o contrato público não é estável, e mudança incompatível cabe no MINOR. É onde este produto está, e a versão diz isso.

E por que não 0.6.1 mesmo sendo, em volume, uma release de segurança

Porque a métrica de semver não é de onde o trabalho veio, é o que o artefato promete a quem usa. Seis achados de segurança fechados poderiam caber em PATCH se todos fossem invisíveis — e três deles são visíveis, e um deles acrescenta um botão à tela. Numerar isto 0.6.1 diria a quem atualiza "nada mudou para você", e mudou.


O que embarca — medido, não lembrado

$ git rev-list --count origin/main..origin/development
30

30 commits e 21 pull requests: #829 a #849, todos com base=development, contados contra a origem em 2026-09-09T23:19Z.

Correção ao que a autorização desta release supunha. O pedido descrevia "da ordem de 33 commits" e listava o #849 (H4) como já fechado. Quando comecei a medir, o #849 estava OPEN e o H4 não estava na development — grep por access_event em lib/, priv/repo/migrations/ e test/ voltava vazio. Ele foi mergeado às 23:09Z, durante esta avaliação, e por isso entra. Registro a sequência em vez de escrever a nota como se o número sempre tivesse sido 21: contar de memória é o que produz release que anuncia o que não carrega.

Os seis achados de segurança fechados

# O que era PR Veredito
H1 POST /set-password trocava a senha sem exigir a atual — acesso temporário virava posse da conta #835 aceito
H12 deps commitada como link simbólico absoluto para si mesma; apagava as dependências a cada git checkout #836 aceito
H3-A tenants.status existia e ninguém o lia — organização suspensa autenticava #837 aceito
H2 o veredito de acesso valia em 2 de 26 LiveViews; o inventário e o teste que enumera #838, #845 aceito
H6 pode_ver_equipe/3 concedia ao admin e pode_ver/3 não — a plataforma afirmava um regime que não aplicava #843 NÃO aceito
H3-B não existia estado de conta desativada; o desligamento era implícito #844 NÃO aceito
H4 nenhum evento de acesso era registrado — e o sucesso apagava o rastro de tentativa falha #849 NÃO aceito

São sete linhas para seis achados, porque o H3 tem duas partes com custos diferentes e uma foi aceita e a outra não.

O resto

O que PR Muda produção?
escopo de projeto deixa de abrir painel de pessoa #846 sim — retira alcance
o corte da coleta usa atividade de issue, e não push de código — e repagina todos os repositórios uma vez #847 sim
o gate do tipo de merge — o que o PR declara passa a ser conferido #834 não
specs 061 (API pública), 062 (servidor MCP) e 063 (issue ausente da origem), e a ADR 0009 #830, #832, #848 não — texto, sem código
a avaliação de segurança de 16 achados, e o inventário do H2 #841, #845 não
a regra da versão na página, e o item de backlog dela #829 não
o site em três endereços (/developers/), o RETOMAR unificado, a nota da v0.6.0 reavaliada, o item de investigação do estado divergente #833, #840, #842, #839 não
back-merge da v0.6.0 #831 não

Veredito por entregável

A classificação decorre dos critérios, nunca do merge e nunca da marcação em tasks.md (sro.rule03.deliverable_acceptance_by_criteria).

De onde saem os critérios, e é preciso dizer porque nenhum destes entregáveis materializa user story de spec.md. Os consertos de segurança respondem a docs/seguranca/2026-09-09-o-que-consertar-agora.md, que traz, por achado, o conserto mínimo e o cenário para o QA — e é contra esses dois que avalio, mais as FRs da spec 045 onde a decisão da pessoa mantenedora virou requisito (FR-022 emendada, FR-024 nova) e os critérios escritos em docs/backlog/conta-desativada.md.

O que conta como evidência aqui: asserção nomeada no repositório, executada por mim, e o veredito de mix gates lido do código de saída. O que não conta: a afirmação do PR sobre o próprio resultado.

A execução que sustenta todos os vereditos

Rodada por mim, sobre 4253bbc, em 2026-09-09:

$ mix gates
...
16 gates verdes.
Result: 2014 passed (4 doctests, 2010 tests)
EXIT=0

E, antes do #849 entrar, os arquivos de segurança isolados:

$ mix test test/the_band_web/live/login_test.exs \
           test/the_band/tenants/conta_desativada_test.exs \
           test/the_band/tenants/organizacao_suspensa_test.exs \
           test/the_band_web/live/h2_veredito_nas_rotas_test.exs \
           test/the_band_web/live/h2_paridade_das_rotas_test.exs \
           test/the_band/tenants/access_test.exs
Result: 55 passed
EXIT=0

O gate 8/16 é novo — "sem link simbólico rastreado" —, e é a metade do conserto do H12.


D01 — /set-password recusa conta em regime normal (H1) · ACEITO

Produzido por: #835

Critério (cenário do QA, §H1) Tipo Conforme Evidência
conta com must_change_password falso — e o teste assere esse falso, senão mede o fluxo legítimo funcional sim test/the_band_web/live/login_test.exs:112 — refute ctx.member.must_change_password com a razão escrita
o POST /set-password é recusado, sessão derrubada, destino /sign-in funcional sim login_test.exs:130 — assert redirected_to(conn) == ~p"/sign-in"
refute que a senha nova autentica funcional sim login_test.exs:138 — {:error, :invalid_credentials} para a senha nova
assert que a senha original continua autenticando funcional sim login_test.exs:142 — "a senha original deixou de valer — a conta foi tomada"
o teste-par: com must_change_password verdadeiro, o POST funciona funcional sim login_test.exs:146, com a guarda assert recarregada.must_change_password (:154) antes
a guarda vai no controller, e Auth.set_password/3 não muda (seeds e fixtures) não funcional sim a suíte inteira verde (2 014), e seeds.exs não é tocado pelo diff

Fase derivada: sro.accepted_deliverable — seis critérios conformes, todos com asserção nomeada e executada.

A decisão de produto que o achado deixou aberta, e quem a tomou. O §H1 dizia: "se a recusa deve derrubar a sessão ou mandar a pessoa para /profile com uma frase — Product Owner e Design decidem". Foi decidida na implementação, pela opção segura, com a razão escrita dentro do próprio teste: "redirecionar para /profile daria a quem chegou com a sessão de outra pessoa uma dica do que tentar em seguida". Endosso a escolha — é a correta —, e registro que ela não passou por este papel antes de virar código. Fica para confirmação junto do resto.


Produzido por: #836

Critério (conserto mínimo, §H12) Tipo Conforme Evidência
git rm --cached deps — a entrada sai do índice funcional sim git ls-files deps devolve vazio em 4253bbc
um gate que reprove link simbólico apontando para dentro da própria árvore não funcional sim gate 8/16 — "sem link simbólico rastreado", verde na minha execução; o alvo é o modo 120000, e não o conteúdo
a exceção, se algum dia for precisa, é declarada com motivo e não é o gate desligado não funcional sim escrito no @doc de sem_link_simbolico/0

Fase derivada: sro.accepted_deliverable.

Por que este é o único achado que não estava em produção, e mesmo assim contava. deps não existe em main — chegaria lá neste release. O dano medido não era de produção: era perda de estado de trabalho a cada git checkout, seis vezes num dia, com a mensagem mandando rodar mix deps.get sem dizer por quê. É reprodutibilidade, e a ligação entre commit e imagem é o que o H7 também pede.

Um resíduo que encontrei ao preparar esta release, e que o conserto não fecha. Medido no worktree em que trabalhei, onde deps é um link para outra árvore:

$ ls -ld deps
lrwxr-xr-x  deps -> /Users/paulossjunior/projects/theband/deps

$ git check-ignore -v deps ; echo $?
1                      # NÃO está ignorado

$ grep -n deps .gitignore
8:/deps/

/deps/ termina em barra, e padrão terminado em barra só casa com DIRETÓRIO. Um link simbólico chamado deps não é diretório para o git, e portanto não é ignorado — ele aparece como não rastreado, e um git add -A o põe de volta no índice. É, com precisão, o mecanismo pelo qual o H12 nasceu: a regra que devia impedir a entrada nunca a impediu.

Isto não altera o veredito, e digo por quê em vez de deixar a conclusão implícita: os dois critérios do conserto mínimo eram tirar do índice e ter um gate que reprove, e os dois estão conformes — o gate pega o caso. O que o resíduo muda é quando se descobre: no mix gates, e não no git add. A defesa existe e é de segunda linha.

O que o fecha: trocar /deps/ por /deps no .gitignore — sem a barra, o padrão casa com o link também. Uma linha, e vai para o backlog junto do N1 e do N2.


D03 — tenants.status passa a ser lido (H3-A) · ACEITO

Produzido por: #837

Critério (cenário do QA, §H3 (a) e (c)) Tipo Conforme Evidência
tenant "suspended" não autentica funcional sim organizacao_suspensa_test.exs:54
a recusa é byte-idêntica à de senha errada e à de conta inexistente — motivo novo seria enumeração não funcional sim organizacao_suspensa_test.exs:65 — assert suspensa == senha_errada e == conta_inexistente, as três {:error, :invalid_credentials}
a guarda: a conta autenticava antes da suspensão, senão o refute passaria por senha errada funcional sim organizacao_suspensa_test.exs:55 — assert {:ok, _} antes de suspender, com a razão escrita
a sessão viva cai — e não só no LiveView: o plug também funcional sim :98 (hook) e :114 ("e a requisição HTTP também — o plug, e não só a hook")
dois tenants povoados: nada muda para quem não foi suspenso não funcional sim :127
não registra tentativa falha — a credencial pode estar correta funcional sim :82

Fase derivada: sro.accepted_deliverable — seis critérios conformes.

O que ficou fora do conserto e não é falha dele: não há tela para suspender uma organização. Marcar tenants.status continua sendo ato de banco. O achado pedia "ler a coluna, ou removê-la", e ler foi o escolhido; a tela não estava no conserto mínimo. Fica nomeado, não escondido.


D04 — o veredito nas rotas de pessoa nomeada, o inventário e o teste que enumera (H2) · ACEITO

Produzido por: #838 e #845

Critério (§H2, "o que fecha o H2") Tipo Conforme Evidência
1. decisão da pessoa mantenedora registrada como FR numerada funcional sim FR-024 em specs/045-autenticacao-e-acesso/spec.md, com as três naturezas decididas — agregado segue o veredito; atribuição e diretório não
2. o inventário, para a decisão ser tomada sobre o que existe e não sobre a ideia geral funcional sim docs/seguranca/2026-09-09-inventario-do-h2.md — 20 rotas de :autenticado, seis falsos positivos separados por leitura, cinco rotas reais em três naturezas
3. teste de paridade que enumera as rotas e exige que todas respondam igual não funcional sim h2_paridade_das_rotas_test.exs:137 — "toda rota com ranking esconde o login de quem a conta não alcança"
a guarda: a conta estranha é de facto recusada pelo veredito, e o ranking de facto tem a pessoa alvo funcional sim h2_paridade_das_rotas_test.exs:130; h2_veredito_nas_rotas_test.exs:81 e :88 — "senão a lista vazia não prova nada"
o par: a administração continua vendo, e o conserto não fechou demais funcional sim h2_paridade_das_rotas_test.exs:162; h2_veredito_nas_rotas_test.exs:112 e :168
a recusa não distingue "existe e você não alcança" de "não existe" não funcional sim h2_veredito_nas_rotas_test.exs:150
a pessoa vê o seu — a segunda metade da decisão funcional sim h2_veredito_nas_rotas_test.exs:122

Fase derivada: sro.accepted_deliverable — sete critérios conformes.

O que o item 3 muda, e é por que aceito com convicção. O H2 não foi uma tela esquecida: foi um regime herdado por omissão — a spec 023 registra que "toda pessoa autenticada vê qualquer outra" valia porque "o roteador exigia require_user e nada além", e cada seção construída depois herdou isso sem ninguém decidir. Consertar as cinco rotas de hoje não impede a de amanhã; o teste que enumera impede. É a diferença entre corrigir um defeito e fechar a classe dele.

O risco que a FR-024 aceita, declarado e não mitigado: com acesso a muitos itens, alguém reconstrói por acumulação o agregado que o veredito recusa direto. A própria FR diz que o caminho é o H4 (registro de acesso) mais limite de taxa — e o H4 desta release não está aceito (D07). O risco continua aceito, com a mitigação declarada ainda mais distante do que a FR supunha.


D05 — administração alcança pessoa, e a FR-022 emendada (H6) · NÃO ACEITO

Produzido por: #843

O conserto mínimo do §H6 é explícito, e é de registro, não de implementação: "três registros, nenhuma implementação".

Critério (conserto mínimo, §H6) Tipo Conforme Evidência
1. decisão da pessoa mantenedora: administrar concede visão, sim ou não? funcional sim sim, registrada como emenda da FR-022 em specs/045-autenticacao-e-acesso/spec.md, com as duas saídas medidas e apresentadas antes da escolha
2. correção do cabeçalho de access.ex (a resposta foi sim, logo o cabeçalho está errado) funcional NÃO lib/the_band/tenants/access.ex:20-22 continua dizendo: "Administrar não é ver (FR-022). Nenhum ramo aqui olha users.role para conceder visão." — e pode_ver/3 passou a ter o ramo User.admin?(user) and user.tenant_id == tenant.id -> {:ok, :admin}
3. marca de revogação em specs/023-painel-da-pessoa/spec.md:271 funcional NÃO a FR-012j continua sem marca: "Quem tem admin de plataforma MUST ver todos os painéis do tenant. Decisão da pessoa mantenedora em 2026-08-27", sem nenhuma nota de que a 045 FR-022 a revogou e a emenda de 2026-09-09 a restaurou
o comportamento novo tem asserção, e o par de outro tenant funcional sim access_test.exs:183 — "admin do PRÓPRIO tenant vê painel — FR-022 emendada em 2026-09-09" — e :201 — "administração de OUTRO tenant não vê nada"
a spec 045, US2, deixa de afirmar o regime antigo funcional NÃO o texto da US2 continua: "Ser administrador não abre painel nenhum; quem administra e também precisa ver recebe organization por concessão" — o oposto do que a FR-022 emendada manda, na mesma spec

Fase derivada: sro.not_accepted_deliverable — falha em três critérios, dois deles do próprio conserto mínimo.

Por que recuso isto, e não o trato como detalhe de documentação. O achado H6 não era um defeito de código: era a plataforma afirmar um regime que não aplicava. A consequência que o §H6 escreveu é literal — "três documentos afirmam três coisas sobre a mesma pergunta, e a próxima pessoa a escrever código de acesso vai implementar a que ler primeiro".

O #843 mudou o código e deixou os três documentos como estavam. O resultado é que a contradição não diminuiu: ela mudou de lugar e piorou de forma. Antes, o cabeçalho de access.ex era desmentido por uma função (pode_ver_equipe/3); agora é desmentido por duas, e uma delas é a que o cabeçalho nomeia. Um conserto cujo objeto era a divergência entre afirmação e prática, entregue deixando a afirmação intacta, não fecha o achado — troca o lado do qual ele se lê.

O que o fecha, e é barato: três edições de texto — o @moduledoc de access.ex, a FR-012j da spec 023 com a marca de revogação-e-restauração datada, e o parágrafo da US2 da spec 045. Nenhuma linha de código.

Destino da mudança: volta ao product backlog, com prioridade alta — é o item mais barato da fila, e o mais caro se ficar. O comportamento novo embarca sem aceitação e está nomeado em O que embarca sem aceitação.


D06 — a conta desativada (H3-B) · NÃO ACEITO

Produzido por: #844 Migração: 20260909180000_conta_desativada.exs

Os critérios estão escritos em docs/backlog/conta-desativada.md, seção "Critérios que a spec vai precisar carregar" — escritos por este papel, antes do código, exatamente para não nascerem no fim.

Critério Tipo Conforme Evidência
conta desativada não autentica por senha, e a recusa é a mensagem única, sem vazar o estado da conta funcional sim conta_desativada_test.exs:53 — "não autentica, e a recusa é IDÊNTICA"
conta desativada não autentica por token funcional n/a o token não existe (spec 061 sem código). Não é conformidade — é critério que não se pode avaliar ainda
desativar gira o token de sessão, e a sessão viva morre no ato funcional sim :90 (LiveView cai, e a linha não é apagada) e :112 ("e a requisição HTTP também — o plug")
o registro diz quem desativou, quando e POR QUÊ; nada é apagado funcional NÃO a migração acrescenta disabled_at e disabled_by_user_id. Não há campo de razão, e disable_user/3 não recebe nenhuma. Quem e quando: sim. Por quê: não existe
a lista de contas distingue desativada de temporária pendente de sem senha, e a distinção sobrevive sem cor não funcional sim :184 — "o estado DESATIVADA é distinguível de temporária pendente"; a marca é asserida em texto (badge com a palavra desativada), não por classe
reativar é ato registrado, com autor e razão funcional NÃO enable_user/2 recebe tenant e user_id apenas — sem ator, sem razão. reativar_changeset/1 não tem argumento. A reativação não deixa registro de quem a fez, e a desativação deixa
o roster, as medidas e o histórico da pessoa não mudam ao desativar a conta não funcional sem evidência nenhum teste compara medida da pessoa antes e depois de desativar a conta dela. É o critério mais fácil de assumir e o mais caro se estiver errado
Parte C — revoke link não é apresentado como desligamento: o texto diz que desfaz a ligação e que não remove acesso funcional NÃO accounts_live/index.ex:302 continua: "Revogar a associação? A pessoa deixa de entrar pelo username do GitHub; a história do elo fica." — não diz que não remove acesso, que era a metade do critério
Parte C — teste que reprova se revogar o elo voltar a ser o único ato oferecido a quem desliga não funcional NÃO não existe
desligar acesso tem ato próprio, nomeado funcional sim o botão Desativar, com confirmação que diz o efeito
não se desativa a própria conta; nem conta de outro tenant; desativar duas vezes é recusado com motivo próprio funcional sim :121, :130, :143
reativar não devolve a senha funcional sim :152 e :166

Fase derivada: sro.not_accepted_deliverable — quatro critérios não conformes e um sem evidência.

E há três coisas fora da tabela que sozinhas já impediriam a aceitação:

  1. A tela mudou sem protótipo aprovado. O próprio item de backlog tem uma seção intitulada "Tem tela, logo tem protótipo", e ela diz: "o Design desenha antes: o quarto estado (ou a coluna nova), o diálogo que pede a razão, e como o desligado se distingue do recém-criado sem depender de cor". Não existe specs/045-autenticacao-e-acesso/prototipo/. A régua da aceitação de tela desta casa é a seção estrutura aprovada do PROMPT.md, e aqui não há PROMPT.md. Sem régua não há conferência, e "ajustou na implementação" é recusa.

O diálogo que pede a razão aparece na frase acima como parte do desenho pedido, e é o mesmo campo que a tabela marca como ausente. As duas lacunas são a mesma lacuna.

  1. Quatro decisões que este papel declarou pré-requisito de tarefa foram tomadas pelo código. O item diz, textualmente: "Não decomponho antes destas respostas — decidir depois de construir é o que produz retrabalho." A decisão 1 recomendava relator próprio, com a razão de que "um disabled_at solto repete o connected_tools.status da ADR 0004 D7, que já é dívida declarada"; o implementado foi disabled_at na própria users. Não há registro de a pessoa mantenedora ter escolhido a outra opção — há o código tendo escolhido.

  2. sro.rule01: o entregável materializa trabalho que não estava em sprint backlog nenhum. Não há spec, não há tarefa, não há issue — o próprio PR declara "Sem issue: lacuna declarada". Escopo que entra sem passar pelo planejamento é o mesmo achado que a v0.6.0 registrou para as US7 e US8, e reincidir com uma migração de esquema junto é pior.

O que a torna aceitável, e é pouco trabalho para muito registro: o campo de razão em desativar e em reativar, o ator na reativação, o teste que compara as medidas da pessoa antes e depois, o texto do revoke dizendo que não remove acesso, e o protótipo publicado com as decisões — mais o registro das quatro respostas da pessoa mantenedora.

Destino da user story: volta ao product backlog, e com spec, porque é isso que faltou. docs/backlog/conta-desativada.md continua sendo o item; a diferença é que agora ele tem código em produção sem aceitação, e o registro diz isso.

A migração, essa sim, está em ordem — e registro separado porque não quero que a recusa contamine o que foi bem feito: 20260909180000 é só-acréscimo de colunas nulas, sem backfill e sem CHECK, e foi ensaiada contra cópia do banco real (docs/producao/ensaio-2026-09-09-migracao-conta-desativada.md): 4 971 issues e 90 vínculos intactos, duas colunas anuláveis, zero contas desativadas por acidente. E o ensaio pegou um defeito no próprio ensaio — a primeira tentativa imprimiu == Migrated sobre o banco errado, porque config/dev.exs ignora DATABASE_URL. Sem o passo de verificação, o documento teria dito "ensaio aprovado" sobre uma execução que não mediu nada.


D07 — os eventos de acesso (H4) · NÃO ACEITO

Produzido por: #849

O §H4 enumera oito eventos que não eram registrados. É contra essa lista que avalio, mais o cenário do QA.

Evento que o §H4 nomeia Registrado? Evidência
entrada aceita sim auth.ex:176 → AccessEvents.entrada_aceita/3
entrada recusada sim auth.ex:65 → AccessEvents.entrada_recusada/3
queda de sessão por token divergente sim current_scope.ex:82 e hooks.ex:69 → sessao_derrubada/3
desaceleração acionada ({:throttled, s}) NÃO AccessEvents.espera_acionada/3 existe e não tem nenhum chamador: grep espera_acionada lib/ test/ devolve só a @spec e o def
recusa de acesso a painel NÃO AccessEvents.painel_recusado/4 existe e não tem nenhum chamador: idem. lib/the_band/tenants/access.ex não referencia AccessEvents uma única vez
concessão de escopo criada ou revogada NÃO declarado pelo próprio PR como fora do escopo
elo declarado ou revogado NÃO idem
troca de senha NÃO session_controller.ex não referencia AccessEvents
Critério (cenário do QA, §H4) Tipo Conforme Evidência
o rastro de tentativa falha é preservado — quantas foram apagadas é registrado antes de as apagar funcional sim eventos_de_acesso_test.exs:49, assert log =~ "falhas_apagadas=2", com a guarda assert antes.failed_attempts == 2
a decisão continua no retorno, e não no log (L69) não funcional sim authenticate/2 devolve {:error, {:throttled, s}} distinto de {:error, :invalid_credentials}; AccessEvents não decide nada
refute que senha, temporária ou session_token apareçam no log não funcional sim eventos_de_acesso_test.exs:131, :132, :136
quatro tentativas falhas → quatro registros de recusa com user_id e tenant_id funcional NÃO o teste exercita duas tentativas e assere o agregado (falhas_apagadas=2), não quatro linhas de recusa com os dois campos
os campos de AGENTS.md §15 entram por Logger.metadata no plug e nas hooks não funcional sim visto na saída da minha execução: request_id=… tenant_id=… user_id=… [warning] acesso: entrada recusada …

Fase derivada: sro.not_accepted_deliverable — cinco dos oito eventos do achado continuam sem registro, e um critério do cenário do QA não é conforme.

O que me faz recusar em vez de aceitar como parcial, e é o ponto: duas funções — painel_recusado/4 e espera_acionada/3 — foram escritas, documentadas e nunca chamadas. Isso é pior que a ausência delas, e não é uma opinião sobre estilo:

  • a ausência é legível; a função órfã não é. Quem for reconstruir um incidente amanhã vai fazer grep painel_recusado, encontrar def painel_recusado, e concluir que a recusa de painel está registrada. Não está;
  • o corpo do PR reforça a leitura errada. Ele escreve: "Não ficava registrado: entrada aceita, entrada recusada, desaceleração acionada, recusa de painel, queda de sessão, nem os atos administrativos" — e a seção "O que este PR não resolve" lista a concessão de escopo e o elo, mas não lista a desaceleração nem a recusa de painel. Um leitor honesto do PR conclui que esses dois foram fechados;
  • e é exactamente a família do sucesso silencioso, na variante que ainda não tinha nome aqui: capacidade declarada em código e nunca exercida. A ausência de erro lida como resultado, agora com a ausência sendo a de uma chamada.

A recusa de painel é a que mais dói, porque é a que o D04 depende para mitigar o risco que a FR-024 aceitou. A FR diz, com todas as letras: "o caminho é registro de acesso (achado H4) e limite de taxa, e enquanto o H4 não existir este risco está aceito e declarado, e não mitigado". Com painel_recusado/4 sem chamador, o H4 não existe para o efeito que a FR-024 precisava dele, e a FR está apoiada numa mitigação que a release parece ter entregado e não entregou.

O que a fecha: chamar as duas funções onde elas pertencem — Access.pode_ver/3 no ramo do {:nao, motivo}, e Auth no ramo do {:throttled, s} —, mais o caso de quatro recusas com os dois campos, e a correção do corpo do PR ou uma nota neste registro dizendo o que ficou.

Destino: volta ao product backlog, com prioridade alta — é o que fecha a mitigação da FR-024. O que foi entregue embarca sem aceitação, e o rastro de tentativa falha, que era o ponto que dava a severidade, está fechado — registro isso porque a recusa não deve apagar a parte que funcionou.


D08 — escopo de projeto deixa de abrir painel de pessoa · ACEITO

Produzido por: #846

Critério (FR-024, §"E escopo de PROJETO não abre painel de pessoa") Tipo Conforme Evidência
escopo de projeto não abre painel de pessoa funcional sim access_test.exs:256 — "escopo de PROJETO não abre painel de pessoa — decisão de 2026-09-09"
o par: o escopo de projeto continua existindo em scopes/2 para o que ele nomeia funcional sim access_test.exs:288 — sem este par, o conserto teria removido o escopo em vez de restringi-lo
os outros caminhos continuam valendo — a própria pessoa, colega de equipe, organization, liderança declarada funcional sim access_test.exs:214, :229, :242, :334
a prova de que o teste pega o defeito: o ramo devolvido reprova não funcional sim declarado no PR ("devolvi o ramo do projeto e rodei") e coerente com a asserção; a asserção é sobre o retorno {:nao, _}, não sobre ausência

Fase derivada: sro.accepted_deliverable.

A razão pela qual aceito uma mudança que retira alcance. O alcance retirado nunca foi decidido: pode_ver_equipe/3 recusava este mesmo escopo desde sempre, com a razão escrita — "ele nomeia um projeto, e uma equipe pode trabalhar em vários; deixá-lo passar faria autoridade subir de lado" —, e pode_ver/3 o aceitava sem teste nenhum (grep escopo_de_projeto test/ voltava vazio). Não era um caminho coberto: era um ramo que ninguém tinha medido. E o alcance dele era maior que o que o veredito vizinho recusava.

O que NÃO foi medido, e é a mesma lacuna que a v0.6.0 dispensou: quantas contas, em produção, perdem alcance com isto. A consulta é barata — contas com concessão project vigente, não-admin, sem outro escopo que alcance o mesmo painel — e não foi feita. Vai para As medidas a fazer depois de subir, e o número continua desconhecido até lá. Não muda o veredito, porque o critério é o escopo não abre o painel e ele é conforme; muda o que se sabe sobre quem sente a mudança.


D09 — o corte da coleta usa atividade de issue, e não push de código · ACEITO

Produzido por: #847

Os critérios são os do item de investigação docs/backlog/investigar-estado-divergente-da-issue.md, caso 2.

Critério Tipo Conforme Evidência
a causa do caso 2 identificada entre as quatro hipóteses, com evidência funcional sim é a hipótese 2, na forma que ninguém tinha escrito: percorrer?/2 pulava o repositório sem push desde a última leitura, e atividade de issue não é push. Um repositório de quadro era pulado para sempre
a medida contra a origem, e não contra o código não funcional sim plataformas-project pushedAt=21/jul updatedAt=28/abr e conectafapes-project pushedAt=hoje updatedAt=03/set — a medida refutou a primeira proposta: updatedAt do repositório está mais antigo que pushedAt nos dois casos, e trocá-los teria piorado o corte com o teste continuando verde
um teste do caso: issue mexida sem push nenhum percorre funcional sim pular_sem_atividade_test.exs, ampliado em 121 linhas. O teste que existia media "sem push, não percorre" — o comportamento, não a correção
o par: a economia medida em 2026-08-14 (106 de 121 repositórios pulados) continua existindo não funcional sim idem — sem este par, o conserto viraria "percorre sempre"
o conserto é retroativo, senão os repositórios congelados continuariam pulados funcional sim QueryVersion da fase issues: 2 → 3. Sem o incremento, o sinal novo só seria lido depois de o corte deixar passar, e o corte antigo não deixava
o sinal novo não custa consulta não funcional sim issues(last: 1, orderBy: {field: UPDATED_AT, direction: ASC}) na consulta de repositórios que já roda

Fase derivada: sro.accepted_deliverable — seis critérios conformes.

O que este entregável NÃO fecha, e precisa estar dito porque o item de backlog é maior que ele: o caso 1 — a issue apagada na origem, que a plataforma mostra aberta. Esse é o objeto da spec 063, mergeada nesta janela como texto sem código: specs/063-issue-ausente-da-origem/ contém spec.md e nada mais, e lib/the_band/work_items/ não foi tocado nesta janela. O item de investigação continua aberto, e o número que ele pede — quantas issues estão neste estado — continua não medido.

E o efeito colateral que é risco de produção, declarado aqui e não escondido: o incremento de QueryVersion faz corte_vale?/2 devolver false uma vez por repositório, e todos são percorridos na primeira coleta depois do delivery. Isso tem custo de cota e de tempo, e nenhum dos dois foi medido. Está em Risco residual aceito.


D10 — o tipo de merge declarado passa a ser conferido · ACEITO

Produzido por: #834

Critério Tipo Conforme Evidência
o gate reprova o que deve reprovar não funcional sim executado por mim: bash scripts/testa-gate-tipo-de-merge.sh → seis casos de reprovação, todos com saída 1 — corpo sem declaração, as duas marcadas, marcada sem motivo, motivo só com o comentário do template, squash num PR para main, squash num back-merge
o gate aprova o que deve aprovar não funcional sim mesma execução: quatro casos com saída 0 — squash com motivo, merge commit com motivo, merge commit para main, forma livre
o teste roda o script do workflow, e não uma cópia dele não funcional sim o script extrai jobs.declarado.steps[0].run do próprio .github/workflows/pr-tipo-de-merge.yml

Fase derivada: sro.accepted_deliverable.

Por que isto vale mais do que parece. As duas reprovações que importam — squash num PR para main e squash num back-merge — são a L92/L83 virando gate: o squash na release diverge os históricos e faz os conflitos crescerem a cada back-merge, e esta casa pagou isso quatro vezes. Um gate que reprova o squash no PR de release protege esta release e a próxima.


Resumo da aceitação

Quantidade
Entregáveis avaliados 10
Aceitos 7 — D01, D02, D03, D04, D08, D09, D10
Não aceitos 3 — D05 (H6), D06 (H3-B), D07 (H4)
Critérios avaliados 57 — contados nas tabelas acima, não estimados
Critérios não conformes 8 — 3 em D05, 4 em D06, 1 em D07
Critérios sem evidência 1 (D06 — o roster e as medidas não mudam ao desativar)
Critérios não avaliáveis ainda 1 (D06 — não autentica por token; o token não existe)

O entregável do sprint compõe-se só dos aceitos (sro.sprint_deliverable_composed_of_accepted_deliverable), e a mesma regra vale para a release: o conteúdo declarado da v0.7.0 são os sete entregáveis aceitos. Os três recusados estão em produção como exceção declarada, e é assim que a página de novidades — quando existir — vai tratá-los: não anuncia.


O que embarca SEM aceitação

Nada recusado embarca escondido. Três coisas embarcam sem aceitação, e são estas:

O que Está em produção depois do merge? Por que não se anuncia
a administração alcançando painel de pessoa (D05, H6) sim — a emenda da FR-022 está no código o conserto era de registro, e os três registros continuam afirmando o contrário do que o código faz. Fecha com três edições de texto
a conta desativada (D06, H3-B) — o botão Desativar, o estado na lista, a migração sim quatro critérios não conformes, um sem evidência, tela sem protótipo aprovado, e quatro decisões de produto tomadas pelo código
os eventos de acesso (D07, H4) sim, em parte cinco dos oito eventos do achado continuam sem registro, e duas funções foram escritas e nunca chamadas

A que mais dói é a D06, e por uma razão que não é a lista de critérios: é a única funcionalidade nova para quem usa desta release inteira. Recusá-la significa que a v0.7.0 não tem nenhuma funcionalidade nova que possa ser anunciada — o resto é conserto, e conserto que retira alcance não se anuncia como ganho. Escrevo isso porque é o preço da regra, e o preço tem de ficar escrito para que a regra possa ser reconsiderada com o custo à vista, em vez de dobrada em silêncio.

E vale notar o que a recusa NÃO diz: não diz que a conta desativada deva sair da release. Ela conserta um achado Alto medido em produção, e retirá-la deixaria o desligamento implícito de pé por mais um ciclo. Diz que ela vai sem aceitação, com o que falta nomeado — que é a diferença entre uma exceção assumida e a regra contornada.


A terceira pergunta que fecha a release: a página anuncia esta versão?

Não. E a resposta a "é pré-condição desta release?" é: sim, pela regra como está escrita — e proponho dispensa explícita, com o custo declarado. Não vou reescrever a regra para caber no que aconteceu.

A regra entrou em AGENTS.md pelo #829, por decisão da pessoa mantenedora em 2026-09-09, e é literal: "A pergunta que fecha a release deixa de ser duas e passa a ser três: a aceitação está confirmada? o CI está verde? e a página anuncia esta versão? Uma release entregue sem a terceira é uma release que quem usa não recebeu."

Por que a dispensa, e não a negação de que seja pré-condição:

  1. o item está bloqueado no Design, e o bloqueio é real. A superfície tem tela, e tela desta casa começa por protótipo aprovado. Não existe protótipo, e não existe nem sequer a decisão das três perguntas abertas de docs/backlog/a-versao-em-producao-na-pagina.md — onde a superfície vive, quem a vê, e se há aviso. Levar essas três à pessoa mantenedora é trabalho meu, e não o fiz antes desta release;
  2. há um bloqueio de primeira ordem antes do Design: a plataforma não sabe qual versão está servindo. Sem isso, a página imprimiria a versão do mix.exs da árvore que construiu o site — que é exatamente o que a regra proíbe ("não a do mix.exs da árvore de trabalho: a que o Dokploy está servindo"). Fechar isso é decisão de arquitetura, e é anterior a qualquer pixel;
  3. o conteúdo desta release é, em maioria, conserto de defeito medido em produção — três deles altos, e um deles (H1) convertendo acesso temporário em posse da conta. Segurar a entrega até a página existir mantém o defeito no ar por mais um ciclo, para ganhar um aviso sobre uma release cuja única funcionalidade nova eu mesmo acabei de recusar.

O que a dispensa custa, e é preciso ser exato: esta release retira alcance de três maneiras (tenant suspenso, escopo de projeto, ranking sem escopo). Alguém que abria um painel ontem e não abre hoje vai encontrar uma recusa e nenhuma explicação de que a regra mudou. Isso não é um aviso que faltou — é uma pessoa concluindo que a plataforma quebrou. É o caso mais forte que a regra do #829 existe para cobrir, e é justamente o que esta release não vai entregar.

A dispensa não é para sempre, e proponho a fronteira agora: a v0.8.0 não sai sem isso. E o caminho tem ordem — (a) a pessoa mantenedora responde às três perguntas abertas do item; (b) decide-se como a aplicação lê a própria versão; (c) o Design desenha e publica; (d) o código; (e) a conferência do QA. O passo (a) é meu, e é o que levo junto desta proposta.


Risco residual aceito

Três, e a decisão de cada um é da pessoa mantenedora — eu proponho e digo o custo.

1. A repaginação de todos os repositórios, de uma vez

O QueryVersion da fase issues sobe de 2 para 3, e isso é deliberado: sem o incremento os repositórios congelados continuariam pulados, e o conserto não seria retroativo. O efeito é que corte_vale?/2 devolve false uma vez por repositório e todos são percorridos na primeira coleta depois do delivery.

O que não foi medido: quanto de cota isso consome, quanto tempo leva, e se o gestor de cotas da ADR 0007 absorve o pico sem parar coleta legítima. O que se sabe: são 712 e 2 669 issues só nos dois repositórios nomeados pela pessoa mantenedora, e a economia normal do corte é de 106 de 121 repositórios pulados — ou seja, a coleta seguinte faz aproximadamente oito vezes o trabalho de uma coleta comum.

Por que aceito assim mesmo: o gestor de cotas existe exatamente para isto (ADR 0007 — "a coleta volta de onde parou"), e o custo é de uma vez só. O que peço é que a primeira coleta depois do delivery seja olhada, e o número anotado — está em As medidas a fazer depois de subir.

2. A exceção do decimal continua embarcada

hex: [ignore_advisories: ["CVE-2026-32686"]] segue no mix.exs, com a mesma decisão registrada na v0.6.0. Reavaliada nesta release, como a decisão original exige a cada fechamento: nada mudou — não há versão nova do decimal no Hex, o registro OSV continua sem evento fixed, e test/the_band/decimal_limitado_test.exs continua sendo a proteção real. Prazo inalterado: até 2026-12-07.

Um sinal a acompanhar: o achado H11 desta avaliação diz que mix hex.audit já está avisando que a entrada "can be removed". Isso é a ferramenta gritando sozinha, que era o desenho — e é o gatilho de reexame antes do prazo.

3. Dez achados de segurança abertos, e nenhum é bloqueio

Achado Sev. Por que não bloqueia esta release
H7 — Dokploy implanta latest Média conserta-se sem release; depois de um incidente não há como dizer qual imagem rodava
H8 — ações do CI por tag mutável Média conserta-se sem release; um terceiro executa código com credencial de publicação
H9 — ssl: true do Repo comentado a determinar é pergunta para quem opera, e ela é anterior ao conserto: a base roda no mesmo host da aplicação? A resposta define a severidade
H5 — sessão encerrada serve no LiveView conectado Média trocar a senha por desconfiança não expulsa a aba aberta
H10, H11, H13, H14, H15, H16 Baixa endurecimento sem caminho de exploração hoje

Nenhum dos três altos ficou aberto — H1, H2 e H3 foram fechados, ainda que a parte B do H3 não esteja aceita. H7 e H8 são os únicos que se resolvem sem release, e por isso não há razão para esperar por ela: podem ser feitos hoje, e ficam melhor feitos hoje.

E a fronteira na direção oposta, copiada do próprio documento de segurança: "prioridade decidida não apaga o achado. Ele fica aberto até ser corrigido ou explicitamente aceito. 'Despriorizado' não é 'resolvido'."


Pré-condições do delivery, em ordem de execução

O merge do PR development → main é o deploy. Nesta ordem, e nenhum passo pula:

# Passo Estado
1 bump do mix.exs para 0.7.0 na development, antes de abrir o PR de release feito neste commit
2 aceitação confirmada pela pessoa alocada ao papel, sobre a seção Veredito por entregável — e sobre as três recusas PENDENTE — e desta vez é o passo que vem antes, não depois
3 CI verde. mix gates executado por mim sobre 4253bbc: EXIT=0, 16 gates, 2 014 testes. O PR de release repete a corrida sobre a integração feito em desenvolvimento; falta o CI do PR de release
4 a migração que altera esquema. 20260909180000_conta_desativada.exs — só-acréscimo de colunas nulas, ensaiada contra cópia do banco real: 4 971 issues e 90 vínculos intactos, 0 contas desativadas por acidente feito
5 decisão sobre as três recusas: embarcam sem aceitação (proposta), ou saem da release PENDENTE
6 decisão sobre a dispensa da página de versão PENDENTE
7 os pedidos de revisão recuperados nos 21 PRs desta janela e nos seis da v0.6.0 PENDENTE — tentei e o ambiente bloqueou a chamada; o comando está abaixo
8 merge, na janela combinada. O CD lê a versão do mix.exs, publica ghcr.io/the-band-solution/theband:v0.7.0, cria a tag v0.7.0 e chama o webhook do Dokploy. Ninguém tagueia à mão pendente
9 back-merge main → development depois do merge — o squash diverge os históricos (L83, quatro reincidências), e agora o gate do #834 reprova squash em PR para main pendente

Duas releases no mesmo dia. A v0.6.0 subiu às 13:39:49Z de 2026-09-09, e esta é preparada às 23:19Z do mesmo dia. Não é problema — é o que acontece quando uma avaliação de segurança acha seis coisas consertáveis num dia — mas tem uma consequência que não deve passar: as medidas do runbook §7 que a v0.6.0 deixou por fazer continuam por fazer, e esta é a quarta release seguida em que isso acontece.


A lacuna de revisão — e ela piorou

Merge não é revisão, e revisão não é aceitação. Medido contra a API em 2026-09-09, PR a PR:

PRs requested_teams + requested_reviewers reviews Classificação
#829 a #849 — todos os 21 desta release vazio 0 revisão nunca pedida

Vinte e um de vinte e um. A regressão que a nota da v0.6.0 identificou começando no #822 não parou: atravessou a release inteira, incluindo os sete PRs de conserto de segurança e a migração de esquema. Não há um único PR nesta janela com revisor pedido, e nenhum tem revisão registrada.

Isto é pior do que na v0.6.0, e por uma razão específica: de #808 a #821 o pedido ia para a equipe the-band, que é o que produz independência — paulossjunior é autor de todos e não pode atender ao próprio pedido, então quem atenderia é Adylla027 ou EduardoNFraiz, que não implementaram. O procedimento estava certo e deixou de ser feito. E os PRs que ele deixou de cobrir são justamente os que mais pedem uma segunda leitura: código de autenticação, código de autorização, e uma migração.

Vazio prova ausência de registro, não ausência de leitura. A classificação correta é revisão nunca pedida — não "não ocorreu". Se a pessoa mantenedora leu e concordou, o registro que cabe é revisão atestada, sem registro, com a data e a frase dela; enquanto isso não for dito, fica como está.

Vou recuperar, e o como está medido. A crença de que "não há como pedir revisão de PR mergeado" foi falsificada na v0.6.0: a API aceitou o pedido no #823 já mergeado. Então a recuperação é uma chamada por PR, e é isto:

for n in 89 822 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 \
         839 840 841 842 843 844 845 846 847 848 849; do
  gh api -X POST repos/The-Band-Solution/theband/pulls/$n/requested_reviewers \
    -f 'team_reviewers[]=the-band'
done
# e CONFERIR, porque --reviewer e --add-reviewer falham em silêncio:
gh api repos/The-Band-Solution/theband/pulls/<n> --jq '.requested_teams[]?.slug'

Tentei executá-lo nesta sessão e o ambiente bloqueou a chamada. Registro a tentativa e o bloqueio em vez de listar o comando como se ninguém tivesse tentado — é a diferença entre pendência e omissão.

São 28 PRs, medidos um a um em 2026-09-09: os 21 desta janela, os seis que a v0.6.0 deixou (#822, #824 a #828 — reconferidos hoje, todos ainda vazios), e o #89, da feature 001, aberto desde então. O #823 é o único com pedido, feito retroativamente na v0.6.0 — e é a prova de que os outros 28 são recuperáveis.

O que fecha a causa, e é decisão de infraestrutura, fora deste papel: abrir os PRs com identidade de agente, bot ou GitHub App. O implementador passaria a ser autor de facto, e quem revisa poderia aprovar formalmente. Apontar isso é deste papel, porque é este registro que a lacuna suja — e é a sexta vez que ele o aponta.

A pergunta que fica, e é desconfortável: sete consertos de segurança entraram em produção sem que ninguém além de quem os escreveu os tenha lido. Três das minhas recusas acima foram encontradas por leitura — o cabeçalho que contradiz o código, as duas funções sem chamador, os critérios de razão e ator ausentes. Nenhuma delas apareceu em gate nenhum, e os 16 gates estão verdes. É exatamente o que a revisão independente existe para acrescentar, e é o que a 058 fez de diferente e vale copiar: três revisões independentes por agente depois do merge — segurança, QA e desenho — que acharam o que os gates não pegam.


O que ficou de fora, e por quê

O que Por quê Destino
H7 e H8 — Dokploy implanta latest, ações do CI com tag mutável conserta-se sem release, e portanto não dependem desta product backlog, e podem ser feitos hoje
H9 — ssl: true comentado a severidade depende da topologia de produção pergunta para quem opera, anterior ao conserto
H5, H10, H11, H13 a H16 média e baixa, sem caminho de exploração hoje product backlog
o §6 do runbook — o backup restaurado de verdade bloqueado em recurso: a conta no destino S3 não existe docs/backlog/backup-restaurado-de-verdade.md. A 050/US2 segue não aceita, desde a v0.1.0
050/US1 — o endereço estável AS4, SC-001, SC-002, SC-004 e SC-005 sem evidência, pela terceira release seguida as duas recuperáveis (SC-004, SC-005) estão nas medidas abaixo
060/US3 — a saída declarada continua não aceita: o SC-013 nunca foi cronometrado. Reavaliado hoje, e a resposta é a mesma — a cronometragem não aconteceu. Ver abaixo não sai; fecha com uma medida de tempo
060/US6 e US7 especificadas, sem tarefa; a US7 com o cartão já construído product backlog
060/US8 parcialmente implementada, sem tarefa, não avaliada product backlog
060/US10 a US12 sem protótipo aprovado product backlog, bloqueadas no Design
spec 061 — a API pública mergeada como texto; nenhuma linha de código, e as telas US1/US3 sem protótipo fora desta release; nada nela embarca como código
spec 062 — servidor MCP texto, e depende da 061 product backlog
spec 063 — a issue ausente da origem texto sem código: specs/063-issue-ausente-da-origem/ tem só spec.md, e lib/the_band/work_items/ não foi tocado product backlog. O caso 1 do item de investigação continua aberto, e o número dele não foi medido
a versão em produção na página bloqueada no Design e em decisão de arquitetura dispensada nesta release, com custo declarado; pré-condição da v0.8.0

A 060/US3 reavaliada — e o veredito não muda

Continua sro.not_accepted_deliverable. O critério que falta é o SC-013 — registrar a saída de alguém em menos de 1 minuto, sem sair da aba Estrutura —, e a metade que a plataforma controla continua provada: o formulário abre sob a linha, no mesmo LiveView, e a submissão não sai da aba (saida_e_equivoco_na_tela_test.exs:57,93,200).

O que falta é uma cronometragem com uma pessoa e um relógio, e ela não aconteceu. A v0.6.0 subiu às 13:39Z de 2026-09-09; passaram-se dez horas e o número não existe. Não há evidência nova, e critério sem evidência não está atendido — nem por passagem de tempo, nem por a funcionalidade estar em produção e aparentemente funcionar.

Registro a reincidência pelo nome, porque ela é o padrão: medidas que só se podem fazer com alguém presente são as que sistematicamente não se fazem. É a mesma família das cinco medidas do runbook §7, e agora são duas releases em que a US3 aparece na lista das recusadas pela mesma razão de um minuto.

Consequência: a US3 continua fora do que a página de novidades poderia anunciar — o que, nesta release, é irrelevante, porque a página não existe.


As medidas a fazer depois de subir

Vazio seria pior que "não medido". Coluna em branco lê-se como esquecimento; não medido é um resultado, e diz que a pergunta continua aberta.

Novas, desta release

# Medida Por que importa Estado
1 a primeira coleta depois do delivery: quanto tempo levou, quanta cota consumiu, e se algum repositório parou por limite o QueryVersion 2→3 força todos os repositórios a serem percorridos de uma vez — cerca de oito vezes o trabalho normal a fazer
2 os dois casos reais do item de investigação: plataformas-project#703 (fechada na origem) passa a aparecer fechada no The Band? é o defeito que originou o #847, e a verificação só é possível depois de a coleta repaginar. Se não corrigir, o D09 volta a não aceito a fazer
3 quantas contas perdem alcance com o escopo de projeto deixando de abrir painel — não-admin, com concessão project vigente, sem outro escopo que alcance o mesmo painel é a mesma lacuna que a v0.6.0 dispensou no passo 7, e o número continua desconhecido a fazer
4 contas com disabled_at preenchido logo depois da migração tem de ser zero. O ensaio provou zero na cópia; produção é outro banco a fazer
5 falhas_apagadas maior que zero em qualquer entrada aceita, no primeiro dia é o sinal que não existia antes do H4: um sucesso com falhas_apagadas=17 é uma campanha que deu certo. Se aparecer, é incidente a fazer

Herdadas, e nenhuma fechou

Medida Origem Estado
SC-001 — entrar e ver painel em < 2 min, de rede que não é a de desenvolvimento runbook §7, 050 não medido, 4ª release
SC-002 — < 15 min de procedimento, < 2 min de indisponibilidade, cronometrando a janela runbook §7, 050 não medido, 4ª release
AS4 — sessão aberta antes do deploy continua valendo depois runbook §7, 050 não medido — e é o único que não se recupera: exige uma sessão aberta antes do merge
SC-004 — zero segredos em imagem e logs (docker history, Config.Env) runbook §7, 050 não medido — recuperável agora, sobre a imagem publicada
SC-005 — 100% das rotas de dados recusam sem sessão runbook §7, 050 não medido — recuperável agora
a medida de permissão do passo 7 da v0.6.0 v0.6.0, dispensada não medido
o custo das duas abas da tela da equipe em produção quickstart.md da 060 não medido
quantas pessoas por equipe têm papel not declared quickstart.md da 060 não medido
a cronometragem do SC-013 (060/US3) v0.6.0 não medido

Duas dessas são recuperáveis agora e não dependem do instante do merge — SC-004 e SC-005. Vale fechá-las em vez de esperar a quinta release, e é o que recomendo.


Ensaio de restauração (FR-008)

A migração desta release foi ensaiada contra dado real em 2026-09-09 — docs/producao/ensaio-2026-09-09-migracao-conta-desativada.md. Números: 3 contas antes e depois, 90 vínculos, 4 971 issues, duas colunas anuláveis criadas, índice parcial criado, 0 contas desativadas por acidente. == Migrated 20260909180000 in 0.0s, código de saída 0.

O ensaio do §6 do runbook — o do backup do S3 — continua NÃO feito, bloqueado porque a conta no destino S3 não existe. Ele responde outra pergunta, e as duas parecem a mesma:

pergunta respondida?
a migração roda sobre dado real? sim, medido
o backup existe de verdade? não, desde a v0.1.0

A 050/US2 segue não aceita por isso, e continua sendo a primeira pendência de produção. Pendência de recurso não fecha com trabalho (L15) — só com a decisão de quem administra de criar a conta.


O que o CD fez

A preencher depois do merge, lido do log da corrida — nunca de memória (L60). Os campos que esta tabela vai carregar: a imagem e o digest, se latest aponta para o mesmo digest, a tag git e quem a criou, o webhook e a resposta do Dokploy, se o merge foi merge commit e não squash, se a versão veio do mix.exs, e a linha da migração no log do contêiner — que é o campo que a v0.6.0 deixou sem evidência.

O que se observou depois

A preencher depois do delivery. E o que esta seção costuma esconder fica dito de antemão: ausência de notícia não é ausência de erro. Não há painel de erro nesta plataforma, e "nada quebrou" é indistinguível de "ninguém olhou" — que é o defeito mais reincidente desta casa, com nove ocorrências registradas.