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, commix gatesexecutado 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 últimoaceitacao.mddesta 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 colunaAceitoque 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:
- 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;
- 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; - 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;
- o §6 do runbook — o backup restaurado de verdade — nunca aconteceu, desde a v0.1.0. A 050/US2 segue não aceita;
- nenhum dos 21 PRs desta janela tem revisão registrada nem pedido de revisão (medido
abaixo).
1.0.0sobre 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¶
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:
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.
D02 — deps sai do índice, e link simbólico rastreado reprova (H12) · ACEITO¶
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¶
| 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:
- 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 doPROMPT.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.
-
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_atsolto repete oconnected_tools.statusda ADR 0004 D7, que já é dívida declarada"; o implementado foidisabled_atna própriausers. Não há registro de a pessoa mantenedora ter escolhido a outra opção — há o código tendo escolhido. -
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, encontrardef 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:
- 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; - 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.exsda árvore que construiu o site — que é exatamente o que a regra proíbe ("não a domix.exsda árvore de trabalho: a que o Dokploy está servindo"). Fechar isso é decisão de arquitetura, e é anterior a qualquer pixel; - 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.