Relatório de incidente 21 de julho de 2026 / versão 2

Incidente 2026-07-21 — análise de engenharia v2

Cada falha observada é tratada como evidência de uma capacidade ausente da plataforma. O objeto desta análise não é tornar um agente mais cuidadoso: é construir controles, interfaces e estados consultáveis que funcionem com qualquer operador — inclusive um humano distraído ou outro modelo de linguagem. Por isso, este relatório propõe zero correções comportamentais.

Escopo

Processo de entrega do dudata-hub

Falhas analisadas

8

Critério

Prevenção mecânica e autodiagnóstico

02

O que aconteceu

Uma rodada paralela implementou seis issues em worktrees isoladas. Na integração, as seis branches foram mescladas localmente em main e enviadas diretamente ao remoto, sem pull requests. Esse push acionou automaticamente o deploy de produção. Só depois foram descobertos o acoplamento entre push e deploy, a divergência entre a documentação e a prática de PRs e o estado real da produção.

  1. 1

    Implementação paralela

    Seis issues (#383–#388) foram executadas em branches e worktrees separadas. O isolamento do trabalho funcionou como previsto.

  2. 2

    Integração fora do fluxo de PR

    As seis branches foram mescladas localmente. Nenhuma chegou ao GitHub como branch remota; nenhum PR foi aberto; os jobs de CI configurados apenas para pull_request não foram executados nesse caminho.

  3. 3

    Push para main tornou-se deploy

    O workflow .github/workflows/deploy.yml é disparado por todo push em main. Sem GitHub Environment e sem aprovação obrigatória, as seis mudanças seguiram para produção sem uma etapa humana ou E2E anterior ao deploy.

  4. 4

    Ambiente local não inicializado

    Em uma worktree nova, faltava .env.local. O comando de desenvolvimento entrou no assistente interativo do Convex e foi disparado cinco vezes no mesmo contexto inválido, sem uma camada de bootstrap ou um erro de pré-condição que explicasse o reparo necessário.

  5. 5

    Estado externo ficou inconsistente

    Chamadas à integração do ClickUp falharam por incompatibilidade de parâmetros. Quando os comentários foram publicados, quatro deles informavam que as mudanças entrariam no ar no próximo deploy, embora o deploy já tivesse ocorrido. O episódio elevou substancialmente o impacto sobre o usuário; conteúdo pessoal sensível foi deliberadamente abstraído deste relatório.

03

Por que o v1 foi rejeitado

O v1 enquadrou o problema como disciplina do operador: lembrar um preflight, interromper loops e conferir o estado antes de agir. Esse enquadramento preserva exatamente a condição que produziu o incidente — o sistema continua aceitando uma transição inválida e espera que quem o opera perceba a tempo.

Correção comportamental

Depende de memória e julgamento

“Ler antes”, “não repetir”, “verificar o deploy” ou “seguir o fluxo” são intenções. Elas não alteram as permissões nem tornam o estado verdadeiro mais fácil de consultar.

Correção de plataforma

Muda o caminho possível

Rulesets, ambientes protegidos, scripts idempotentes, clientes tipados e APIs de estado impedem a transição perigosa ou retornam um diagnóstico acionável.

Teste do júnior distraído

A correção ainda funcionaria se o operador fosse um humano júnior distraído ou um LLM diferente, sem memória desta sessão?

Se a resposta depender de atenção, contexto implícito ou adesão voluntária a um documento, a causa-raiz ainda não foi alcançada.

Falha Promessa rejeitada Classe de artefato exigida
Push direto“Sempre abrir PR”Ruleset que exige PR e checks
Deploy involuntário“Confirmar antes”workflow_dispatch agora; Environment aprovado no Enterprise
Worktree inválida“Inspecionar o erro”Bootstrap idempotente e preflight executável
Status incorreto“Checar produção”Estado de deploy consultável e consumido pela integração

04

Análise por falha

As oito falhas são sintomas de uma plataforma de entrega incompleta. Cada cadeia para na primeira capacidade sistêmica ausente; nenhuma termina em memória, disciplina ou atenção do operador.

F1Alta

Integração local e push direto contornaram PRs

Sintoma. Seis branches foram mescladas localmente e enviadas a main. Nenhum PR, revisão no servidor ou evento de fechamento de issue existiu.

Cadeia 5-whys

  1. 1. Código sem revisão chegou a main porque o cliente pôde atualizar a ref com um push.
  2. 2. O remoto aceitou porque o ruleset não exigia PR, aprovação nem verificações.
  3. 3. O merge local não carregava objetos de revisão no servidor, CI ou issue.
  4. 4. A documentação não foi avaliada na atualização da ref e ainda descrevia merge em main.
  5. 5. A política de entrega existia como convenção e prosa, não como máquina de estados do servidor.

Capacidade ausente

Controlador de admissão de main, com PR, aprovação e CI avaliados pelo GitHub antes de mover a ref.

Correção de engenharia

Ruleset apontado explicitamente à branch padrão, sem bypass rotineiro, mais teste de divergência via API. Modelo de PR e hook local são metadados e retorno rápido; o ruleset é o controle.

target: branch
enforcement: active
conditions:
  ref_name: { include: ["~DEFAULT_BRANCH"] }
rules:
  - type: pull_request
    parameters:
      required_approving_review_count: 1
      dismiss_stale_reviews_on_push: true
      require_last_push_approval: true
  - type: required_status_checks
    parameters:
      required_status_checks: [{ context: quality }]
      strict_required_status_checks_policy: true
  - type: required_linear_history
bypass_actors: []
Teste do júnior distraído — passa. Um push direto é recusado pelo GitHub mesmo sem documentos carregados e com hooks locais desativados.

Como a comunidade resolve

Rulesets e branches protegidas rejeitam o push direto no remoto; a merge queue testa a integração contra o estado mais recente de main.

F2Crítica

Um push tornou-se deploy de produção sem decisão de liberação

Sintoma. deploy-prod rodava em todo push a main, sem environment:, e publicou Cloud Run, Convex e Firebase.

Cadeia 5-whys

  1. 1. A atualização do repositório virou produção porque o push disparou o workflow completo.
  2. 2. O job rodou sem decisão de liberação porque não referenciava um Environment protegido.
  3. 3. Não havia registro de SHA em produção porque também não existia Environment nem publicador do status de implantação.
  4. 4. README manual e workflow automático mantinham representações incompatíveis.
  5. 5. Admissão de produção e estado implantado não eram estados consultáveis da plataforma.

Capacidade ausente

Decisão explícita de produção mais registro de SHA → estado implantado.

Correção de engenharia

No GitHub Team/private verificado, workflow_dispatch interrompe o deploy automático no dia um. O estado-alvo Enterprise usa Environment production com revisor, prevenção de autoaprovação, política de branch e segredos restritos.

# .github/workflows/deploy.yml — controle disponível no dia um
on:
  workflow_dispatch:
jobs:
  deploy:
    environment:
      name: production
      url: https://dudata-hub-prototype.web.app
    runs-on: ubuntu-latest
    # Existing build, validation and deploy steps follow.
# Enterprise: habilitar required reviewers no Environment e restaurar o trigger pós-merge.

VERIFICADO A organização usa GitHub Team e o repositório é privado; nesse cenário, revisores obrigatórios de Environment exigem GitHub Enterprise. O dispatch manual registra intenção explícita, mas não aprovação independente.

Teste do júnior distraído — passa no dia um. Merge ou push não publica produção; alguém precisa disparar o workflow. No estado-alvo Enterprise, credenciais e execução também aguardam aprovação independente.

Como a comunidade resolve

Environments protegidos do GitHub retêm jobs e segredos até a aprovação; deployments de preview do Convex criam uma superfície isolada para validação antes de produção.

F3Alta · segurança

A worktree nova não formava um ambiente executável

Sintoma. O comando de desenvolvimento encontrou uma worktree sem .env.local e entrou repetidamente no assistente interativo do Convex em vez de iniciar a aplicação.

Cadeia 5-whys

  1. 1. O ambiente de desenvolvimento não iniciou porque faltavam valores e seleção de implantação.
  2. 2. git worktree add não copia arquivos ignorados nem executa bootstrap.
  3. 3. O ponto de entrada delegava direto a Turbo/Convex sem validar ambiente, porta ou identidade.
  4. 4. Convex interpreta configuração ausente como outro modo de execução, potencialmente interativo.
  5. 5. Não existia contrato created → prepared → listening por checkout.

Capacidade ausente

Provisionamento de worktree como código e ponto de entrada não interativo, com falha imediata.

Correção de engenharia

Um comando cria e provisiona o checkout, escreve manifesto ignorado, aloca portas e seleciona uma implantação de desenvolvimento com menor privilégio. predev recusa ausência ou identidade insegura.

# scripts/worktree-create.sh
git worktree add -b "$branch" "$path" main
( cd "$path" && bun run bootstrap -- --worktree "$path" )

// package.json
{
  "scripts": {
    "bootstrap": "bun scripts/bootstrap.ts",
    "predev": "bun scripts/predev.ts",
    "dev": "bun run predev && turbo run dev"
  }
}
Teste do júnior distraído — passa. Um checkout fresco é provisionado por uma interface única ou dev encerra antes do assistente interativo com WORKTREE_NOT_BOOTSTRAPPED.

Como a comunidade resolve

mise fixa o toolchain e tarefas; a Dev Container Specification codifica o ambiente; Convex multiple deployments provisiona um backend por worktree.

F4Baixa

O contrato do ClickUp vazava vocabulário incompatível

Sintoma. Chamadas usaram taskId, task_id e depois entity_id; cinco requisições falharam antes do formato aceito.

Cadeia 5-whys

  1. 1. Os argumentos não correspondiam ao esquema da ferramenta selecionada.
  2. 2. O ponto de chamada era uma fronteira sem cliente tipado ou validação local.
  3. 3. Operações moldadas pelo provedor vazavam nomes inconsistentes para todo chamador.
  4. 4. O erro era texto transitório, não uma atualização estruturada do contrato.
  5. 5. Não havia registro consumido pelo build, fachada estável nem testes de contrato.

Capacidade ausente

Fronteira MCP orientada por esquema, com operações geradas e erros autodescritivos.

Correção de engenharia

tools/list vira contrato versionado; o adaptador oferece nomes do domínio, valida antes da mutação e testa divergências de esquema em CI.

// integrations/clickup/client.ts
export async function postTaskComment(input: {
  taskId: string;
  body: string;
}) {
  return callValidated("clickup_create_comment", {
    entity_id: input.taskId,
    entity_type: "task",
    comment_text: input.body,
  });
}
Teste do júnior distraído — passa. A única API disponível aceita taskId; o adaptador converte e rejeita dados inválidos antes da rede.

Como a comunidade resolve

O MCP TypeScript SDK transforma Zod em JSON Schema e valida antes da rede; structured results tipam a resposta; o MCP Inspector oferece fixtures e inspeção.

F5Média

Status externo foi escrito a partir de crença, não da implantação

Sintoma. Quatro comentários afirmaram que a mudança entraria no ar depois, embora o deploy de F2 já a tivesse colocado em produção.

Cadeia 5-whys

  1. 1. O status publicado partiu de um plano presumido, não do registro real.
  2. 2. O fluxo de escrita não tinha uma consulta is SHA X live?.
  3. 3. Workflow e execução não publicavam um vínculo entre SHA e revisão.
  4. 4. Comentários livres viraram uma segunda fonte de verdade.
  5. 5. A integração aceitava afirmações de liberação sem implantação resolvida.

Capacidade ausente

Serviço consultável de implantação como fonte exclusiva de afirmações de liberação.

Correção de engenharia

deploy-status cruza implantação do GitHub, status e SHA da revisão exposta pela aplicação. O adaptador do ClickUp só renderiza LIVE, NOT_LIVE ou UNKNOWN a partir desse objeto.

$ bun run deploy-status --sha a195b2e --environment production
LIVE
sha: a195b2e...
environment: production
url: https://dudata-hub-prototype.web.app
deployment: <GitHub deployment URL>
runtime-revision: <Cloud Run revision + full SHA>
Teste do júnior distraído — passa. O adaptador não recebe frases de estado; recebe uma implantação resolvida e produz a linguagem correspondente.

Como a comunidade resolve

As APIs de GitHub Deployments e Deployment Statuses ligam SHA, ambiente e resultado; revisions do Cloud Run expõem o artefato realmente servido.

F6Alta · sistêmica

Regras eram descobertas, mas não sustentavam transições

Sintoma. Documentos e semântica do deploy foram examinados só depois de merge, push, deploy e comunicação.

Cadeia 5-whys

  1. 1. Leitura de documentos era opcional perante Git e deploy.
  2. 2. Nenhuma ação irreversível exigia provar compatibilidade com a política.
  3. 3. Markdown não estava ligado a ruleset, gatilho, Environment ou CLI.
  4. 4. CI não testava a política declarada contra a configuração real.
  5. 5. Conhecimento do operador era pré-condição porque o processo não estava nas fronteiras executáveis.

Capacidade ausente

Política como código aplicada em fronteiras irreversíveis e ligada à documentação por testes.

Correção de engenharia

Ruleset bloqueia merge indevido e workflow_dispatch bloqueia deploy silencioso no plano atual; delivery-policy.yaml descreve invariantes e CI compara política, estado remoto e workflow. Environment com revisor entra no estado-alvo Enterprise.

# delivery-policy.yaml
main:
  requires_pull_request: true
  required_checks: [quality]
  minimum_approvals: 1
production:
  environment: production
  trigger: workflow_dispatch # Team/private baseline
  approval_required: false   # true after Enterprise migration
  allowed_branches: [main]
Teste do júnior distraído — passa. A segurança não exige descoberta prévia: o remoto bloqueia a transição e o CI identifica a divergência.

Como a comunidade resolve

OpenSpec mantém deltas de intenção validáveis; ExecPlans preservam progresso e decisões; harness engineering conecta esses artefatos a testes e PRs.

F7Média

O fluxo documentado contradizia o fluxo praticado

Sintoma. AGENTS.md prescrevia merge sequencial em main; a convenção histórica usava PRs.

Cadeia 5-whys

  1. 1. A seção de paralelismo descrevia uma ação local insegura.
  2. 2. Nenhum teste comparava documentação, política de branch e workflow.
  3. 3. Markdown podia mudar sem efeito na plataforma de entrega.
  4. 4. Histórico era observação, não interface autoritativa.
  5. 5. Não existia pipeline de integridade da documentação como configuração.

Capacidade ausente

Documentação gerada da política executável, ou continuamente verificada contra ela.

Correção de engenharia

Uma fonte estruturada gera o fragmento curto de entrega; CI compara gerador, ruleset, gatilho de deploy e workflow. O servidor continua sendo a autoridade.

<!-- BEGIN GENERATED DELIVERY POLICY -->
main accepts merged pull requests with 1 independent approval
and `quality` green.
Production deploy requires an explicit `workflow_dispatch`.
Enterprise target: the `production` environment awaits approval.
Live status is queried by SHA with `bun run deploy-status`.
<!-- END GENERATED DELIVERY POLICY -->
Teste do júnior distraído — passa. CI detecta a divergência sem leitura humana; ruleset e gatilho manual ainda rejeitam os caminhos contraditos no plano atual.

Como a comunidade resolve

O padrão de harness engineering usa instruções curtas como índice de referências geradas; OpenSpec valida e arquiva deltas; CODEOWNERS protege mudanças de política.

F8Média

Revisão e aprovação não eram estados distintos

Sintoma. Verificações automatizadas foram tratadas como aprovação, embora não existisse revisão de PR nem decisão de liberação registrada.

Cadeia 5-whys

  1. 1. CI e aprovação eram rótulos conversacionais, não estados diferentes.
  2. 2. Não havia PR nem Environment dos quais consultar aprovações.
  3. 3. CI só prova verificações; não registra aceitação de produto, risco ou liberação.
  4. 4. main e produção não exigiam seus respectivos registros de autoridade.
  5. 5. A plataforma não modelava separadamente a autoridade de revisão de código e a de liberação.

Capacidade ausente

Estados consultáveis e separados para autoridade de merge e autoridade de produção.

Correção de engenharia

Ruleset exige verificações e aprovação de PR. No Team/private, dispatch registra intenção de deploy; no Enterprise, Environment exige aprovação independente posterior. pr-status e deploy-status expõem estados distintos.

Draft PR → checks pending → checks green → PR approved → merged
                                              └─ code-review authority

Team: dispatch requested → running → LIVE / FAILED
      └─ explicit intent, not independent approval
Enterprise: queued → production approval pending → running → LIVE / FAILED
                     └─ independent release authority
Teste do júnior distraído — passa. Green CI não move autoridade: GitHub recusa merge; produção continua parada até dispatch no plano atual e até aprovação independente no estado-alvo.

Como a comunidade resolve

Rulesets do GitHub representam checks e aprovação como condições distintas; a merge queue preserva ambas no SHA integrado.

05

As capacidades de plataforma

As oito falhas convergem para cinco controles executáveis. Esta seção especifica os artefatos, a ordem de implantação e as limitações que permanecem mesmo depois da correção.

CAP-01

Contrato de runtime local ao checkout

F3 · F6

O bootstrap copia — nunca cria symlinks — os arquivos locais com modo 0600, fixa o toolchain via mise, aloca portas determinísticas e termina com um preflight executável. Atualização de env exige --refresh-env; produção nunca é selecionada silenciosamente.

Toolchain e bootstrap

# mise.toml
[tools]
node = "24"
bun = "1.3.14"

# scripts/bootstrap-worktree.sh
#!/usr/bin/env bash
set -euo pipefail
root="$(git rev-parse --show-toplevel)"
main="$(git worktree list --porcelain | sed -n 's/^worktree //p' | head -n 1)"
refresh=false
[[ "${1:-}" == "--refresh-env" ]] && refresh=true
fail() { printf 'BOOTSTRAP FAILED: %s\nFIX: %s\n' "$1" "$2" >&2; exit 1; }
command -v mise >/dev/null || fail 'mise is not installed' 'install mise, then run: mise exec -- bun run bootstrap'
mise install
copy_env() {
  local rel="$1" src="$main/$1" dst="$root/$1"
  [[ -f "$src" ]] || fail "missing seed $src" "create it from ${src%.local}.example in the principal checkout"
  [[ "$root" == "$main" ]] && return
  if [[ -e "$dst" && "$refresh" != true ]]; then printf 'KEEP %s (use --refresh-env to replace)\n' "$rel"; return; fi
  install -m 600 "$src" "$dst"
}
copy_env apps/web/.env.local
copy_env packages/backend/.env.local
[[ -f "$main/apps/mobile/.env.local" ]] && copy_env apps/mobile/.env.local
mkdir -p "$root/.dev"
crc="$(printf '%s' "$(cd "$root" && pwd -P)" | cksum | awk '{print $1}')"
web_port="${DUDATA_WEB_PORT:-$((20000 + crc % 10000))}"
expo_port="${DUDATA_EXPO_PORT:-$((30000 + crc % 10000))}"
printf 'DUDATA_WEB_PORT=%s\nDUDATA_EXPO_PORT=%s\n' "$web_port" "$expo_port" > "$root/.dev/ports.env"
git config core.hooksPath .githooks
mise exec -- bun install --frozen-lockfile
mise exec -- bun scripts/predev.ts --bootstrap
printf 'READY checkout=%s web=http://127.0.0.1:%s expo=%s\n' "$root" "$web_port" "$expo_port"

Preflight que falha antes do wizard

import { existsSync, readFileSync } from 'node:fs'
import { createServer } from 'node:net'
import { spawnSync } from 'node:child_process'
const root = process.cwd()
const fail = (problem: string, fix: string): never => { console.error(`PREDEV FAILED: ${problem}\nFIX: ${fix}`); process.exit(1) }
const read = (path: string) => { if (!existsSync(path)) fail(`missing ${path}`, 'mise exec -- bun run bootstrap'); return readFileSync(path, 'utf8') }
const value = (text: string, key: string) => new RegExp(`^${key}=(.+)$`, 'm').exec(text)?.[1]?.trim().replace(/^['"]|['"]$/g, '')
if (Bun.version !== '1.3.14') fail(`Bun ${Bun.version}; expected 1.3.14`, 'mise install && mise exec -- bun run dev')
const node = spawnSync('node', ['--version'], { encoding: 'utf8' }).stdout.trim()
if (!/^v24\./.test(node)) fail(`Node ${node}; expected v24`, 'mise install && mise exec -- bun run dev')
const mobileMode = process.argv.includes('--mobile')
if (!mobileMode) {
  const web = read(`${root}/apps/web/.env.local`)
  for (const key of ['VITE_CONVEX_URL','CONVEX_URL','CONVEX_SITE_URL']) if (!value(web,key)) fail(`${key} is empty`, 'mise exec -- bun run bootstrap -- --refresh-env')
}
const backend = read(`${root}/packages/backend/.env.local`)
const deployment = value(backend, 'CONVEX_DEPLOYMENT')
if (!deployment || !/^(dev|local):/.test(deployment)) fail('CONVEX_DEPLOYMENT must target dev: or local:, never prod:', 'mise exec -- bun run bootstrap -- --refresh-env')
const ports = read(`${root}/.dev/ports.env`)
const name = mobileMode ? 'DUDATA_EXPO_PORT' : 'DUDATA_WEB_PORT'
const port = Number(value(ports, name))
await new Promise<void>((resolve) => { const server=createServer(); server.once('error',()=>fail(`${name} ${port} is busy`,`${name}=$(( ${port} + 1 )) mise exec -- bun run bootstrap`)); server.listen(port,'127.0.0.1',()=>server.close(()=>resolve())) })

Entrypoints

// package.json
"bootstrap": "bash scripts/bootstrap-worktree.sh",
"predev": "bun scripts/predev.ts",
"dev": "bash scripts/dev.sh",
"predev:mobile": "bun scripts/predev.ts --mobile",
"dev:mobile": "bash scripts/dev-mobile.sh"

# scripts/dev.sh
set -euo pipefail
set -a; source .dev/ports.env; set +a
exec mise exec -- bunx turbo run dev

# backend package
"dev": "convex dev --env-file .env.local </dev/null"

# Vite
server: { host: '127.0.0.1', port: Number(process.env.DUDATA_WEB_PORT ?? 3000), strictPort: true }

Falhas possíveis da correção

Colisões de hash continuam possíveis, mas falham antes do start e aceitam override. Cópias de env podem envelhecer; por isso refresh é explícito. Um deployment Convex por worktree ainda exige autenticação, quota, expiração, limpeza e estratégia de seed. Redirecionar stdin transforma prompts de autenticação/configuração em falhas intencionais.

CAP-02

Fronteiras server-side de merge e deploy

F1 · F2 · F6 · F7 · F8

O ruleset elimina a atualização direta de main. No plano atual, workflow_dispatch separa merge de deploy imediatamente. O estado-alvo Enterprise restaura deploy após merge, mas o job espera aprovação no Environment.

Ruleset autoritativo

{
  "name": "Main Branch", "target": "branch", "enforcement": "active", "bypass_actors": [],
  "conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } },
  "rules": [
    { "type": "deletion" }, { "type": "non_fast_forward" }, { "type": "required_linear_history" },
    { "type": "pull_request", "parameters": {
      "allowed_merge_methods": ["squash", "rebase"], "dismiss_stale_reviews_on_push": true,
      "require_code_owner_review": false, "require_last_push_approval": true,
      "required_approving_review_count": 1, "required_review_thread_resolution": true
    }},
    { "type": "required_status_checks", "parameters": {
      "do_not_enforce_on_create": false,
      "required_status_checks": [{ "context": "quality" }],
      "strict_required_status_checks_policy": true
    }}
  ]
}

Dia um e estado-alvo

# Team/private — mudança imediata
on:
  workflow_dispatch:
jobs:
  deploy:
    environment:
      name: production
      url: https://dudata-hub-prototype.web.app

# Enterprise — depois de habilitar required reviewers
jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 24 }
      - uses: jdx/mise-action@v3
      - run: mise exec -- bun install --frozen-lockfile
      - run: mise exec -- bun run test
      - run: mise exec -- bun run typecheck
  deploy:
    needs: verify
    environment:
      name: production
      url: https://dudata-hub-prototype.web.app
    runs-on: ubuntu-latest

O hook versionado em .githooks/pre-push bloqueia refs/heads/main com uma correção acionável, mas é somente feedback rápido: --no-verify o ignora; o ruleset continua sendo o controle.

Falhas possíveis da correção

Um nome incorreto de check bloqueia todo merge; quality deve ser observado primeiro em PR inofensivo. Dispatch manual registra intenção, não aprovação independente. Environment em Team/private registra deployment, mas não pausa. Um único revisor com prevenção de autoaprovação causa deadlock.

CAP-03

Revisão em produção como fato consultável

F2 · F5 · F6

A imagem carrega o SHA, o health o devolve sem cache e o comando só retorna sucesso quando GitHub e runtime concordam. Toda comunicação externa consome o renderer, não texto livre.

# Dockerfile, no estágio final
ARG GIT_SHA=unknown
ENV DUDATA_GIT_SHA=$GIT_SHA

# deploy.yml build args
--build-arg GIT_SHA="${{ github.sha }}" \

// api/health.ts
GET: () => Response.json(
  { status: 'ok', revision: process.env.DUDATA_GIT_SHA ?? 'unknown' },
  { headers: { 'Cache-Control': 'no-store' } },
)

Consulta e nota autorizada

#!/usr/bin/env bash
# scripts/deploy-status.sh
set -euo pipefail
repo='DUDATA-COMPANY/dudata-hub'
wanted="${1:-$(git rev-parse HEAD)}"
deployment="$(gh api "repos/$repo/deployments?environment=production&per_page=1" --jq '.[0] | [.id,.sha] | @tsv' 2>/dev/null || true)"
if [[ -n "$deployment" ]]; then
  deployment_id="${deployment%%$'\t'*}"; deployed_sha="${deployment#*$'\t'}"
  state="$(gh api "repos/$repo/deployments/$deployment_id/statuses?per_page=1" --jq '.[0].state')"
  source="https://github.com/$repo/deployments"
else
  run="$(gh api "repos/$repo/actions/workflows/deploy.yml/runs?branch=main&status=success&per_page=1" --jq '.workflow_runs[0] | [.head_sha,.html_url] | @tsv')"
  deployed_sha="${run%%$'\t'*}"; source="${run#*$'\t'}"; state=success
fi
health="$(curl --fail --silent --show-error https://dudata-hub-prototype.web.app/api/health)"
revision="$(printf '%s' "$health" | mise exec -- bun -e 'const x=JSON.parse(await Bun.stdin.text()); console.log(x.revision ?? "unknown")')"
printf 'wanted=%s deployed=%s state=%s health=%s source=%s\n' "$wanted" "$deployed_sha" "$state" "$revision" "$source"
[[ "$state" == success && "$deployed_sha" == "$wanted" && "$revision" == "$wanted" ]] || exit 3

# scripts/release-note.sh
sha="${1:?usage: scripts/release-note.sh <sha>}"
scripts/deploy-status.sh "$sha" >/dev/null || { printf 'REFUSED: %s is not verified live in production.\n' "$sha" >&2; exit 1; }
printf 'Production is live at %s (verified by deploy state and public health).\n' "$sha"

Falhas possíveis da correção

O primeiro deploy pode reportar unknown se o ARG estiver no estágio errado. Health confirma identidade e disponibilidade, não correção do produto. Rollback faz HEAD divergir de propósito. A leitura de workflow é fallback de migração; Deployments vira a fonte canônica.

CAP-04

Lifecycle como estado executável do repositório

F1 · F6 · F7 · F8

PR liga código a uma issue real via referência interpretada pelo GitHub; o check quality valida o vínculo, build e contrato de processo. Documentação descreve a mesma configuração que CI verifica.

<!-- .github/PULL_REQUEST_TEMPLATE.md -->
## Issue
Closes #NNN
## Verification
- [ ] `mise exec -- bun run test`
- [ ] `mise exec -- bun run typecheck`
- [ ] `mise exec -- bun run build`
## Deployment impact
<!-- None, migration, runtime configuration, or operational risk. -->

# scripts/check-pr-issue-link.sh
count="$(gh api graphql -f query='query($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$number){closingIssuesReferences(first:1){totalCount}}}}' -f owner="$owner" -f repo="$repo" -F number="$PR_NUMBER" --jq '.data.repository.pullRequest.closingIssuesReferences.totalCount')"
[[ "$count" -gt 0 ]] || { printf 'PR must link a real issue with `Closes #NNN`.\n' >&2; exit 1; }

Contrato e teste de drift

# docs/process.md
issue -> isolated branch/worktree -> bootstrap -> verify -> PR ->
CI + independent review -> merge -> production authorization ->
deploy -> queried status -> release note

# scripts/check-process-contract.sh
set -euo pipefail
rules=ops/github/main-ruleset.json
grep -Fq '"~DEFAULT_BRANCH"' "$rules"
grep -Fq '"bypass_actors": []' "$rules"
grep -Fq '"type": "pull_request"' "$rules"
grep -Fq '"context": "quality"' "$rules"
grep -Eq '^[[:space:]]+environment:' .github/workflows/deploy.yml
grep -Fq 'Closes #NNN' .github/PULL_REQUEST_TEMPLATE.md
grep -Fq 'scripts/deploy-status.sh' docs/process.md
if grep -Fq 'workflow_dispatch:' .github/workflows/deploy.yml; then
  grep -Fq 'production uses explicit workflow dispatch' docs/process.md
elif grep -Fq 'branches: [main]' .github/workflows/deploy.yml; then
  grep -Fq 'merges trigger a deployment that waits' docs/process.md
else
  printf 'deploy trigger is neither fallback nor protected target state.\n' >&2; exit 1
fi
if grep -Fq 'faz os merges na `main` em sequência' AGENTS.md; then
  printf 'AGENTS.md still prescribes the incident path.\n' >&2; exit 1
fi

Falhas possíveis da correção

Um PR pode ligar a issue errada; revisão preserva a validação semântica. GitHub só fecha referências destinadas à default branch. O tripwire por grep é deliberadamente simples; um controle agendado deve consultar e normalizar ruleset e Environment vivos.

CAP-05

Gateway tipado para escritas no ClickUp

F4 · F5

Uma CLI estreita basta: vocabulário estável do projeto, validação antes da rede e duas operações permitidas. A nota de release só entra pelo serviço da CAP-03.

export type Fetcher = typeof fetch
const taskIdPattern = /^[A-Za-z0-9_-]+$/
export function clickup(token: string, request: Fetcher = fetch) {
  if (!token) throw new Error('CLICKUP_API_TOKEN is required')
  const call = async (taskId: string, method: string, body: object) => {
    if (!taskIdPattern.test(taskId)) throw new Error('invalid ClickUp task id')
    const url = `https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}${method === 'POST' ? '/comment' : ''}`
    const response = await request(url, { method, headers: { Authorization: token, 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
    if (!response.ok) throw new Error(`ClickUp ${response.status}: ${await response.text()}`)
    return response.json()
  }
  return {
    comment(taskId: string, text: string) {
      if (!text.trim()) throw new Error('comment text is required')
      return call(taskId, 'POST', { comment_text: text, notify_all: false })
    },
    status(taskId: string, status: string) {
      if (!status.trim()) throw new Error('status is required')
      return call(taskId, 'PUT', { status })
    },
  }
}
if (import.meta.main) {
  const [operation, taskId, ...rest] = process.argv.slice(2)
  const api = clickup(process.env.CLICKUP_API_TOKEN ?? '')
  const result = operation === 'comment' ? await api.comment(taskId, rest.join(' '))
    : operation === 'status' ? await api.status(taskId, rest.join(' '))
    : (() => { throw new Error('usage: bun tools/clickup.ts <comment|status> <task-id> <value>') })()
  console.log(JSON.stringify(result))
}
# tools/clickup-release-note.sh
task="${1:?usage: tools/clickup-release-note.sh <task-id> <sha>}"
sha="${2:?usage: tools/clickup-release-note.sh <task-id> <sha>}"
note="$(scripts/release-note.sh "$sha")"
exec mise exec -- bun tools/clickup.ts comment "$task" "$note"

// contract test: invalid input performs zero HTTP calls
let calls = 0
const fake = (async () => { calls++; return new Response('{}') }) as typeof fetch
await expect(clickup('token', fake).comment('bad id!', 'x')).rejects.toThrow('invalid')
expect(calls).toBe(0)

Falhas possíveis da correção

Um id sintaticamente válido ainda pode apontar para a tarefa errada. Token pessoal herda acesso amplo. O wrapper cobre apenas comentários e status. Mudanças da API quebram um adaptador e um contrato centralizados — não cada agente separadamente.

06

O método Pocock

O corpus instalado é forte na modelagem da intenção: vocabulário de domínio, ADRs, specs, tickets verticais, blockers, TDD e review em dois eixos. Mas a máquina termina em commit. PR, merge, deploy e comunicação não são estados nativos do método; sem um dispatcher e gates externos, as skills continuam sendo protocolos opcionais.

6.1

A máquina de estados — e o ponto de fuga

S0

Repo configurado

setup

S1

Problema moldado

grill + docs

S2

Spec publicada

to-spec

S3

Tickets publicados

to-tickets

S4

Frontier pronta

blockers fechados

S5

Implementado

implement + TDD

S6

Revisado

spec + standards

S7

Commit

fim atual

ESTADOS AUSENTES

PR → merge → deploy → comunicação

O roteador ask-matt define grill-with-docs → to-spec → to-tickets → implement/TDD → code-review → commit. O fluxo público em AI Hero confirma a mesma ordem. A inferência é arquitetural: o corpus organiza a produção do commit, mas não possui uma transição de entrega.

6.2

Instalado, parcialmente adotado, não conectado

Elemento esperadoEvidência no dudata-hubEstado real
docs/agents/* + ponte no AGENTSOs três arquivos de configuração do setup e o bloco Agent skills não existem no checkout nem no histórico alcançável.Não configurado
Vocabulário de triageOs cinco labels existem; ready-for-agent, needs-triage e ready-for-human têm uso. Não há mapeamento no repo para as skills.Adoção parcial
UNVERIFIED proveniência do vocabulário
CONTEXT.md + ADRsHá glossário raiz e índice de 13 ADRs, compatíveis com a disciplina de domínio esperada.Artefatos compatíveis
Spec e tickets#382 usa o formato de spec; #383–#388 usam Parent, What to build, Acceptance criteria e Blocked by. Todos estavam ready-for-agent, sem dependências nativas e ainda abertos depois do código integrado.Convenção sem wiring
Harness e despacho.claude/ não contém settings, hooks ou commands. Os manifests centrais usam allow_implicit_invocation: false.Sem dispatcher ou gate
PR e releaseO método termina no commit; o AGENTS local manda integrar em main; não existe ponte obrigatória para PR, aprovação ou release.Estado ausente

Isso não é um defeito de ergonomia das skills. A página pública do setup afirma que ele grava configuração durável e não se invoca sozinho. O limite é precisamente o observado: uma skill melhora decisões quando chamada; não bloqueia uma transição irreversível quando ignorada.

6.3

Transformar o método em infraestrutura determinante

Os artefatos semânticos do método passam a ser entradas de uma máquina controlada pela plataforma: admission valida tickets; o dispatcher cria o trabalho elegível; CI verifica o vínculo issue–PR; o ruleset autoriza merge; a autorização de produção usa dispatch explícito no Team e Environment protegido no Enterprise; o projector deriva a comunicação do estado implantado.

Issue form issue-admission ready frontier Herdr dispatcher worktree + PR pipeline-contract ruleset production authorization release-status ClickUp adapter

A. Admissão e despacho por estado

O workflow valida schema, labels e blockers. Só o dispatcher produz um job de worktree para tickets admitidos e livres.

# .github/workflows/issue-admission.yml
name: issue-admission
on:
  issues: { types: [opened, edited, labeled, unlabeled, reopened] }
permissions: { contents: read, issues: write }
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: bun scripts/validate-ticket.ts
        env:
          ISSUE_JSON: ${{ toJson(github.event.issue) }}

// services/herdr-dispatcher/eligible-ticket.ts
export const isEligible = (ticket: Ticket) =>
  ticket.labels.has("ready-for-agent") &&
  ticket.admission === "valid" &&
  ticket.blockers.every(({ state }) => state === "closed") &&
  !ticket.assignee;

B. Integração e liberação como autoridades separadas

O PR é o join entre código e issue. Um ruleset remoto exige checks de qualidade e rastreabilidade mais uma aprovação. O deploy referencia production, que retém seus segredos até a autorização.

{
  "name": "Main Branch",
  "enforcement": "active",
  "rules": [
    { "type": "pull_request", "parameters": {
      "required_approving_review_count": 1,
      "dismiss_stale_reviews_on_push": true,
      "require_last_push_approval": true,
      "required_review_thread_resolution": true
    }},
    { "type": "required_status_checks", "parameters": {
      "strict_required_status_checks_policy": true,
      "required_status_checks": [
        { "context": "quality" },
        { "context": "traceability" }
      ]
    }}
  ],
  "bypass_actors": []
}

# deploy.yml
jobs:
  deploy:
    environment:
      name: production
      url: https://dudata-hub-prototype.web.app

Rulesets podem exigir PR e checks de status segundo a documentação do GitHub. Jobs que referenciam um environment ficam sujeitos às regras desse environment. VERIFICADO No GitHub Team/private atual, o estado executável é dispatch manual; aprovação independente por revisor de Environment é o estado-alvo Enterprise.

C. Execução e comunicação sem texto livre

O runner recusa worktrees sem manifesto; o adapter aceita apenas um release validado e gera a frase a partir do estado. F3–F5 deixam de depender de recuperação improvisada.

#!/usr/bin/env bash
# scripts/dev-worktree
set -euo pipefail
root="$(git rev-parse --show-toplevel)"
manifest="$root/.worktree/runtime.json"
test -f "$manifest" || { echo "WORKTREE_NOT_BOOTSTRAPPED: $manifest" >&2; exit 78; }
test -f "$root/apps/web/.env.local" || { echo "WEB_ENV_MISSING" >&2; exit 78; }
test -f "$root/packages/backend/.env.local" || { echo "BACKEND_ENV_MISSING" >&2; exit 78; }
exec bun --cwd "$root" run dev

// integrations/clickup/comment.ts
const Comment = z.object({
  taskId: z.string().min(1),
  release: z.object({
    sha: z.string().regex(/^[0-9a-f]{40}$/),
    state: z.enum(["pending", "live", "failed"]),
    runUrl: z.string().url(),
  }),
});

export async function postReleaseComment(input: unknown) {
  const { taskId, release } = Comment.parse(input);
  await clickup.createComment({
    entity_id: taskId,
    entity_type: "task",
    comment_text: clientMessage(release),
  });
}

07

Como a comunidade constrói isso

A unidade de comparação não é o framework nem a persona do agente; é o artefato que carrega confiabilidade. Arquivos versionados, executáveis, sandboxes, APIs, checks de CI e políticas remotas sobrevivem à troca do operador. Prompts e sequências lembradas não sobrevivem.

CapacidadeArtefatos e referênciasAderênciaTransferência para dudata-hub
Ambiente de desenvolvimento como código mise + lock; Dev Containers; Convex multiple/preview deployments; Dagger container-use. Alto Um único mise run bootstrap fixa tools, instala dependências, aloca portas e seleciona um Convex dev temporário por worktree.
Execução isolada e autoridade mínima sandbox do Codex; devcontainer do Claude Code; Convex MCP; loops isolados de Simon Willison. Alto Runtime de código sem principal de produção; apenas credenciais curtas de preview. WIF e segredos de produção existem exclusivamente no job protegido.
Gates de merge e deploy protected branches/rulesets; merge queue; production Environment; feature flags. Alto Agora: PR obrigatório, check estável quality e deploy por workflow_dispatch. No Enterprise: terceiro gate com Environment protegido. Flags reduzem exposição; não substituem autoridade.
Estado de runtime consultável GitHub Deployments + Statuses; Cloud Run Revisions API; Convex deploy metadata. Alto release-status --json une SHA, workflow, revisão, backend, Firebase e health endpoint; mensagens externas só consomem essa projeção.
Pipeline da especificação à entrega OpenSpec; GitHub Spec Kit; Task Master; ExecPlans. Alto Change folders leves, schema validado, issues/PRs gerados e acceptance IDs ligados a evidências de teste. GitHub — não o spec tool — autoriza merge e deploy.
Documentação que não deriva harness engineering; OpenSpec validate/archive; CODEOWNERS. Alto Workflow e scripts viram fonte; um gerador produz a matriz operacional; CI falha se a regeneração sujar o diff ou se referências não resolverem.
Integração tipada e orientada por esquema MCP TypeScript SDK; output schemas; MCP Inspector; tool design da Amp. Alto Cliente gerado/ligado ao registry, Zod em entrada e saída, snapshots de tools/list e erros estáveis antes da chamada remota.

Padrão 1 — bootstrap como interface única

Host e container chamam a mesma tarefa; o app não possui um caminho “dev” que ignore o bootstrap.

# mise.toml
[tools]
bun = "1.3.14"
node = "22"

[tasks.bootstrap]
run = [
  "bun install --frozen-lockfile",
  "./scripts/worktree-env bootstrap --json",
]

[tasks.dev]
depends = ["bootstrap"]
run = "bun run dev"

Padrão 2 — autoridade separada por principal

A política externa ao prompt limita filesystem, rede e credenciais; identidade de deploy é null no runtime de código.

filesystem:
  write: ["$WORKTREE"]
network:
  allow: ["registry.npmjs.org", "api.convex.dev"]
credentials:
  convex: "preview:${WORKTREE_ID}"
  gcp_deploy: null
  github: "contents:write,pull_requests:write"
deny_commands:
  - "gcloud run deploy"
  - "firebase deploy"
  - "convex deploy --prod"

Padrão 3 — verdade de liberação por SHA

Cada sistema expõe seu estado observado; divergências bloqueiam a renderização de mensagens.

{
  "sha": "a195b2e...",
  "github": {"environment":"production","state":"success"},
  "cloud_run": {"revision":"hub-a195b2e","traffic":100},
  "convex": {"message":"sha=a195b2e"},
  "firebase": {"state":"success"},
  "public": {"sha":"a195b2e","healthy":true}
}

Padrão 4 — contrato MCP testável

Registry e structured result deixam de ser contexto para o modelo e viram entradas do build.

const result = await client.callTool({
  name: "task_comment",
  arguments: input,
});
const parsed = TaskCommentResult.safeParse(
  result.structuredContent,
);
if (!parsed.success) {
  throw new ToolContractError(
    "INVALID_TOOL_RESULT",
    parsed.error,
  );
}

expect(await client.listTools())
  .toMatchFileSnapshot("contracts/tools-list.json");

Lista curta ranqueada

“Steal this” para o dudata-hub

  1. 1

    Ruleset de main

    PR, check quality, sem force-push nem bypass rotineiro.

  2. 2

    Deploy explícito no plano atual

    workflow_dispatch no dia um; aprovação e credenciais restritas por Environment depois da migração para Enterprise.

  3. 3

    mise.toml + lock + tasks

    Bootstrap/dev como interface única do checkout.

  4. 4

    scripts/worktree-env

    Portas, config local e Convex dev expirável por worktree.

  5. 5

    Preview Convex por PR

    Backend semeado, isolado e expirável para validação real.

  6. 6

    Manifesto + release-status

    SHA live e mensagens externas derivados de fatos.

  7. 7

    Cliente MCP tipado

    Zod em input/output e snapshots do registry.

  8. 8

    Config-to-doc generator

    Diff de regeneração e contratos documentais no CI.

  9. 9

    OpenSpec leve

    Change folders, validação e mapping de evidência de aceite.

  10. 10

    Devcontainer de agentes

    Usuário não-root, egress allowlist e zero credencial de produção.

08

Plano de rollout

A ordem começa pelos caminhos irreversíveis, torna o trabalho reproduzível e só então adiciona controles que dependem de plano ou integração externa.

PrioridadeObjetivoCapacidadesCritério de saída
P0Fechar caminhos silenciosos para main e produçãoRuleset sem bypass; fallback Team/private com workflow_dispatch; revisão no health, deploy-status e release-notePush direto é rejeitado, merge não publica automaticamente e a máquina servida é identificada por SHA.
P1Tornar checkout e lifecycle reproduzíveismise.toml, bootstrap, preflight, portas por worktree e adaptador Herdr; gates de vínculo issue–PR e drift de processoDuas worktrees sobem sem wizard e sem colisão; PR sem issue real e contrato divergente falham no check quality.
P2Adicionar aprovação independente e integração externa estreitaEnvironment protegido após GitHub Enterprise; CLI ClickUp tipada e testes de contratoLiberação exige autoridade diferente do autor e nenhuma escrita no ClickUp contorna o adaptador validado.

Artefatos do dia um

  1. 1. Aplicar o ruleset de main sem bypass.
  2. 2. Trocar deploy.yml para workflow_dispatch; tratar Enterprise em trilha separada.
  3. 3. Entregar bootstrap-worktree.sh + predev.ts como um contrato e prová-lo em duas worktrees.

09

Fontes

  • Contexto neutro do incidentereports/raw-findings-2026-07-21/00-incident-context.md
  • Análise forense v1 — tabela F1–F8reports/raw-findings-2026-07-21/findings-forensic.md
  • Frameworks e ferramentas de especificaçãoreports/raw-findings-2026-07-21/findings-frameworks.md
  • Práticas comunitárias de agentes e entregareports/raw-findings-2026-07-21/findings-practices.md
  • Auditoria do repositório — inventário e controles remotosreports/raw-findings-2026-07-21/findings-repo-audit.md
  • Síntese SOL — riscos e intervençõesreports/raw-findings-2026-07-21/findings-sol.md
  • Mapa comunitário por capacidadereports/raw-findings-v2/findings-community-capabilities.md
  • Método Pocock — corpus instalado e wiringreports/raw-findings-v2/findings-pocock-method.md
  • Forense de sistemas — F1–F8 e síntese de capacidadesreports/raw-findings-v2/findings-systems-forensics.md
  • Desenho verificado da plataforma — controles, sketches e rolloutreports/raw-findings-v2/findings-platform-design.md

Referências externas principais

Nota de segurança: linguagem pessoal sensível presente no material de origem não é reproduzida. Seu impacto é registrado de forma neutra.