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.
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
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.
Seis issues (#383–#388) foram executadas em branches e worktrees separadas. O isolamento do trabalho funcionou como previsto.
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.
main tornou-se deployO 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.
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.
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
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
“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
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
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.
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
main porque o cliente pôde atualizar a ref com um push.main.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: []
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.
Sintoma. deploy-prod rodava em todo push a main, sem environment:, e publicou Cloud Run, Convex e Firebase.
Cadeia 5-whys
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.
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.
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
git worktree add não copia arquivos ignorados nem executa bootstrap.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"
}
}
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.
Sintoma. Chamadas usaram taskId, task_id e depois entity_id; cinco requisições falharam antes do formato aceito.
Cadeia 5-whys
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,
});
}
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.
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
is SHA X live?.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>
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.
Sintoma. Documentos e semântica do deploy foram examinados só depois de merge, push, deploy e comunicação.
Cadeia 5-whys
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]
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.
Sintoma. AGENTS.md prescrevia merge sequencial em main; a convenção histórica usava PRs.
Cadeia 5-whys
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 -->
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.
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
main e produção não exigiam seus respectivos registros de autoridade.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
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 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.
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.
# 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"
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())) })
// 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.
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.
{
"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
}}
]
}
# 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.
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' } },
)
#!/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.
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; }
# 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.
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 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.
Repo configurado
setup
Problema moldado
grill + docs
Spec publicada
to-spec
Tickets publicados
to-tickets
Frontier pronta
blockers fechados
Implementado
implement + TDD
Revisado
spec + standards
Commit
fim atual
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.
| Elemento esperado | Evidência no dudata-hub | Estado real |
|---|---|---|
docs/agents/* + ponte no AGENTS | Os 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 triage | Os 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 + ADRs | Há 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 release | O 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.
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.
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;
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.
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
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.
| Capacidade | Artefatos e referências | Aderência | Transferê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. |
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"
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"
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}
}
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
Autoridade remota e ambiente determinístico vêm antes de frameworks de especificação.
Ruleset de main
PR, check quality, sem force-push nem bypass rotineiro.
Deploy explícito no plano atual
workflow_dispatch no dia um; aprovação e credenciais restritas por Environment depois da migração para Enterprise.
mise.toml + lock + tasks
Bootstrap/dev como interface única do checkout.
scripts/worktree-env
Portas, config local e Convex dev expirável por worktree.
Preview Convex por PR
Backend semeado, isolado e expirável para validação real.
Manifesto + release-status
SHA live e mensagens externas derivados de fatos.
Cliente MCP tipado
Zod em input/output e snapshots do registry.
Config-to-doc generator
Diff de regeneração e contratos documentais no CI.
OpenSpec leve
Change folders, validação e mapping de evidência de aceite.
Devcontainer de agentes
Usuário não-root, egress allowlist e zero credencial de produção.
08
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.
| Prioridade | Objetivo | Capacidades | Critério de saída |
|---|---|---|---|
| P0 | Fechar caminhos silenciosos para main e produção | Ruleset sem bypass; fallback Team/private com workflow_dispatch; revisão no health, deploy-status e release-note | Push direto é rejeitado, merge não publica automaticamente e a máquina servida é identificada por SHA. |
| P1 | Tornar checkout e lifecycle reproduzíveis | mise.toml, bootstrap, preflight, portas por worktree e adaptador Herdr; gates de vínculo issue–PR e drift de processo | Duas worktrees sobem sem wizard e sem colisão; PR sem issue real e contrato divergente falham no check quality. |
| P2 | Adicionar aprovação independente e integração externa estreita | Environment protegido após GitHub Enterprise; CLI ClickUp tipada e testes de contrato | Liberação exige autoridade diferente do autor e nenhuma escrita no ClickUp contorna o adaptador validado. |
Artefatos do dia um
main sem bypass.deploy.yml para workflow_dispatch; tratar Enterprise em trilha separada.bootstrap-worktree.sh + predev.ts como um contrato e prová-lo em duas worktrees.09
reports/raw-findings-2026-07-21/00-incident-context.mdreports/raw-findings-2026-07-21/findings-forensic.mdreports/raw-findings-2026-07-21/findings-frameworks.mdreports/raw-findings-2026-07-21/findings-practices.mdreports/raw-findings-2026-07-21/findings-repo-audit.mdreports/raw-findings-2026-07-21/findings-sol.mdreports/raw-findings-v2/findings-community-capabilities.mdreports/raw-findings-v2/findings-pocock-method.mdreports/raw-findings-v2/findings-systems-forensics.mdreports/raw-findings-v2/findings-platform-design.mdNota de segurança: linguagem pessoal sensível presente no material de origem não é reproduzida. Seu impacto é registrado de forma neutra.