Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Importante:
- o `workflow-lint` roda actionlint e `scripts/qa/ci_invariants.sh`, que falha se a lista de jobs divergir de `JOBS_DOCUMENTADOS`, se um job sair do runner fixado (`ubuntu-24.04`, nunca `ubuntu-latest`), se uma ação cair abaixo da major em node24 (`checkout@v7`, `setup-java@v6`, `cache@v6`, `upload-artifact@v7`), se o workflow ganhar filtro de `paths` ou perder o grupo de `concurrency` por PR/SHA, se o `android-e2e` perder a limpeza de `pg_data/` ou se a chave do cache de AVD não terminar em `-<RUNNER>`;
- `main` e `develop` são protegidas: os 8 checks de `./scripts/qa/ci_invariants.sh --checks-obrigatorios` (todo job exceto `android-e2e`) precisam passar para mesclar, e `main` exige PR; admins ainda podem dar push direto. O `android-e2e` está verde desde as correções de 2026-09-28, mas segue informativo até acumular histórico;
- o script não lê a proteção configurada no GitHub: ao renomear ou criar um job, reaplique-a (ver `CONTRIBUTING.md` › CI e merge), senão as PRs ficam esperando um check que não existe mais;
- migrar para o Ubuntu 26.04 (`ubuntu-latest` migra em 2026-10-19; um ensaio passou 9/9) é uma PR que troca juntos `runs-on`, `RUNNER` e o sufixo da chave do AVD, e precisa de um actionlint que conheça o rótulo `ubuntu-26.04`. Histórico em [docs/ci-audit/2026-09-28-avaliacao-ci-develop.md](docs/ci-audit/2026-09-28-avaliacao-ci-develop.md); lacunas conhecidas da guarda nas issues #19 a #24.
- migrar para o Ubuntu 26.04 (`ubuntu-latest` migra em 2026-10-19; um ensaio passou 9/9) é uma PR que troca juntos `runs-on`, `RUNNER` e o sufixo da chave do AVD, e precisa de um actionlint que conheça o rótulo `ubuntu-26.04`. Histórico em [docs/ci-audit/2026-09-28-avaliacao-ci-develop.md](docs/ci-audit/2026-09-28-avaliacao-ci-develop.md); lacunas conhecidas da guarda nas issues #19 a #21 e #23 a #24 (#22 corrigida — `on:` como string/lista é normalizado em vez de quebrar, e uma flag não reconhecida sai com 2 e mensagem de uso em vez de sair calada com 0).

