teqo-backup/docs/AGENT-OPS.md
Francisco Solla f6a6d8e42b chore(OPS123): reserva o provider openai aos agentes de design (Closes #1091)
Nenhum modelo openai/* (luna/sol-fast/terra inclusive) fora dos agentes de design, por /models, --model, small_model ou config. Cobre o uso de gpt-5.6-luna em sessoes build. Regra always-on no AGENTS.md + doutrina/AGENT-OPS; guard estatico em tests/unit/openaiProviderScope.unit.spec.ts.
2026-09-16 11:10:44 -03:00

41 KiB
Raw Permalink Blame History

AGENT-OPS — operação do paradigma de agentes paralelos

Uma página. Norte: simplicidade (GitHub = tracker E Actions — OPS76), agente entrega em main, deploy começa automático no merge e a produção continua manual (reviewer) com verificação full (OPS71 + OPS104).

Ambientes de dados (test ladder — nunca misturar)

Ambiente Conteúdo Quem usa
Mínimo (teqo_test local / service CI) schema migrado + pnpm db:seed:minimal (sintético, sem PII) agentes, Cursor Cloud, CI
Staging (teqo_staging, homeserver) sintético (db:seed:minimal) job deploy-staging do deploy.yml (OPS103); inspeção humana antes da aprovação de produção
Prod (teqo_1313, homeserver) real job deploy-production do deploy.yml (OPS53 + OPS71 + OPS103)

Agentes nunca recebem DATABASE_URL de prod/staging e nunca setam ALLOW_REMOTE_DB. Não há mais smoke stage / ALLOW_STAGE_TEST_DB / ci-stage.yml — o staging da OPS103 é alvo de promoção de um SHA de main (merge em main ou workflow_dispatch → staging → aprovação → produção), nunca destino de PR.

Fluxo

claim → feature branch → PR --base main (GitHub) → CI PR green (cascade + skips) → auto-merge nativo em main (rebase)
main → push dispara deploy.yml sozinho (ou workflow_dispatch manual) → preflight (pula se já há deploy ativo) → verify full verde → deploy-staging → aprovação do environment production → deploy-production; requeue re-despacha se main andou no meio (OPS53 + OPS103 + OPS104 + OPS107 — o approval de produção não ocupa a lane compartilhada; runs novos seguem staging e aprováveis)

Skills: plan-issue (intenção + Issues) → work-issue (humano: Issue já claimada → impl plan → confirmação → execução) ou agent-work-issue (pool: já claimada → impl plan → execução sem pausa) → /simplify → capture-review-debts → PR --base main (GitHub) → project-status. docs/roadmap.md = legado congelado; fonte canônica = Issues do GitHub (github.com/fsolla/teqo/issues).

Design hi-fi (OPS113): item que muda UI passa pelo agente designer (model: openai/gpt-5.6-sol; crítica contestada/pixel-critical em openai/gpt-6-astra, mesmo agente via /models); a doutrina .agents/skills/plan-issue/ui-design-html.md é a fonte única do artefato docs/plans/<slug>-ui-design.html (+ -ui-design-assets/*.svg), que é a fonte de verdade do port — o implementador porta classe-a-classe. Indisponibilidade/quota desce a ladder — opencode-go/deepseek-v4.1-flash (agente designer-degraded) → deepseek/deepseek-flash (inline pelo orquestrador) — sempre gravando o tier no PR (Design tier:) — ou na Issue, no fluxo autônomo sem tier primário, que não abre PR e flipa para blocked; tier degradado marca DEGRADED, não certifica e para em sign-off humano (triggers no work-issue/agent-work-issue: OPS115). Ambos têm permission fail-closed: edit negado fora de docs/plans/*-ui-design* e bash com os vetores de escrita enumerados negados (redirecionamento, sed -i, tee, shells/interpretadores — OPS117), preservando leitura e o prettier --write do artefato; o resto do shell fica em ask (o guard cobre os vetores, não é sandbox de SO). Escopo de dispatch fechado (ui-design-html.md §Escopo de dispatch): o tier frontier só roda Criar/Criticar o artefato de item que muda UI — smoke/validação de permission/config, teste de visão, exploração e escrita de plano vão para o modelo padrão da sessão (o smoke do guard dos agentes de design roda no designer-degraded, barato, pois a semântica de permission é do frontmatter, não do modelo); prefira uma sessão para N itens UI a N sessões frias; agente de design sem pin (designer-campanha-solla) herda o modelo da sessão — dispare a sessão barata e só troque para frontier no design. Provider openai reservado ao design: nenhum outro agente/sessão — nem small_model, compaction ou worker de orquestração — troca para openai/* (gpt-5.6-luna/-fast, sol-fast, terra inclusive) por /models, --model ou config; o resto roda no modelo padrão (deepseek/deepseek-flash), e a reserva cobre todo o consumo do provider, não só o dispatch do designer.

  • Agente faz sozinho: claim (pool-supervisor no pool; humano via pnpm agent:claim fora da sessão — OPS33: worktree next claima) → implementa → e2e local afetado (OPS72, discricionário: roda os e2e que criou + os da mesma superfície — pnpm test:e2e:affected; ver skill) → pnpm push (origin = GitHub) → PR via node scripts/github-pr.mjs Ready (nunca draft) com Closes #N → o safety net agent-pr-ready-automerge.yml arma o auto-merge nativo do GitHub (enablePullRequestAutoMerge, rebase) — o servidor mergea quando o required check CI (PR) / checks fica verde. Regra always-on: .agents/rules/agent-pr-workflow.mdc. Em Cursor Cloud: ManagePullRequest com draft: false (o default draft da tool não vale neste repo) — o merge continua pelo safety net.
  • Só humano: aprovar o environment production no run (o único gate de publicação — OPS104 nunca auto-aprova; OPS107: aprove só o run escolhido e rejeite os demais — runs não aprovados não publicam nada), envs de produção no homeserver (~/stack/teqo-1313.env, fora do repo — o deploy as lê via BuildKit secrets), GITHUB_TOKEN local (PAT) para os scripts GitHub, secrets no repo GitHub, pnpm configure:branch-protection (idempotente; reaplicar se o drift voltar), runbook manual/rollback (docs/ops/teqo-1313-deploy.md).
  • Falha do verify (OPS106): o deploy não fica mais parado esperando alguém notar — auto-unblock.yml (workflow_run no Deploy, só push em main e só job verify vermelho) dispara UM agente autônomo de desbloqueio no homeserver com o mesmo contrato do worktree fix (worktree fix/*, banco local teqo_wt*, opencode + /bug-fix, PR Ready + auto-merge). Single-flight: Issue token auto-unblock + /tmp/teqo-unblock.lock; falha com agente ativo só comenta no token (nunca enfileira). Guardrails fail-closed de banco/ambiente, cap de 4h, sem auto-retry do deploy e sem auto-approve de produção. Bootstrap/operação: docs/ops/teqo-1313-deploy.md (seção Auto-unblock) + pnpm unblock:check. O AUTOMERGE_PAT é entregue ao processo destacado (push/PR/outcome) por credential helper não-persistente — o built-in token fica só no launcher síncrono.

Dono do PR, dono do CI

Done = PR mergeado em main com CI verde. Falha de CI no teu PR — infra, teste pré-existente ou regressão — é tua até o merge. Exceção de blast radius: migration / access / Consent → pare e escale.

Bypass de CI (cutover / transição)

  • Local: se gate:push ainda espelha pipeline antigo e bloqueia push legítimo da PR que o muda → git push --no-verify permitido como escape de transição (não WIP permanente). Documentado aqui; após o cutover, entregas passam no CI novo sem bypass.
  • Cutover humano: pode relaxar temporary required checks se o flip de proteção travar merges; reverter para CI (PR) / checks em main assim que o CI novo estiver verde.

Comandos

Comando Faz
pnpm agent:claim [-- --dry-run] Fila ready+unblocked por prio → in-progress + brief (GitHub)
pnpm worktree next [--issue N] [--stay] Claim determinístico (mesma fila/lock do claim) → worktree + provisionamento
pnpm worktree fix [bug] [--stay] Worktree de correção de bug (fix/<slug>, skill /bug-fix — o launch envia a descrição do bug); não claima nem cria Issues
pnpm agent:session <serve|start|attach|stop|list> [--json] Sessões persistentes do run (OPS110 — attach/detach; ver seção abaixo)
pnpm unblock:check Smoke do bootstrap do agente de desbloqueio no homeserver (OPS106; exige GITHUB_TOKEN + AUTOMERGE_PAT)
pnpm agent:register -- --id X --title T [--prio P1] [--depends A,B] [--plan docs/plans/x.md] [--model slug] Cria Issue no GitHub (--plan ⇒ blocked até agent:ready; sem plano ⇒ ready)
pnpm agent:ready -- --issue N[,N…] Pós-merge do plano: blocked→ready (só Issues com link docs/plans/)
pnpm agent:status Overview / fila / mermaid
pnpm agent:prioritize -- <issue> <P0..P3> Troca prio:*
pnpm agent:file-miss -- --title ... Issue kind:agent-miss
pnpm agent:pool -- status|tick --dry-run|doctor Pool Cloud dormente (OPS65) — o workflow agent-pool.yml foi removido; dispatch falha 404; scripts/skill ficam para histórico
pnpm push [-- -u origin HEAD …] Canônico: ensure-deps + gate:push + push para o GitHub (origin)
GITHUB_TOKEN=… node scripts/github-pr.mjs --head <b> --title <t> --body-file <f> Abre PR no GitHub (Ready, base main, nunca draft)
GITHUB_TOKEN=… pnpm configure:branch-protection [-- --dry-run] Proteção de main no GitHub: required check CI (PR) / checks (idempotente — cria/atualiza/no-op)
pnpm db:seed:minimal DB mínimo sintético

Labels (GitHub): ready|in-progress|blocked|done|in-prod, prio:*, kind:*, needs:*, auto-unblock (token do single-flight do agente de desbloqueio — OPS106; fora da fila de claim).

plan-issue (OPS17 + OPS18): gate pós-overview fecha o lote antes de qualquer Issue/PR; register com --plan nasce blocked (não entra na fila do claim/pool); após o PR de planos mergear em main (Related #N), promote dual idempotente: (A) pnpm agent:ready na sessão; (B) Action plan-issue-ready-on-main-merge.yml lê Related #N e promove Issues ainda “aguardando plano” (blocked + link docs/plans/). Chores sem plano continuam nascendo ready.

Sessões persistentes (attach/detach — OPS110)

O launch do terminal (worktree next|plan|new|fix) não abre mais um TUI dono da sessão: ele chama pnpm agent:session start …, que sobe/reaproveita o opencode serve compartilhado, cria a sessão endereçável, dispara o driver destacado (opencode run --attach … --auto --command <skill>, que auto-aprova permissões enquanto vive) e anexa o TUI (opencode attach -s <sessionID>). Fechar o terminal/TUI desconecta — o run continua; encerrar é ato explícito.

Comando Faz
pnpm agent:session serve [--hostname=H] [--port=P] Sobe/reaproveita o servidor (loopback 127.0.0.1:4199; TEQO_AGENT_SERVER_HOST/PORT)
pnpm agent:session start --purpose=<next|plan|new|fix> --dir=<D> --model=<M> [--issue=N] [--argument="<bag>"] [--new] Cria a sessão, dispara o driver (quando há comando a auto-submeter — plan sem bag é driverless) e anexa o TUI
pnpm agent:session attach [--session=<ses_…>|--branch=<B>|--issue=N] (Re)entra na sessão — default: branch do cwd; sobe o servidor se ele estiver fora
pnpm agent:session stop [--session=<ses_…>|--branch=<B>|--issue=N] Ato explícito de encerrar: abort via API + fim do driver + stoppedAt
pnpm agent:session list [--json] Status dos runs (working|idle|stopped|unknown); --json é o contrato do OPS109
  • Endereçamento (OPS109): ~/.local/state/teqo/agent-sessions/<slug>.json (TEQO_AGENT_SESSION_DIR > XDG_STATE_HOME) guarda version, code, issue, purpose, branch, dir, sessionID, url, model, driverPid, logPath, startedAt, stoppedAt; list --json entrega {version, server, sessions:[…status]} — é a única fonte para "abrir a sessão da Issue".
  • Sair ≠ encerrar: fechar o TUI/terminal não mata o run (verificado: SIGKILL no cliente e tmux kill-session não abortam; attach replayer histórico e streaming ao vivo). Ctrl+C no TUI limpa o input; sair é fechar o terminal ou o keybind app_exit (ctrl+c,ctrl+d,<leader>q).
  • Reuso: start com driver vivo ou sessão busy → apenas anexa (nunca dois runs no mesmo branch); caso contrário cria sessão nova (--new força). worktree kill encerra a sessão do worktree antes de destruí-lo (estado/lock removidos — sem sessão órfã).
  • Segurança: loopback por padrão; --hostname fora do loopback exige OPENCODE_SERVER_PASSWORD (fail-closed) e --mdns nunca é usado. A credencial vive só no ambiente — nunca no estado/log; loopback sem senha roda sem auth (aceito pelo produto: nada além de 127.0.0.1).
  • Modos de falha: morte do driver deixa pedidos de permissão pendurados até um attach responder (o stop sempre aborta via API, sem depender do driver); reboot derruba o run (a sessão/histórico persiste e pode ser reanexada); o daemon do servidor fica de pé (idle, loopback) — parada manual pelo pid em agent-sessions/server.json.

Contrato de PR

  1. Schema → atualizar db:seed:minimal no mesmo PR (needs:migration).
  2. Migrations NÃO são serializadas entre PRs: o CI (migrate + int, incl. campaignMigrationReconciliation) valida a cadeia em todo PR; rebase antes de migrate:create continua obrigatório.
  3. Ready + auto-merge: pnpm push -u origin HEAD (GitHub) → PR via node scripts/github-pr.mjs (Ready, base main, Closes #N) → o safety net agent-pr-ready-automerge.yml (scripts/github-pr-automerge.mjs) arma o auto-merge nativo do GitHub por rebase — o servidor espera o required check CI (PR) / checks e mergea; nada a armar por PR, não há gh (OPS50) e não há poll (OPS71). Merge é por rebase (nunca merge commit): preserva a linha histórica limpa do main e é o modo do auto-merge.
  4. Changelog da entrega (OPS44, OPS85): toda entrega escreve docs/changelog/<data>-<id>.md (ex. 2026-08-13-ops44.md) — o único registro commitado do histórico. O agregado docs/CHANGELOG-AGENTS.md é um artefato gitignored regenerado sob demanda (OPS85): pnpm changelog:build grava o arquivo local, pnpm changelog:read imprime no stdout, ambos com seed do snapshot congelado docs/CHANGELOG-AGENTS-HISTORY.md quando o arquivo local não existe — nunca commitar o agregado nem editar o HISTORY na mão. Guard de CI docs-guards: (a) docs/changelog/ é additions-only (M/D falham); (b) o HISTORY é congelado (só a criação é permitida); (c) o agregado nunca aparece no diff de um PR (a remoção única de OPS85 foi a exceção que criou o HISTORY); (d) nenhum marcador de conflito em diffs de markdown (docs/, AGENTS.md, .agents/ — cobre PRs docs-only, onde a suíte unit não roda). Desde OPS63 os mesmos checks rodam também no pre-push local (gate:push → gate:ci, fase 1 — mesmos scripts, default origin/main). Restauração legítima (header muda, restauração estilo D8, correção do HISTORY) escapa escrevendo changelog-rewrite: <motivo> como linha própria no body do PR — definição canônica aqui; o texto do checkbox do PR template nunca ativa (linha-ancorado). O escape é CI-only (body do PR): no pre-push local a restauração legítima falha com a mesma mensagem do CI — bypass documentado é git push --no-verify direto (o CI, com o escape no body, continua sendo a via da restauração).

Registries compartilhados (serialização por arquivo, não por PR)

Arquivos de "lista" que N agentes escrevem em paralelo (plans, migrações, catalog) conflitam por construção: cada escrita reescreve o arquivo inteiro. Regras:

  • Cada entrega escreve UM arquivo por registro (docs/plans/<slug>.md, migration com nome único, etc.) — o arquivo comum é derivado (build/CI) ou só-leitura por PR (guard). O changelog é o caso resolvido em OPS85: o registro commitado é por-arquivo (docs/changelog/<data>-<id>.md), o agregado legível virou artefato regenerado gitignored e o snapshot docs/CHANGELOG-AGENTS-HISTORY.md é só-leitura por guard.
  • serializes no plano de intenção nomeia o registro exato que a Issue toca (ex. serializes: 'docs/plans/'), para o executor agendar com clareza — é dica de ordem, não lock (rebase continua obrigatório).
  • O rebase (Contrato de PR item 3) é o mecanismo real de conciliação: conflitos de append resolvem com git checkout --ours/--theirs por lado.

Decisões arquivadas

  • Docs agent-facing (changelog, plans) NÃO migram para fora do repo (arquivado 2026-08-24, OPS85): a evidência da OPS85 matou a pergunta — o conflito de merge era do agregado (arquivo comum derivado, 171 blocos reescritos no mesmo anchor), não das entradas por-arquivo (imunes por construção desde OPS44) nem dos plans (sempre funcionaram). Migrar 12+ skills com paths hardcoded + o ciclo claim→plano→PR para um alvo externo trocaria conflito de merge por desincronização de registro que os guards não enxergam (anti-goal "segunda fonte de verdade"). Se um dia houver custo novo que justifique, o alvo registrado é Wiki.js no homeserver (Node+DB, revisões por página, edição paralela sem git); Outline é segunda opção quando o custo de infra adicional se justificar; Pages estático é a resposta errada para a classe "conflito de git"; coleção Doc no Payload acopla docs ao deploy. Nada foi instalado.

CI por alvo (GitHub Actions, OPS71)

Workflow Trigger Banco Passos
ci-pr.yml PR → main (same-repo) 1× service Postgres 17 Job único checks sequencial (OPS62 X1): guards → lint→format→typecheck→knip→cycles→unit→int→build→e2e; skips via ci-scope.mjs (step scope); fail-fast estrutural — step vermelho interrompe o job, o run falha; concurrency nativo cancela o run em push novo; e2e apenas blast radius — selected ou, em high-risk, o conjunto curated (nunca full — OPS72/OPS86); área de risco sem mapping falha unmapped-risk; unit/int changed com seleção vazia cai em full (vitest-changed-or-full.mjs) — em 1 processo com PLAYWRIGHT_WORKERS=4; 1 service postgres-int (localhost:5432) com pnpm db:reset (drop→migrate→seed:minimal) entre int e build/e2e (OPS88 — substitui o 2º container); fork-PR excluída no if: do job (repo público — não queima minutes)
deploy.yml push em main (automático, OPS104) + workflow_dispatch 1× service Postgres 17 Cinco jobs (OPS103 + OPS104 + OPS107): preflight (hosted; scripts/deploy-preflight.mjs pula o run se já há deploy queued/in_progress — waiting de approval não conta; dispatch manual pula o guard) → verify (hosted, suíte full sem skips — lint→format→typecheck→knip→cycles→unit→int→db:reset→build .next-e2e→e2e full; OPS88: um build só, o default saiu) → deploy-staging (needs: [verify], environment: staging, TEQO_ENV=staging) → requeue (hosted, fora do grupo deploy-homeserver; scripts/deploy-requeue.mjs re-despacha deploy.yml se o head de main andou — OPS104) e deploy-production (needs: [deploy-staging], environment: production com required reviewer, TEQO_ENV=production, sem concurrency desde a OPS107 — o approval não segura a lane, que vive só no staging); ambos os deploys rodam em runs-on: [self-hosted, homeserver] e executam scripts/deploy-homeserver.sh <sha> localmente no homeserver (sem SSH; roda até o fim com o SHA do run mesmo se main andar durante o verify — OPS102; idempotente; um flock compartilhado é a serialização real entre runs (o concurrency: deploy-homeserver com queue: max vive só no deploy-staging — OPS107); migrator→migrate→runner→rollout→smoke) — staging vermelho fail-closa produção; nada publica sem o approve do reviewer (aprove só o run escolhido; rejeite os demais)
issue-done-on-main-merge.yml PR merged → main (same-repo) + dispatch manual (recovery) — Closes/Fixes → done + in-prod + close no GitHub (token GITHUB_TOKEN; body do PR via PR_BODY do evento GitHub — no dispatch, busca via gh com o mesmo contrato: merged + main + same-repo)
plan-issue-ready-on-main-merge.yml PR merged → main (same-repo) + dispatch manual (recovery) — Related #N aguardando plano → ready no GitHub (OPS18; soft-skip; mesmo token)
agent-pr-ready-automerge.yml PR same-repo → main (open/sync/…) — Safety net via github-pr-automerge.mjs (plain Node, REST+GraphQL): draft cursor/* → Ready (PATCH); arm enablePullRequestAutoMerge (rebase) com o AUTOMERGE_PAT (fail-closed — nunca o GITHUB_TOKEN nativo, cujo merge como github-actions[bot] não cria runs no closed; OPS71-FLIP) — o servidor espera o required check CI (PR) / checks (o OPS64 "rollup pode mentir" é estrutural aqui: job único posta um check-run honesto); draft não-cursor/* = veto, skip
archive-cursor-agent.yml dispatch manual (ops) — Helper: cancela run + arquiva um agente Cursor Cloud (CURSOR_API_KEY) — dormente (OPS65) como o pool

agent-pool.yml foi removido no OPS65 (pool dormente; o tick */10 custava 144 pnpm install/dia na workstation). O verificador de main (ci.yml) foi eliminado no OPS71 (gate 2026-08-19): só o verify do deploy.yml (iniciado por push em main ou dispatch manual) roda a suíte full.

