diff --git a/AGENTS.md b/AGENTS.md index 4b860ec3..4b9ce5ee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 `-`; - `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 diff --git a/CLAUDE.md b/CLAUDE.md index f58b0f6c..0fb608a3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 `). 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 `-`. -- `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 diff --git a/scripts/qa/ci_invariants.sh b/scripts/qa/ci_invariants.sh index 9e3fb821..2ff58e53 100755 --- a/scripts/qa/ci_invariants.sh +++ b/scripts/qa/ci_invariants.sh @@ -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 = {