15 KiB
Impl: OPS102 — Deploy manual roda até o fim mesmo com main à frente (sem guard de stale run)
Status: em execução Atualizado em: 2026-09-12 Issue: #962 Intenção: docs/plans/ops102-deploy-manual-sem-stale-guard.md Appetite restante: herdado (~0,5 dia eng; sem migration, sem UI, sem schema — remoção de ~8 linhas no script, reescrita de 2 specs unit, 4 docs vivas + changelog)
Leitura da intenção
- Outcome: um
workflow_dispatchdodeploy.ymlcompleta o deploy do SHA do run mesmo quemainavance durante os ~50 min doverify— sem "stale run" abortando. Sem falso verde: job verde = SHA publicado (build + rollout + smoke) ou container já o rodava ("already deployed") — nunca skip silencioso. - O que NÃO negociar: o guard "already deployed" (
scripts/deploy-homeserver.sh:70-74) permanece intacto; nenhum caminho verde sem deploy; nenhum mecanismo novo anti-out-of-order;verify/build/ordem migrator→migrate→runner/rollback/smoke intocados;workflow_dispatch-only intacto; histórico (postmortem #953, changelogs,CHANGELOG-AGENTS-HISTORY.md, planos ops53/65/66/71/103) não é editado. - O que reavaliar: nada de produto — a questão em aberto da intenção já está decidida (Recomendação A: documentar o risco, não engenhar). Em engenharia, duas escolhas baratas: a forma exata dos pins unitários (Decisão 2) e o texto da linha de risco out-of-order na tabela "Falhas conhecidas" (Decisão 3).
Abordagem recomendada
flowchart TD
A["workflow_dispatch (operador escolhe o SHA)"] --> B["verify hosted: suíte full (~50 min)<br/>main pode avançar no meio"]
B --> C["deploy job: bash scripts/deploy-homeserver.sh $GITHUB_SHA"]
C --> D["flock: serializa dispatches (l.54-55)"]
D --> E{"container já roda a revision do SHA? (l.70-74)"}
E -- sim --> F["'already deployed' → exit 0<br/>ÚNICO verde sem build"]
E -- não --> G["migrator build → migrate → runner build → rollout → smoke"]
G --> H["deploy completo do SHA do run<br/>mesmo com main à frente (OPS102)"]
F -. "removidos: guards pré/pós-flock (l.49-52, l.57-60)" .-> D
Opções consideradas: A | B | C por decisão, abaixo.
Recomendação: A + A/B + A — remover os dois guards de stale run (Decisão 1); pins unitários negativos + positivo do "already deployed" + pin do único exit 0 (Decisão 2); atualizar runbook/AGENT-OPS e registrar o risco out-of-order na tabela "Falhas conhecidas" (Decisão 3). Cada mudança fica no dono do contrato (script, spec, docs vivas) sem criar superfície nova.
Rejeitadas: manter os guards como warn ou condicionados (Decisão 1: B/C); remover os testes e confiar no e2e (Decisão 2: C); só apagar as menções das docs (Decisão 3: B).
Decisão 1 — destino dos guards de stale run
- Opções: A) remover os dois blocos (
l.49-52pré-flock el.57-60pós-flock); B) torná-los não-fatais (warn); C) mantê-los condicionados ao gatilho (workflow_dispatch). - Recomendação: A — remover. A premissa do guard era o deploy automático (OPS53):
mainandava sozinho e publicar SHA velho era errado. Desde a OPS71 o dispatch é ato humano deliberado e ociSkipInvariants.unit.spec.ts:111-115pina que o workflow éworkflow_dispatch-only — a comparação com o HEAD domaindeixou de significar algo. - Rejeitadas: B porque um warning em job verde é ruído sem ação (e mantém um
git ls-remotepor deploy + a leitura errada de quemainimporta); C porque o workflow já é sóworkflow_dispatch, então a condição seria sempre verdadeira — é A com código morto e um round-trip a mais ao remoto.
Decisão 2 — como pinar o contrato novo nos unit tests
- Opções: A) pins negativos (
not.toContain('stale run' / 'git ls-remote' / 'refs/heads/main')) + manter o pin positivo do "already deployed"; B) pin positivo do fluxo novo (toContain('already deployed')+not.toContain('stale run')); C) remover os testes e confiar no e2e. - Recomendação: A+B combinados — reescrever as specs
l.19-23el.25-30como contrato negativo com comentário de intenção (o guard OPS53 só faz sentido em deploy automático; reintroduzi-lo deve falhar), manter intacta a specl.32-38("already deployed") e adicionar dois pins que dão dentes à remoção: (i) o únicoexit 0do script é o do "already deployed" (não existe mais nenhum caminho de skip verde — regressão do #953); (ii) oflockpermanece (anti over-deletion). - Rejeitadas: C porque o e2e não roda o script de deploy (nenhum spec de e2e o exercita; o
deploy.ymlnão é disparado no CI de PR) — seria trocar a única rede por nenhuma; B sozinho porquenot.toContain('stale run')prova ausência de string, não ausência de outro caminho de falso verde — o pin doexit 0único cobre a classe.
Decisão 3 — documentação viva
- Opções: A) atualizar runbook + AGENT-OPS e adicionar a linha de risco out-of-order na tabela "Falhas conhecidas"; B) só remover as menções ao guard; C) deixar como está (registro histórico).
- Recomendação: A — o runbook e o AGENT-OPS são docs vivas (fonte de operação); a remoção do guard muda o fluxo, então o texto muda junto, e o risco de dispatch fora de ordem (SHA antigo re-dispatchado depois de um deploy mais novo) fica explícito com gatilho de revisita (Recomendação A da questão em aberto da intenção).
- Rejeitadas: B porque apaga o fluxo sem dar a leitura nova ao operador (e perde o contexto do #953); C porque o histórico é
docs/changelog/+docs/postmortems/+HISTORY(intocados) — runbook/AGENT-OPS não são registro congelado.
Componentes / mudanças
scripts/deploy-homeserver.sh(dono do fluxo):- Remover o bloco pré-flock
l.49-52e o bloco pós-flockl.57-60(a comparaçãogit ls-remote … refs/heads/main+ os doisfatal "stale run…"). Oawksome junto, sem órfãos. - Renomear o cabeçalho
# --- guards ---(l.47) para algo como# --- serialization (flock) ---, já que o único bloco restante ali é oflock(l.54-55). - Header de fluxo
l.11-12: remover "HEAD guard (only the current main HEAD deploys; a stale run FAILS the job — never a false green…)" e começar em "flock serialization -> workspace fetch at -> …". - Comentário do
set-urll.96-98: "so the HEAD guard and the fetch compare against the repo…" → "so the fetch targets the repo that actually received the merge" (oTEQO_REPO_URLcontinua usado por clone/set-url/fetch — não remover). - Preservar sem tocar:
flockl.54-55; bloco de idempotência "already deployed"l.62-74(inclui oexit 0);say/fatal(usados no resto do script).
- Remover o bloco pré-flock
.github/workflows/deploy.yml: atualizar o comentáriol.23-25("the script's HEAD guard only deploys when the dispatched SHA is main's HEAD (a stale dispatch skips)") para "the script deploys the dispatched SHA to the end even if main advances during verify (OPS102 — deliberate dispatch; idempotency is the 'already deployed' guard)". Nada mais muda:on: workflow_dispatch(l.29-30),needs/if/runs-one a invocaçãol.159(bash scripts/deploy-homeserver.sh "$GITHUB_SHA") ficam intactos.tests/unit/deployScript.unit.spec.ts: reescrever as specsl.19-23el.25-30conforme Decisão 2; manter a specl.32-38("already deployed",docker inspect+$running_rev+"$running_rev" = "$SHA"); o restante do arquivo (BuildKit, proxy, ordem OPS66, smoke, secrets, Dockerfile) não muda.ciSkipInvariants.unit.spec.tsnão muda (já pina sóworkflow_dispatch:+ ausência depush/schedule).docs/ops/teqo-1313-deploy.md:- Fluxo passo 4 (
l.20-22): "guarda de HEAD (git ls-remotedeTEQO_REPO_URL… senão 'stale run' skip) →flock" → "flock(serializa dispatches) → guard 'already deployed'"; manter a menção aTEQO_REPO_URLcomo origem do clone/atualização do workspace (a info não se perde). - Tabela "Falhas conhecidas" (
l.132): trocar a linha "Job verde sem deploy ('stale run')" por "Dispatch fora de ordem (SHA antigo re-dispatchado depois de um deploy mais novo)" | causa: oflockserializa mas não ordena; o "already deployed" não pega revision diferente | tratamento: não re-dispatchar SHA antigo; se acontecer, re-dispatchar omainatual (rollback manual disponível na seção Rollback). Gatilho para revisitar: deploy mais frequente/automatizado ou regressão real. A linhal.133("already deployed") permanece.
- Fluxo passo 4 (
docs/AGENT-OPS.md:l.81— removerHEAD guard;da descrição dodeploy.yml;l.110— trocar "O script: HEAD guard (TEQO_REPO_URL— default …, público) → flock → guard 'already deployed' …" por "O script: flock → guard 'already deployed' …", acrescentando que o deploy roda até o fim com o SHA do dispatch mesmo semainandar durante o verify (OPS102).docs/changelog/2026-09-12-ops102.md(novo): UMA entrada curta no padrão AGENTS; nunca editar o agregado nem o HISTORY.- Migration: nenhuma. Access/Consent: N/A (não toca Payload). UI: Impeccable A — N/A (sem UI).
Dados → forma
N/A (sem UI).
Fases verificáveis
- Script + workflow (remoção + comentários). Remover os dois blocos de stale run, ajustar header de fluxo, comentário do
set-urle comentário dodeploy.yml. Verificações:bash -n scripts/deploy-homeserver.sh;git diffrevisado mostrandoflocke "already deployed" intactos e só comentários além dos guards removidos;grep -n 'stale run\|git ls-remote\|refs/heads/main' scripts/deploy-homeserver.shvazio. - Unit tests. Reescrever as duas specs + adicionar os pins do
exit 0único e doflock; manter a spec do "already deployed". Verificações:pnpm test:unit -- tests/unit/deployScript.unit.spec.tsverde. Prova de valor (RED barata): rodar a spec reescrita contra o script pré-remoção (ex.:git stashdo diff da Fase 1) e vernot.toContain('stale run')falhar — o pin tem dentes. - Docs vivas + changelog. Runbook (fluxo + linha out-of-order), AGENT-OPS (
l.81,l.110), entradadocs/changelog/2026-09-12-ops102.md. Verificação:grep -rn 'stale run\|HEAD guard' scripts/ .github/ docs/ops/ docs/AGENT-OPS.mdvazio (fora dedocs/plans/,docs/changelog/,docs/postmortems/,docs/CHANGELOG-AGENTS-HISTORY.md— histórico intocável). - Gates + entrega.
pnpm gate:fastverde (lint/format/typecheck/unit);pnpm push(gate:push = gate:ci — o PR tocatests/unit, então a seleção roda a spec alterada); PR comCloses #962, basemain, auto-merge armado. Aceite final (humano, pós-merge): dispatch manual dodeploy.yml→verifyverde → jobdeployconcluído publicando a revision do SHA do run; idealmente commainandando durante o verify (natural se houver merge concorrente) para exercitar o cenário ao vivo — a prova determinística do contrato fica nos pins.
Rabbit holes / Não escopo (engenharia)
- Não adicionar mecanismo de ordenação/anti-out-of-order (recusar SHA não-descendente, fila ordenada, timestamp): decisão explícita da intenção — o
flockserializa e a fila natural do verify ordena; o risco vai documentado com gatilho. - Não refatorar o miolo do script (
flock, migrator→migrate→runner, rollback, smoke, cleanup): o pipeline acabou de ser recuperado (#952/#953) e o appetite não cobre; tocar só os guards e os comentários. - Não reintroduzir verifier automático de
mainnem deploy automático; não mexer noverify. - Não editar histórico:
docs/postmortems/2026-09-12-deploy-build-importmap.md,docs/changelog/2026-09-12-*,docs/CHANGELOG-AGENTS-HISTORY.md, planosops53/ops65/ops66/ops71/ops103(menções ao guard ali são registro). - Não mexer no guard "already deployed" nem no
ciSkipInvariants(não muda). - Não transformar o pin negativo em dogma:
not.toContain('git ls-remote')pina o contrato atual (dispatch deliberado); o comentário no spec diz que, se um dia um verifier automático voltar, é ali que o contrato se reavalia — sem abrir porta para skip verde.
Riscos e mitigação
- Regressão do falso verde #953 (verde sem deploy): mitigação = pin negativo explícito + pin do único
exit 0(só o "already deployed", que prova que o container roda o SHA) + manutenção do pin positivo do blocol.32-38; não existe mais nenhum caminho de skip silencioso. - Over-deletion na remoção (levar
flock/already-deployed junto): mitigação = diff focado na Fase 1 + pins positivos deflocke "already deployed" no mesmo spec +bash -n. - Dispatch fora de ordem pode regredir prod (SHA antigo re-dispatchado após deploy mais novo): aceito e documentado na tabela "Falhas conhecidas" com gatilho de revisita; fora de escopo por decisão (não engenhar agora).
- Drift de docs (menção órfã a "HEAD guard"/"stale run"): mitigação = grep da Fase 3 e revisão do PR.
- O verify do
deploy.ymlnão é exercitado no CI de PR: mitigação =bash -n+ pins unitários; a prova ao vivo é o dispatch manual pós-merge (aceite final humano).
Aceite de engenharia
scripts/deploy-homeserver.shsem os dois blocos de stale run (pré e pós-flock) e sem menção a HEAD guard nos comentários (header de fluxo eset-url);flocke "already deployed" intactos.github/workflows/deploy.ymlcom comentário atualizado;workflow_dispatch-only intacto (ciSkipInvariantsverde sem mudanças); invocaçãol.159intocadatests/unit/deployScript.unit.spec.ts: pins negativos +exit 0único +flockverdes, comprovadamente RED contra o script pré-remoção; spec "already deployed" (l.32-38) preservada- Runbook (fluxo
l.20-22+ linha out-of-order em "Falhas conhecidas") edocs/AGENT-OPS.md(l.81,l.110) alinhados; grep de drift vazio fora do histórico docs/changelog/2026-09-12-ops102.mdcriado; agregado/HISTORY/históricos intocadospnpm gate:fastverde;pnpm pushverde; PRCloses #962basemain- (humano, pós-merge) dispatch manual do
deploy.yml:verifyverde + jobdeployconcluído com a revision do SHA do run — critério final da Issue
Self-score de decision-quality: 4/5
- Decisões caras têm rejeitadas? ✓ — as 3 decisões têm opções nomeadas e rejeitadas com razão de morte (warn/condicional, confiar no e2e, só apagar docs).
- Cabe no appetite? ✓ — ~0,5 dia: remover ~8 linhas, reescrever 2 specs, ajustar 4 docs vivas + changelog; sem migration/UI/schema.
- Rabbit holes nomeados? ✓ — out-of-order, refactor do script, verifier automático, histórico, dogma do pin.
- Depth check? ✓ — nenhum módulo/utilitário novo; edita o dono de cada contrato (script, spec, runbook, AGENT-OPS), sem pass-through.
- Intenção satisfeita? ✓ — dispatch roda até o fim com o SHA do run; único verde sem build é o "already deployed"; registro vivo alinhado.
- Não dou 5 porque — o cenário ao vivo (main andando durante o verify) não é reproduzível deterministicamente no CI: a prova fica nos pins + no próximo dispatch manual pós-merge, e o risco out-of-order é aceito sem mecanismo por decisão documentada (não por omissão).