Action runtimes: actions/checkout@v5, actions/setup-node@v5, pnpm/action-setup@v6 e actions/cache@v4.

Fast gate: pnpm gate:fast. Push: pnpm push.

Secrets (humano, uma vez — repo GitHub)

Secret Uso
AUTOMERGE_PAT Arm do auto-merge nativo (agent-pr-ready-automerge.yml) — PAT de usuário real (fsolla) com pull-requests: write + contents: write no repo. Sem ele o job falha fail-closed: o GITHUB_TOKEN nativo armado faria o merge como github-actions[bot] e o closed do PR não criaria runs (anti-recursão do GitHub) — os flips pós-merge ficariam silenciosamente mortos (OPS71-FLIP, PR #746)
CURSOR_API_KEY Supervisor do pool (Cursor Cloud) — dormente (OPS65); só o helper archive-cursor-agent.yml a usa hoje

Local: GITHUB_TOKEN (PAT com escopo repo + issues: write) para node scripts/github-pr.mjs, pnpm configure:branch-protection, pnpm issue/agent:* e qualquer script GitHub — o push SSH usa a chave ~/.ssh/id_ed25519 registrada no GitHub. Envs de produção (DB, S3, VAPID, REVALIDATE_SECRET, NEXT_PUBLIC_SITE_URL) vivem em ~/stack/teqo-1313.env no homeserver — nunca como secrets do GitHub. POOL_GITHUB_TOKEN é legado (era da transição GitHub pré-OPS50). FORGEJO_API_TOKEN (git.solla.dev) é legado da era pré-OPS76 — o tracker migrou para o GitHub e o cliente forgejo-api.mjs foi removido.

Flips pós-merge (OPS71-FLIP): o evento closed e a recuperação manual

O flip issue-done/plan-issue-ready roda no pull_request: closed do PR no GitHub — mas o merge feito pelo auto-merge armado com o GITHUB_TOKEN nativo é atribuído a github-actions[bot] e NÃO cria workflow runs (anti-recursão documentada do GitHub: eventos acionados por ações do GITHUB_TOKEN não geram runs; o closed fica no timeline do PR com 0 runs — achado ao vivo no PR #746). Isso só não acontece porque o arm agora usa o AUTOMERGE_PAT (merge atribuído a usuário real → runs criados, comportamento do PR #742). Desde a OPS104 o mesmo invariante também governa o deploy automático: um merge via GITHUB_TOKEN não criaria o run de push do deploy.yml — o AUTOMERGE_PAT é o que mantém trigger 1 (e os flips) funcionando.

Se mesmo assim o flip não rodou (evento perdido, run falhou, PR mergeado por bot antes do fix): o workflow tem workflow_dispatch de recuperação — dispatch de issue-done-on-main-merge.yml (ou plan-issue-ready-on-main-merge.yml) com o input pr = número do PR; o job busca o PR via gh e aplica o mesmo contrato do evento (merged + base main + same-repo, senão skip). O flip fecha a Issue (PATCH state=closed — o closed do GitHub por keyword pode fechar, mas o flip explicita labels done/in-prod + close para o tracker GitHub). Falha em qualquer passo = job vermelho (OPS61, nunca mentir).

Branch protection de main no GitHub (aplicada no cutover do OPS71, via pnpm configure:branch-protection): required check CI (PR) / checks (a UI exibe workflow/job; o literal de match é checks — o GitHub casa pelo nome do check-run — pin ao vivo PR #742), strict=false, 0 reviews, enforce_admins: true (nem admin mergea com CI vermelho — mesmo invariante do rule-based Forgejo). O auto-merge nativo espera esse mesmo check antes de mergear; a regra no servidor é o gate real de qualquer caminho de merge, inclusive API manual. Reaplicar/consertar drift: pnpm configure:branch-protection (idempotente).

Deploy (OPS53 + OPS71 + OPS103 + OPS104 + OPS107): começa automático — um push em main dispara o deploy.yml (o job preflight pula se já há deploy queued/in_progress; um run waiting de approval não conta) e workflow_dispatch segue como escape manual. O job verify (hosted, suíte full incl. e2e full, intocado — verifica o commit uma vez) roda antes dos deploys: deploy-staging (environment: staging, sem reviewer) e então deploy-production (needs: [deploy-staging], environment: production com required reviewer; staging vermelho nunca deixa produção rodar). Desde a OPS107 o deploy-production não tem concurrency: o approval pendente não segura o staging nem a aprovação de runs novos — o grupo vive só no deploy-staging (aprove só o run escolhido e rejeite os demais). Depois do staging verde, o job hosted requeue compara o head de main com o SHA do run e re-despacha um run novo se main andou no meio — fora do grupo de concurrency, para um approval pendente não bloquear a convergência. Os dois deploys executam scripts/deploy-homeserver.sh <sha> no runner self-hosted do GitHub no homeserver (conecta outbound ao GitHub — funciona atrás do Cloudflare tunnel; o hosted nunca toca o homeserver; o self-hosted não conta minutos hosted), parametrizado por TEQO_ENV (production default intocado; staging = teqo-staging/teqo_staging/~/stack/teqo-staging.env/127.0.0.1:1314). O script: flock único no host (compose/workspace/registry são compartilhados) → guard "already deployed" (revision do container do ambiente) → build do migrator (BuildKit, secrets do env file do ambiente) → push/tag em localhost:5000 → swap dos tags no compose (backup; sed ancorado ao serviço do ambiente) → migrations (teqo-1313-migrate/teqo-staging-migrate — antes do build do runner, OPS66) → build do runner (contra o banco JÁ migrado) → rollout → healthcheck → smoke. Desde a OPS102 o deploy roda até o fim com o SHA do run mesmo se main avançar durante o verify; a convergência é do requeue da OPS104 (o antigo guard de stale run segue morto); o único verde sem build é o "already deployed", que prova que o container já roda o SHA. Falha = job vermelho; nada é publicado pela metade. Runbook (rollback, falhas conhecidas, bootstrap do staging): docs/ops/teqo-1313-deploy.md. O deploy Vercel foi removido no OPS50; o verificador automático de main (ci.yml) foi eliminado no OPS71 — a rede de regressão de main agora é o verify full do próprio deploy.yml, que nasce no merge (OPS104 supersede o anti-goal "manual-only" da OPS102: automatiza-se o início do run, nunca o approve de produção).

Cursor Cloud

.cursor/environment.json instala deps + Postgres nativo + db:seed:minimal. Sem secrets de prod. Preferir pnpm push; escape git push --no-verify só no cutover documentado acima. PR via ManagePullRequest (draft: false) — o merge continua pelo safety net do GitHub.

Agent pool

Dormente (OPS65) — o workflow agent-pool.yml foi removido e o dispatch via CLI falha 404; scripts/skills ficam para histórico. O que foi: Supervisor remoto (agent-pool.yml): schedule + replenish em merge → main + dispatch. Workers PR --base main. Default model composer-2.5. Doctor: pnpm agent:pool -- doctor. Skill: agent-pool.

Guardrails / leitura

pnpm agent:file-miss → harvest → docs/GUARDRAILS.md. Always-on: AGENTS.md → codebase-map → engineering-standards → esta página → PRODUCT/DESIGN/CUSTOMER.