## Observações finais

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Product/architecture source of truth (PRD, UX flows, LGPD design, stack decision
- When touching sync behavior (backend `SyncFsm` or the ACS `offline_visit_queue.dart`), preserve retry/queue/conflict semantics — offline-first correctness is the primary architectural risk called out in `AGENTS.md`.
- When reusing a clinical fill color (`red`/`accent`/`danger`/`yellow`/`green`) as text or icon color in the Flutter apps, use the `*OnSurface` token and measure contrast against the surface it actually renders on (commonly `Card`/`surfaceRaised`), not the Scaffold background — see the WCAG contrast tokens section in [apps/CLAUDE.md](apps/CLAUDE.md), `spec/ux_accessibility_assessment.md` and each app's `test/contrast_tokens_test.dart`.
- CI lives in [.github/workflows/ci.yml](.github/workflows/ci.yml) and runs on every PR, on pushes to `main`/`develop`, and by hand (`gh workflow run CI --ref <branch>`). 9 jobs: `workflow-lint`, `serverpod-backend`, `backend-docker-build`, `patient-app`, `acs-app`, `admin-app`, `coverage-report`, `android-e2e` (the only one that boots a real emulator against the stack), `admin-android-build`. `workflow-lint` runs actionlint plus `scripts/qa/ci_invariants.sh`, which fails when: this job list drifts from `JOBS_DOCUMENTADOS`; a job leaves the pinned runner (`RUNNER = 'ubuntu-24.04'`, never `ubuntu-latest`); an action drops below its node24 major (`checkout@v7`, `setup-java@v6`, `cache@v6`, `upload-artifact@v7`); the workflow gains a `paths` filter or loses the per-PR/per-SHA `concurrency` group; `android-e2e` loses its `pg_data/` cleanup step; or the AVD cache key does not end in `-<RUNNER>`.
- `main` and `develop` are protected: the 8 checks from `./scripts/qa/ci_invariants.sh --checks-obrigatorios` (every job except `android-e2e`, tied to GitHub Actions app 15368) must pass to merge, and `main` also requires a PR; admins can still push directly. `android-e2e` has been green since the fixes of 2026-09-28 but stays informational until it builds a longer history. The script does not read the live protection: after renaming or adding a job, re-apply it (see `CONTRIBUTING.md` › CI e merge) or PRs wait forever for a check that no longer exists. Moving to Ubuntu 26.04 (`ubuntu-latest` migrates on 2026-10-19; a rehearsal ran 9/9 green) is a PR that changes `runs-on`, `RUNNER` and the AVD key suffix together, and needs an actionlint that knows the `ubuntu-26.04` label. History in `docs/ci-audit/2026-09-28-avaliacao-ci-develop.md`; known gaps in the guard are issues #19–#24.
- `main` and `develop` are protected: the 8 checks from `./scripts/qa/ci_invariants.sh --checks-obrigatorios` (every job except `android-e2e`, tied to GitHub Actions app 15368) must pass to merge, and `main` also requires a PR; admins can still push directly. `android-e2e` has been green since the fixes of 2026-09-28 but stays informational until it builds a longer history. The script does not read the live protection: after renaming or adding a job, re-apply it (see `CONTRIBUTING.md` › CI e merge) or PRs wait forever for a check that no longer exists. Moving to Ubuntu 26.04 (`ubuntu-latest` migrates on 2026-10-19; a rehearsal ran 9/9 green) is a PR that changes `runs-on`, `RUNNER` and the AVD key suffix together, and needs an actionlint that knows the `ubuntu-26.04` label. History in `docs/ci-audit/2026-09-28-avaliacao-ci-develop.md`; known gaps in the guard are issues #19–#21, #23–#24 (#22 fixed — `on:` as a string/list is normalized instead of crashing, and an unrecognized flag exits 2 with a usage message instead of silently exiting 0).
- Never commit real patient data, credentials, or the dev Docker Compose secrets into anything beyond local development.

## graphify
Expand Down
53 changes: 49 additions & 4 deletions scripts/qa/ci_invariants.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,70 @@
#
# Não precisa de rede nem da stack; só python3 com PyYAML. Roda no job
# workflow-lint do próprio CI.
#
# CI_INVARIANTS_WORKFLOW aponta para um workflow diferente do real — só para
# testar as checagens contra fixtures sintéticas, sem tocar em
# .github/workflows/ci.yml.
set -euo pipefail

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
caminho_workflow="${CI_INVARIANTS_WORKFLOW:-$repo_root/.github/workflows/ci.yml}"

# Sem isto, o Python usa a codificação do locale do host para stdout/stderr —
# em runners Linux normalmente já é UTF-8, mas não é garantido (e não é, por
# padrão, no Windows), e as mensagens de falha têm acento. Sem UTF-8 forçado,
# elas saem corrompidas em vez de crashar, o que é pior: passa despercebido.
export PYTHONIOENCODING=utf-8

exec python3 - "$repo_root/.github/workflows/ci.yml" "$@" <<'PY'
exec python3 - "$caminho_workflow" "$@" <<'PY'
import json
import sys

import yaml

# Valida os argumentos ANTES de fazer qualquer trabalho — uma flag digitada
# errada (ex.: --checks-obrigatorio, sem o S) não pode sair calada com "ok"/
# exit 0: quem chama este script com --checks-obrigatorios normalmente
# alimenta a saída direto num PUT da API de proteção de branch, e "ok" sem o
# JSON esperado quebra ali, longe da causa real (issue #22).
FLAGS_CONHECIDAS = {'--checks-obrigatorios'}
flags_desconhecidas = [a for a in sys.argv[2:] if a not in FLAGS_CONHECIDAS]
if flags_desconhecidas:
print(f'uso: ci_invariants.sh [{" | ".join(sorted(FLAGS_CONHECIDAS))}]', file=sys.stderr)
print(f'flag(s) desconhecida(s): {flags_desconhecidas}', file=sys.stderr)
sys.exit(2)

caminho = sys.argv[1]
with open(caminho, encoding='utf-8') as f:
wf = yaml.safe_load(f)

# PyYAML segue o YAML 1.1, em que a chave `on` é lida como o booleano True.
gatilhos = wf.get('on', wf.get(True)) or {}
jobs = wf.get('jobs') or {}
falhas = []


def _normaliza_gatilhos(bruto):
# PyYAML segue o YAML 1.1, em que a chave `on` é lida como o booleano
# True. E `on:` aceita três formas no GitHub Actions: dict (a única que
# os checks abaixo sabem ler), string solta (`on: push`) e lista
# (`on: [push, pull_request]`) — as duas últimas não carregam
# sub-configuração nenhuma (não dá para expressar `branches:` nelas), e
# sem normalizar viravam `str`/`list` aqui e quebravam com AttributeError
# no primeiro `.get()` de check_gatilhos, longe da causa (issue #22).
valor = bruto.get('on', bruto.get(True))
if valor is None:
return {}
if isinstance(valor, dict):
return valor
if isinstance(valor, str):
return {valor: None}
if isinstance(valor, list):
return {evento: None for evento in valor}
falhas.append(f'on: tem tipo inesperado ({type(valor).__name__}): {valor!r}')
return {}


gatilhos = _normaliza_gatilhos(wf)
jobs = wf.get('jobs') or {}

# Os jobs que CLAUDE.md e AGENTS.md enumeram. Mudou aqui, muda lá no mesmo
# commit — foi exatamente essa enumeração que envelheceu (FINDING-1).
JOBS_DOCUMENTADOS = {
Expand Down
Loading