Pular para conteúdo

v0.10.0 — três consertos de segurança que não estão no ar, e o servidor MCP

Release de development para main, e não hotfix. main e produção estão na v0.9.2 (/version responde 0.9.2); development está 37 commits à frente. Merge commit, nunca squash (AGENTS.md §12, L83), e back-merge de main para development depois, também por merge commit, para que o commit da release volte à integração (L92).

Proposta em 2026-09-27. Decisão da pessoa mantenedora no mesmo dia: levar produção pela release inteira, e não por um hotfix de cada conserto, porque ela carrega três consertos de segurança que não estão no ar (N5, H2-R e #989). Segurança vem primeiro (AGENTS.md §14.0): exposição ativa em produção entra na primeira release possível, e esta é a primeira. O momento do delivery é da pessoa mantenedora: todo merge em main é deploy.

A versão, e por quê

v0.10.0, MINOR. A release entrega capacidade que não existia: um agente com o token da API passa a perguntar sobre uma equipe por POST /mcp, com quatro ferramentas. Nada do que a API prometia a quem tem alcance deixa de valer.

Grau Carrega? Por quê
MAJOR não nenhum contrato legítimo quebra. A única resposta que muda de forma é a de quem o veredito recusa, e o que ela carregava era o próprio defeito. A decisão está em O contrato da API para quem o veredito recusa, abaixo
MINOR sim o servidor MCP (spec 062): rota nova, quatro ferramentas, docs/api/mcp.md. Funcionalidade nova, sem quebra
PATCH também, e absorvido os três consertos de segurança e o #941 sozinhos seriam PATCH. Numa release que carrega MINOR, o PATCH não sobe o número por conta própria

mix.exs já diz 0.10.0, e não foi tocado por esta nota.

A exceção à regra de que só aceito embarca, e fica registrada

Não existe registro de aceitação da 062. docs/sprints/034-servidor-mcp/ tem só o sprint-backlog.md: nenhum aceitacao.md, nenhum sprint-review.md. Pela regra deste papel, a 062 não compõe release.

Ela embarca por decisão da pessoa mantenedora de 2026-09-27, e a razão registrada é de segurança, não de valor: levar os três consertos já. A alternativa seria reverter a 062 em development ou abrir um hotfix por conserto a partir de main; o custo dela não foi medido, e a observação de que o #981 carrega junto a parte de segurança da 062 (a US3 e o A3) é leitura da descrição do PR, não medição. Então:

  • a 062 está no ar, e não aceita. Isso é diferente de recusada, e diferente de aceita;
  • a aceitação dela é ato pendente, a fazer contra os critérios de specs/062-servidor-mcp/spec.md, e o que se puder medir em produção só se mede com token de conta: pelo registro de 2026-09-24, o lado autenticado é não medível pelos agentes, e fica "não medível", nunca "aceito";
  • a aplicação não deve anunciar a 062 como entregue enquanto a aceitação não existir. Hoje a superfície de anúncio não existe (ver Depois do merge), e por isso a restrição não custa nada nesta release, mas fica dita para a primeira que a tiver.

Os três consertos de segurança

Os três vêm do mesmo exercício: o inventário de segurança feito antes da 062 (docs/seguranca/2026-09-24-inventario-antes-do-mcp.md), porque o servidor MCP reusa a porta do token e serve a outro modelo o dado sobre pessoas. Dois deles o inventário classificou como bloqueantes da 062, e por isso entram na mesma release que ela, e antes dela nesta nota.

#945 — suspender uma organização passa a cortar também os tokens da API dela (achado N5)

O que entrega: a porta do token (ApiAuth.com_tenant/2) passa a conferir tenant.status, como as três portas de sessão já conferiam desde o #837. Token de organização suspensa é recusado com o mesmo corpo das outras recusas (SC-003), e o motivo organizacao_suspensa vai ao log (SC-004). Guarda: test/the_band_web/api/organizacao_suspensa_test.exs.

O que estava no ar, desde a v0.9.0: suspender uma organização derrubava a sessão e recusava a entrada pela tela, e não tocava nos tokens. Medido no inventário: o token responde 200, a organização é suspensa, e o mesmo token segue respondendo 200 em GET /api/v1/people. Toda pessoa da organização suspensa com token emitido continuava lendo pessoas, equipes, medidas, projetos e sincronizações até o token vencer, ou nunca, para o token sem expiração. Quem suspendesse acharia que suspendeu. Severidade alta.

O que o conserto não desfaz: o que foi lido depois da suspensão já foi lido. A pergunta que importa é se há organização suspensa em produção, e o inventário registra que isso não foi lido (item 3 das suas limitações). Para rodar em produção:

-- Leituras por token feitas por organizações que hoje estão suspensas.
-- A base não guarda QUANDO a organização foi suspensa: tenants tem status, e não suspended_at.
-- O updated_at do tenant é só um teto aproximado: ele muda com qualquer alteração do registro.
SELECT t.id AS tenant_id, t.updated_at AS alterado_em,
       r.token_public_id, r.route,
       count(*) AS leituras, min(r.occurred_at) AS primeira, max(r.occurred_at) AS ultima
FROM api_access_reads r
JOIN tenants t ON t.id = r.tenant_id
WHERE t.status = 'suspended'
GROUP BY t.id, t.updated_at, r.token_public_id, r.route
ORDER BY ultima DESC;

Nenhuma linha: nenhuma organização suspensa leu por token, e o N5 fica sem consequência. Com linhas: as leituras posteriores à suspensão foram indevidas; a data exata da suspensão não está na base e precisa vir de quem suspendeu.

#946 — o perfil escrito pelo modelo passa a seguir o veredito, na tela e na API (achado H2-R)

O que entrega: o perfil que o modelo escreve sobre uma pessoa (forças, evolução, "atenção") e as competências contadas que saem dele passam a seguir o mesmo veredito pode_ver/3 do painel de trabalho, nas três portas. Decisão da pessoa mantenedora de 2026-09-24: o perfil é agregado, e virou linha na tabela da FR-024 da 045.

Porta Antes Depois
tela /people/:id a seção #profile para qualquer conta do tenant só com alcance; sem ele, o perfil nem é lido, e o evento gerar_perfil é recusado no handler
GET /api/v1/people/:id profile sempre profile é null sem alcance, e a nota diz recusa, não "nenhum perfil gerado"
GET /api/v1/people competencies de todas as pessoas da página só das pessoas alcançadas, com o alcance da página numa só chamada (pessoas_alcancadas/2, L38)

O que estava no ar: qualquer conta autenticada do tenant lia, na página de qualquer pessoa, o texto que um modelo escreveu sobre ela; e qualquer token lia as competências contadas de todas. Medido na tela no inventário (conta member sem elo, {:nao, :sem_elo_declarado}, as marcas do perfil visíveis); lido, não medido, na API. Severidade alta. Desde quando está no ar não foi medido nesta nota: é desde que a seção #profile existe na página da pessoa. Nenhum dado de outro tenant: o alcance era o tenant inteiro.

O que o conserto não desfaz: quem viu, viu. O rastro é o mesmo do #989, abaixo, porque a porta é a mesma página e a mesma rota: o evento painel recusado marca cada conta que abriu a pessoa sem alcance, e naquele momento o perfil estava na tela. Para a listagem, a consulta em api_access_reads com route = '/api/v1/people' lista os tokens que a leram; quais pessoas estavam na página de cada leitura não fica gravado (target_id só existe na rota de detalhe), e isso fica dito.

Muda uma tela em uso sem protótipo novo: a seção some para quem não alcança, e a recusa que aparece ao lado é a da seção Work, já aprovada. Nenhum elemento novo foi desenhado.

#992 — as mudanças e as discussões de uma pessoa passam pelo veredito (issue #989)

O que entrega: quem o veredito recusa deixa de ver, na página da pessoa e em GET /api/v1/people/:id, os títulos dos PRs dela e as issues em que ela comentou. A identidade e a proveniência continuam visíveis a todo o tenant. Quem tem alcance vê o mesmo de antes. Decisão da pessoa mantenedora de 2026-09-27: PRs e discussões de que a pessoa participou são trabalho, e não identidade.

O que estava no ar, desde a v0.6.x (#430, 2026-08-18): a página da pessoa tem uma seção, Where this came from, que fica fora do painel que o veredito protege. Ela devia ter só a proveniência — de onde a pessoa veio, com que identificador, desde quando —, mas carregava também duas listas que são trabalho:

  • Changes: os títulos dos PRs que a pessoa abriu, revisou e integrou, e os commits;
  • Discussions they took part in: as issues em que ela comentou, com o título.

As duas eram carregadas para qualquer conta autenticada do tenant. Quem a tela recusava via o painel fechado e, logo abaixo, os títulos. A API repetia a exposição de propósito: GET /api/v1/people/:id devolvia changes e discussion_participation a quem o veredito recusava, "porque na tela também estão fora". Não há dado de outro tenant: o alcance era o tenant inteiro, contra o que a pessoa mantenedora decidiu em 2026-08-26 (FR-012 da 023: a própria pessoa, a liderança declarada, o escopo e a administração).

Achado pela avaliação de segurança da feature 069, parte 2, como achado lateral, e medido em 2026-09-26 com uma sonda: conta recusada com {:nao, :sem_elo_declarado}, título do PR visível, seção de trabalho escondida.

Por que nenhum teste pegava: havia um teste, e ele exigia a exposição (person_detail_test.exs, "discussões e mudanças continuam vindo para quem o veredito RECUSA"), e conferia só is_map, sem dado povoado: não teria como ver um título vazar.

Porta Antes Depois
tela /people/:id as duas listas carregadas e desenhadas para todos carregadas só com o veredito (se_pode), e os dois blocos só aparecem com @ve_o_trabalho?
API GET /api/v1/people/:id changes e discussion_participation sempre null quando access.can_see_work é false, como work

O contrato da 061 (specs/061-api-publica/contracts/api-v1-people.md) e o esquema OpenAPI foram emendados no mesmo PR: os dois campos são nullable, com a razão escrita.

A evidência: test/the_band_web/trabalho_dentro_do_veredito_test.exs, cinco testes, as duas portas, com um PR e uma discussão de títulos únicos. A conta recusada não vê nenhum dos dois; o admin vê os dois, e é isso que prova que o refute não é vazio. Defeito injetado: a tela de main no lugar da corrigida reprova 1 de 5 ("a tela mostrou o PR a quem o veredito recusa"); a API de main reprova 1 de 5 (changes não é null). Restaurado das cópias conferidas, os 5 passam.

O que o conserto não desfaz: quem pode ter visto

Vale para o #992 e para o #946, que expunham na mesma página e na mesma rota.

O título que já foi visto não volta, nem o perfil. O que se pode saber é quem viu, e as duas portas deixam rastro:

  1. Toda visualização recusada gerou o evento painel recusado no log da aplicação, nível warning, com user_id, tenant_id, alvo_person_id e o motivo. Na tela e na API, o evento é emitido exatamente no caso que expunha os títulos. Buscar no log de produção:
acesso: painel recusado

Cada linha é uma conta que abriu a página, ou a rota, de uma pessoa que não podia ver. Se a pessoa tinha PR ou discussão coletados, a conta viu os títulos; se tinha perfil gravado, viu o perfil.

  1. Na API, cada leitura concedida pelo token fica em api_access_reads, desde a v0.9.0. A consulta abaixo lista os tokens que leram /people/:id. Cruzando o target_id com as linhas do log acima, sabe-se quais credenciais receberam os títulos:
SELECT r.token_public_id,
       r.target_id           AS person_id,
       count(*)              AS leituras,
       min(r.occurred_at)    AS primeira,
       max(r.occurred_at)    AS ultima
FROM api_access_reads r
WHERE r.route = '/api/v1/people/:id'
GROUP BY r.token_public_id, r.target_id
ORDER BY ultima DESC;

O que não se sabe: a retenção do log de produção não foi medida. Se ele guarda menos que desde 2026-08-18, as visualizações de antes do corte ficam sem rastro, e isso fica dito.

As buscas rodam em produção, e são ato de quem opera: o agente deploy-producao ou a pessoa mantenedora. Nenhum agente desta sessão acessa produção.

O servidor MCP (spec 062)

O que a pessoa passa a conseguir fazer: ligar um agente de IA ao The Band com o token da API que já emite em /api-tokens, e perguntar a ele sobre uma equipe — quem está nela, o que cada pessoa tem aberto, quanto o trabalho espera por revisão e o que está parado —, recebendo em cada resposta a ressalva no mesmo objeto: a composição, a janela, a origem, a regra com versão, as limitações e as interpretações erradas, lidas da base de conhecimento, e a data da coleta. O agente vê exatamente o que a pessoa dona do token veria na tela e na API, e nada além.

Embarca sem aceitação registrada: ver A exceção à regra de que só aceito embarca, acima.

A superfície nova: POST /mcp, na pipeline [:api, :api_autenticada] do router, a mesma de /api/v1. Documentada para quem integra em docs/api/mcp.md.

#944 — o plano da 062 passa a descrever o código que existe

O que entregou: antes de uma linha de implementação, o plano deixou de mandar criar um registro de leitura e um limite que a 061 já tinha, e passou a tratar os dois defeitos que o reuso traria. Trouxe junto o inventário de segurança de 2026-09-24, de onde saíram o N5 e o H2-R. Só documentação.

#977 — a biblioteca do protocolo entra, fixada, com a exceção do cowlib guardada

O que entregou: {:ex_mcp, "== 1.5.0"} e, em mix.exs, hex: [ignore_advisories: ["EEF-CVE-2026-43966", "EEF-CVE-2026-43969"]]. A exceção cobre as duas advisories do cowlib que a ex_mcp traz, com a medição de alcance em specs/062-servidor-mcp/r3-cowlib-alcance.md e as guardas em test/the_band/cowlib_inalcancavel_test.exs, que reprovam se qualquer premissa da exceção cair. Abriu o sprint 034 (sprint-backlog.md e 30 issues). Nenhuma rota, nenhuma tela.

#979 — a fundação do contexto MCP

O que entregou: lib/the_band/mcp/ com Ferramentas, Envelope e Ausencia (T002); KnowledgeBase.measurement/1, de onde o envelope lê as ressalvas (T003); os três estados da ausência — checked, not_checked, refused —, e nenhum dos dois de ausência sai com 0 (T005, SC-002); a guarda de que nenhum módulo do contexto fala com Repo, Ecto.Query ou Ecto.Adapters.SQL (T008). Nada alcançava o usuário ainda.

#981 — o servidor MCP responde em /mcp, com a ressalva no mesmo objeto

O que entregou: as quatro ferramentas (team_roster, team_open_work, team_review_wait, team_stale_work) com dado real, pelos mesmos contextos da 061, e a parte de segurança da 062, que é a maior do PR:

  • caminho único por chamada: argumento fechado, equipe carregada no tenant, veredito, só então ferramenta (R6);
  • lista fechada de métodos JSON-RPC antes da biblioteca, e nada abre stream (R4);
  • a leitura gravada no ponto do veredito, com ferramenta e equipe (R1), e um limite só por token: /mcp e /api/v1 gastam o mesmo contador (FR-026);
  • texto de terceiro marcado no schema como untrusted_text, com os caracteres invisíveis sinalizados, e nenhuma ferramenta busca URL (A3);
  • a resposta inteira varrida: nenhum segredo, e-mail nem platform_access_level (SC-005).

Toca código da 061 em produção, e fica dito: a recusa de equipe passa a deixar rastro nas três portas (N6), inclusive em /teams/:id, onde a recusa da quebra por pessoa passa a ser registrada no log sem mudança visível; o ApiReadLog pula o /mcp para não gravar duas vezes.

#986 — paridade das três portas, e o token revogado cai na chamada seguinte

O que entregou: prova de que a tela, a API e o MCP dão o mesmo veredito de acesso a uma equipe, nos quatro caminhos de concessão e na recusa; de que sem alcance as quatro ferramentas recusam com razão, nunca com lista vazia; e de que um token revogado é recusado na chamada seguinte, sem reiniciar nada (062/US2, T017–T019). Só teste.

#988 — fecha a 062: o que não responde, quanto custa, e um cliente real

O que entregou: prova de que as quatro ferramentas declaram o que não respondem, de quanto custam contra a rota HTTP, e de que um cliente MCP real (o SDK oficial de Python) as usa de ponta a ponta (T015, T028–T031); e docs/api/mcp.md, para quem vai ligar um agente.

O resto que embarca

#941 — o botão de copiar passa a copiar, e o estado do token fala inglês

O que entregou: em /api-tokens, o botão Copy value passa a copiar o valor, e a dizer quando não consegue; a coluna state passa a imprimir active / revoked / expired em vez de "ativo" / "revogado" / "expirado". Os dois defeitos estão no ar desde a v0.8.0. Muda tela em uso, na direção do que o protótipo aprovado já dizia.

#942 — a régua da tela de tokens ganha guarda (061)

O que entregou: só teste. A conferência de QA achou que o SC-013 da 061 — o hash do token não aparece no HTML — era declarado conforme por um teste que não afirmava nada: com o hash injetado em cada linha, os 84 testes ficavam verdes. O teste passa a reprovar com o defeito injetado. Nada muda em produção.

#980 — segurança vem primeiro, e o CLAUDE.md

O que entregou: a decisão de 2026-09-25 escrita em AGENTS.md §14.0 e no CLAUDE.md novo, que importa o AGENTS.md e passa a ser carregado em toda sessão de agente. Só documentação.

#982 — o roster deixa de devolver e-mail (já em produção pela v0.9.2)

Já está no ar: foi a produção pelo hotfix v0.9.2 (#984), e o back-merge o trouxe de volta a development. Entra nesta lista só como registro; esta release não muda nada nele. O registro completo, com a consulta de quem pode ter lido, é docs/releases/v0.9.2.md.

#983 — o relatório da pessoa e da equipe entra no backlog

O que entregou: o registro do pedido de relatório em HTML ou PDF, gerado a partir das duas telas, e das decisões que vêm antes. Ainda não é spec, e nada dele chega a quem usa.

Os três riscos medidos

Medidos em 2026-09-27 sobre origin/main..origin/development, com a árvore limpa.

Risco Veredito Medido como
migração destrutiva nenhuma migração nenhum arquivo em priv/repo/migrations/ entre origin/main e origin/development. O ensaio de restauração não é pré-condição desta release
variável obrigatória nova nenhuma .env.example sem variável nova; compose.yaml e config/runtime.exs sem mudança. O Dokploy não precisa de configuração nova
mudança em tela em uso sim, em três telas, e numa rota da API ver a tabela abaixo
Superfície O que muda Para quem
/api-tokens o botão de copiar passa a copiar; o estado sai em inglês (#941) todos que emitem token
/people/:id o perfil escrito pelo modelo (#946) e os blocos Changes e Discussions they took part in (#992) somem só para quem o veredito recusa; quem tem alcance vê o mesmo
/teams/:id a recusa da quebra por pessoa passa a ser registrada no log (#981, N6) nenhuma mudança visível
GET /api/v1/people/:id profile, changes e discussion_participation passam a null só para token sem alcance sobre a pessoa
GET /api/v1/people competencies só das pessoas alcançadas (#946) só para as pessoas fora do alcance do token
POST /mcp rota nova (062) quem liga um agente

Dependência nova, e fica dita porque é o quarto risco que esta release traz: ex_mcp == 1.5.0, com uma exceção num gate de segurança (mix hex.audit) para as duas advisories do cowlib. É risco residual aceito, e não falso positivo, e cumpre o que o §14.0 exige de exceção: a medição (r3-cowlib-alcance.md), a decisão da pessoa mantenedora de 2026-09-24 (escrita no comentário ao lado da exceção, em mix.exs), e a guarda que a derruba quando a premissa cai (cowlib_inalcancavel_test.exs): o endpoint deixar de ser Bandit, um listener Cowboy ser iniciado, algum código chamar :cow_* ou gun, ou a ex_mcp mudar de versão.

O contrato da API para quem o veredito recusa

A pergunta: GET /api/v1/people/:id passa a devolver profile, changes e discussion_participation como null para token sem alcance sobre a pessoa, e GET /api/v1/people deixa de trazer competencies das pessoas fora do alcance. Para quem integra sem alcance, a forma da resposta muda. Isso torna a release MAJOR?

Decisão: não. É conserto, e a release fica MINOR. As razões, em ordem de peso:

  1. O que a resposta carregava para quem não tinha alcance era o defeito, e não uma garantia. O contrato da 061 devolvia os dois campos ao recusado "porque na tela também estão fora": ele copiava a exposição da tela, e a exposição da tela contrariava a FR-012 da 023, decidida em 2026-08-26, que é quem define quem vê trabalho de uma pessoa. Semver protege o que o produto promete a quem o usa; não protege o acesso a dado que a regra de acesso já negava. Chamar isto de MAJOR seria declarar o vazamento como funcionalidade;
  2. quem tem alcance recebe exatamente a mesma resposta. Nenhum campo muda de tipo, de nome ou de significado para o uso legítimo da rota;
  3. o sinal já estava na resposta. access.can_see_work já existia, e work já era null quando ele é false; profile e competencies já podiam ser null (nenhum perfil gerado). Um cliente que lê a resposta como documentada já trata null nesses campos. O que muda é que changes e discussion_participation passam a seguir o mesmo sinal que work, e a nota diz recusa, não ausência;
  4. o contrato foi emendado no mesmo PR que mudou o código: o api-v1-people.md da 061 e o esquema OpenAPI declaram os campos nullable, com a razão (#946, #992). Não há divergência silenciosa entre o que a documentação promete e o que a rota faz.

O que fica dito, porque é verdade mesmo sem ser MAJOR: um integrador que lia changes ou discussion_participation com um token sem alcance, e não tratava null, vai quebrar depois do merge. Isso é consequência pretendida: ele lia o que não devia. Se houver token nessa situação, a consulta de Quem pode ter visto o mostra, e a conversa com quem o emitiu é a mesma da exposição.

E, por completude: a série é 0.x. Pela semver, antes da 1.0.0 a API pública não é estável, e nem uma quebra real obrigaria o salto para 1.0.0. A decisão acima não se apoia nisso: as notas de release desta casa aplicam a tabela MAJOR, MINOR e PATCH à 0.x como se fosse estável (ver v0.9.2.md), e é por essa régua que a mudança não é MAJOR.

O que esta release não carrega

O que alguém pode esperar ver nela, e não está:

O quê Onde está Por que alguém esperaria, e o que fica
a 069: localizar pessoa e o trabalho aberto dela pelo MCP, e o espelho das oito rotas da API branch 069-mcp-busca-de-pessoas, 4 commits à frente de development, só documentação (spec e avaliação de segurança), sem PR aberto é a continuação da 062, e foi dela que saiu o #989. O MCP desta release só responde sobre equipes: nenhuma ferramenta busca pessoa
#991: GET /api/v1/people/:id diz a qualquer conta do tenant se a pessoa tem conta na plataforma issue aberta, label de segurança é da mesma rota do #992, e quem ler esta nota pode supor que o #992 fechou tudo nela. Não fechou: o dado de que a pessoa tem conta continua saindo para todo o tenant. Não é dado de outro tenant
#987: team_open_work declara is_composed: false para equipe composta issue aberta é defeito de uma ferramenta que esta release publica. Está no ar com o defeito: para equipe composta de outras, a ressalva de composição que a ferramenta devolve está errada. Medido em 2026-09-25 com o SDK oficial de Python, na LEDS - ConectaFapes de dev: o team_roster conta 48 membros (31 diretos e 17 via partes), e o team_open_work conta 31 e declara is_composed: false. Quem lê a resposta de uma equipe composta deve saber disso
#985: a cláusula vinculo_vigente de pode_ver_equipe/3 nunca é alcançada, e quem pertence é registrado como escopo de equipe issue aberta o veredito de equipe que as três portas usam, inclusive o /mcp. O resultado da concessão não muda (quem pertence continua vendo), e o motivo registrado está errado: o log e a leitura gravada dizem "escopo" onde é "vínculo". A paridade do #986 prova o mesmo veredito nas três portas, e não prova o motivo certo
a aceitação da 062 não existe ver A exceção à regra de que só aceito embarca
a superfície que anuncia a versão na aplicação não existe a página do The Band não diz qual versão está no ar nem o que ela trouxe; só a rota /version, que é endpoint e não página. Lacuna aberta desde a v0.9.1

Os efeitos do #987 e do #985 foram medidos: o #987 com o cliente Python real (a própria issue traz a tabela), e o #985 pela paridade de test/the_band_web/mcp/paridade_test.exs, que fixa a razão escopo_de_equipe no caso do vínculo vigente.

Depois do merge

A ordem, e de quem é cada passo. Nenhum agente desta sessão acessa produção.

  1. o CD publica e entrega: o merge do PR de release em main dispara o workflow, que cria a tag v0.10.0, publica ghcr.io/the-band-solution/theband:v0.10.0 e chama o webhook do Dokploy. Ninguém builda, tagueia nem publica à mão;
  2. a versão no ar, conferida pela origem e não pelo mix.exs:
curl -s "$PRODUCAO_URL/version"   # tem de responder 0.10.0

Enquanto responder 0.9.2, os três consertos não estão no ar, e nada abaixo começa; 3. o agente deploy-producao acompanha a entrega e confere a saúde depois dela; 4. as buscas de quem pode ter visto, em produção, pelo deploy-producao ou pela pessoa mantenedora: - N5: a consulta de leituras por token de organização suspensa; - H2-R e #989: o evento acesso: painel recusado no log, e a consulta de /people/:id em api_access_reads, cruzadas pelo alvo_person_id; - a retenção do log de produção, medida, porque sem ela não se sabe o que a busca alcança; - resultado registrado nesta nota, em O que se observou depois, com linha ou com "nenhuma linha", e nunca omitido; 5. o agente aceitacao-em-producao mede o que é medível sem conta: /version e o lado anônimo. /mcp, /people/:id, /api-tokens e /teams/:id são autenticados, e ficam não medível, nunca "aceito" (registro de 2026-09-24); 6. back-merge de main para development, por merge commit (L92).

A pergunta que fecha a release, e a resposta de hoje:

Pergunta Resposta
a aceitação está confirmada? não para a 062, que embarca por exceção registrada; os consertos de segurança têm guarda com defeito injetado (#992) ou teste de cenário (#945, #946)
o CI está verde no PR de release? a conferir no PR, pelo código de saída
a página anuncia esta versão? não, e a superfície não existe. Quem usa a aplicação não será informado de que as seções da página da pessoa mudaram

O que se observou depois

A preencher depois do delivery: a data do merge, a resposta de /version, e o resultado das buscas do passo 4.