A varredura estava em /private/tmp — 242 KB de trabalho medido que sumiria no próximo restart. Agora é `docs/audits/2026-08-14-afirmacoes-de-estado.md`, com o COMANDO de cada achado preservado. É o comando que faz o documento valer: quem for tratar um achado mede de novo em vez de confiar na linha. O relatório envelhece exatamente como aquilo que ele critica, e diz isso no topo. Junto, a evidência do alinhamento do canal `stable` (`2026-08-14-alinhamento-stable-v1.3.0.md`): os três pacotes tinham `stable` e `1.3.0` em digests diferentes, e agora batem. Medido ANONIMAMENTE depois, com token de pull público — escrita que só se confirma pela sessão que a fez não está confirmada. `latest` e `main` conferidos como controle: intactos. ## A triagem é por TIPO de documento, não por gravidade Foi a chave que faltava, e ela inverte o que eu faria sozinha: gravidade alta num documento-retrato **não** se conserta. **RETRATO** (`current-state.md`, `harness-audit.md`) — carimbados, não corrigidos. Os dois já traziam `audited_against: 789dfa6` no front-matter, e ninguém lê front-matter: o corpo falava no presente. O carimbo agora é a primeira coisa visível, com a distância medida — **1.014 commits e 71 migrations** entre o commit auditado e hoje — e declara que nada ali é mantido. Documento honestamente datado nunca mente; documento atualizado uma vez volta a mentir na semana seguinte, e pior, porque a atualização recente faz confiar mais. **AUTORIDADE** — corrigidos, e onde deu, a afirmação virou COMANDO: - os três READMEs diziam "**Quatro checks são obrigatórios**". São cinco. Em vez de trocar o número, entrou o `gh api … --jq '.required_status_checks.contexts'` com a saída do dia — e a admissão de que a linha já disse "quatro" e "cinco"; - os três READMEs mandavam ter o Google Authenticator à mão porque "o primeiro login de admin exige MFA". **Não exige** — o `bootstrap-owner.ts` grava `mfa_required: false` explícito. Um comprador espera uma tela que não vem; - os três diziam que o comando local era "a lista **completa** dos gates obrigatórios". Não é: `e2e` e `imagens-ok` só rodam no CI. Verde na sua máquina não é verde no merge; - `AGENTS.md` afirmava que **6 vars de `lib/env.ts` faltam no `.env.example`, incluindo 3 secrets**. Medido: das 45 chaves, a única ausente é `NODE_ENV`, e os três secrets nomeados estão todos lá. A regra do DoD continua; a dívida caiu; - `AGENTS.md` também dizia que login/signup/convite estavam sem rate limit. O `lib/auth/rate-limit.ts` cobre os quatro, por IP e por identificador hasheado. Crons e MCP seguem sem, e isso ficou escrito; - `AGENTS.md` prometia um "DoD de 15 itens" — agora são 16, e a linha virou o `grep` que conta, em vez de um número que envelhece de novo no próximo item. **PLANEJAMENTO** (`docs/stories/`, epics) — intocado. É onde vivem 385 dos ponteiros mortos do repo, e registro histórico pode apontar para o que nunca existiu. O gate já exclui essa pasta de propósito. ## O processo — duas linhas, não um sprint 1. `CONTRIBUTING.md`: quem toca um doc de autoridade corrige as afirmações **daquele** doc. Não sai caçando nos outros — a dívida decai sozinha se ninguém a alimentar. 2. `CLAUDE.md`, DoD item 16: se o PR muda comportamento, procurar a afirmação de estado sobre esse comportamento. Só sobre o que mudou. E onde puder virar comando, trocar em vez de corrigir. ## Um erro de instrumento, no meio disto O script com que filtrei os achados "que passam de afirmação envelhecida" imprimia sempre a MESMA citação — variável de loop vazando para o loop seguinte. As saídas medidas estavam certas, as citações não. Reescrito e refeito: 25 achados, não os 43 que a primeira passada listou. Um instrumento quebrado não devolve erro; devolve uma lista plausível. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RKm2XcTcfgi1vdMcDYSRWa
8.0 KiB
Contributing — DeskcommCRM
Antes de começar
- Leia
CLAUDE.md— convenções não-negociáveis. - Leia
ARCHITECTURE.md— visão de 1 página. - Identifique o epic de origem em
docs/stories/epics/MASTER.md.
Fluxo
Branches
feat/EPIC-XX-short-slug # nova feature
fix/EPIC-XX-short-slug # bug fix
chore/short-slug # chore (deps, configs)
docs/short-slug # apenas docs
Commits
Conventional commits + escopo EPIC-XX:
feat(EPIC-04): kanban drag-and-drop com fractional indexing
fix(EPIC-03): cron recover-stuck-messages marcando sending stuck >5min como failed
docs(EPIC-12): mark complete + wave log
Mensagens em PT-BR são aceitas. O assunto deve ser imperativo e ≤72 chars.
epic-executor
Mudanças grandes seguem docs/stories/epics/. O epic-executor consome o frontmatter (epic_id, priority, depends_on, status) e executa wave-by-wave com validação E2E continuous.
Ao finalizar um epic:
- Atualizar frontmatter
status: pending → completed (partial: ...)oustatus: completed. - Append "Wave Completion Log" no final do arquivo.
- Atualizar a row correspondente em
docs/stories/epics/MASTER.md.
PR process
-
Branch a partir de
main. -
Implementar. Adicionar testes (E2E pra fluxos, unit pra lógica pura).
-
Definition of Done. A lista está separada em duas por um motivo: até hoje ela misturava o que uma máquina reprova com o que só uma pessoa percebe, e contribuidor marcava o checklist inteiro de boa-fé para ser barrado por um gate que ninguém tinha contado a ele.
O que o CI reprova sozinho — rode antes de abrir o PR e não terá surpresa:
pnpm typecheck && pnpm lint && pnpm lint:channels && pnpm test:unit && pnpm test:shell && pnpm build pnpm test:db # precisa de Docker; sobe um Postgres limpo e aplica o baselineO que o CI NÃO vê — fica com você e com a revisão, e é onde moram os defeitos caros:
- RLS habilitada e policy
tenant_isolation_<tabela>_allse você criou tabela tenant-aware (o teste de isolamento cobre uma lista fixa de tabelas; a sua nova não entra sozinha) - Audit log emitido se há mutação relevante
- Rate limit aplicado se a rota é pública
- Zod validando todo input externo
- Sem
console.logesquecido (uselib/logger.ts). Opnpm lintnão reprova isso — a regra está como aviso, então ele passa verde; a conferência é humana - Env vars novas em
.env.exampleelib/env.ts, com default que não quebre instalação nova - Mudança de schema saiu como tripla: arquivo em
supabase/migrations/, apêndice idempotente nosupabase/baseline.sqle linha noMANIFEST.md. O kit self-host aplica só o baseline — migration que não chega lá não chega em quem instalou numa VPS. Nenhum job de CI confere isso - Se você tocou
Dockerfile*,docker-compose*.ymlouhostgator-setup-kit/: a mudança alcança quem já instalou. Lei emdocs/doctrine/packaging.md. O CI reprova serviçobuild:-only, instalação em tag móvel e imagem quebrada (imagens-ok); o que fica com você é o resto: variável nova com default que não quebre.envantigo, e a atualização não pedindo edição manual de arquivo. Nenhum bump pode exigir que o operador da VPS edite alguma coisa na mão — se exigir, abra issue com plano de migração em vez de PR - Docs atualizadas se mudou contrato (PRD/spec)
pnpm test:e2e(subset relevante) — opcional se você contribui de fora, ver abaixo
- RLS habilitada e policy
-
Abrir PR contra
main. Description deve referenciar o epic e listar evidências (logs/screenshots dos testes). -
Tocou um documento de autoridade? Corrija as afirmações de estado daquele documento — as que dizem o que está ativo, o que falta, o que aponta para onde. Não saia caçando nos outros: a dívida decai sozinha se ninguém a alimentar. Achados medidos, com o comando de cada um, em
docs/audits/2026-08-14-afirmacoes-de-estado.md. -
CI deve passar antes de merge. Obrigatórios:
verify,invariants(isolamento RLS),build-and-size,e2eeimagens-ok.O
imagens-ok(em.github/workflows/publish-image.yml) constrói as três imagens que o self-hoster instala, roda em PR e bloqueia desde 2026-08-13.Verde no
e2enão é "jornada provada": ele mesmo imprime, no resumo, quais specs não cobriu — e a que fica de fora é justamentevps-fresh-onboarding, a instalação do zero.Esta lista dizia "três obrigatórios" e chamava o
e2ede não-bloqueante. Estava desatualizada nos dois pontos, e quem a usasse como régua mediria contra a régua errada. Confira na fonte antes de confiar em qualquer lista escrita:gh api repos/melgarafael/DeskcommCRM/branches/main/protection --jq '.required_status_checks.contexts'
Pegando uma issue — o protocolo
Existe porque já falhamos nisto: em 2026-07-30 abrimos uma issue, um contribuidor começou a resolvê-la, e um mantenedor entregou a mesma correção 21 segundos antes sem que nenhum dos dois pudesse ver o outro. O trabalho dele foi para o lixo. As regras abaixo são para que isso não se repita.
- Comente "pego esta" antes de codar. Uma linha basta. Um mantenedor te atribui a issue — a partir daí ela é sua e ninguém mais mexe.
- Issue com pessoa atribuída não se duplica. Se você quer ajudar mesmo assim, comente oferecendo; não abra PR concorrente.
- Mantenedor não implementa issue marcada
good first issueouhelp wantedsem antes se atribuir a ela publicamente. Se você vir uma dessas sem dono, ela é sua para pegar — essa é a garantia que damos em troca do passo 1. - Sem resposta em 48h depois do "pego esta"? Comece assim mesmo e diga no PR. A demora é nossa, o custo não pode ser seu.
Se você está contribuindo de fora (fork) — leia isto
Duas coisas vão parecer erro seu e não são:
- O check
Vercelfica vermelho com "Authorization required to deploy". Amaindeste repositório faz deploy de produção, e a Vercel se recusa a construir PR de fork por segurança — o que está certo. Ignore esse check; ele não entra no gate de merge. - Os workflows ficam parados esperando aprovação no seu primeiro PR. É política do GitHub para quem nunca contribuiu antes. Um mantenedor libera; do segundo PR em diante roda sozinho. Se demorar, comente no PR.
E sobre o pnpm test:e2e do DoD: rodar a suíte completa exige Docker, banco semeado e WAHA
local. Não travamos PR externo nisso — mande o que conseguiu provar (unit + descrição do
que testou na mão), que a prova de tela fica com o mantenedor. Exigir prova sem entregar a
ferramenta de produzi-la seria pedágio, não rigor.
Anti-patterns proibidos
Lista completa em CLAUDE.md. Os mais letais:
- Trigger Postgres fazendo HTTP
- Service role usado em handler sem filtrar
organization_idmanualmente getSession()no backend (usegetUser())- API key em query string
- Bearer plaintext no DB
console.logem código merged
Setup local
Veja README.md §Como rodar local.
Suporte
GitHub Discussions — é o canal público, funciona para qualquer pessoa e é onde a resposta fica registrada para quem vier depois. Para bug, abra uma issue.
Se for algo que não cabe em público (segurança, por exemplo): rafael@maudibrasil.com.br — o mesmo
endereço do CODE_OF_CONDUCT.md.
Esta seção apontava para um Discord interno cujo convite mora num Notion privado — inalcançável justamente para quem mais precisava dela, que é quem vem de fora. Ficou aqui como lembrete de que canal de suporte se testa pelo lado de fora.