Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
run: |
python3 -c 'import yaml' 2>/dev/null || { sudo apt-get update && sudo apt-get install -y python3-yaml; }
./scripts/qa/ci_invariants.sh
./scripts/qa/ci_invariants_test.sh
- name: Testes dos scripts de CI
run: |
./scripts/ci/decode_secret_file_test.sh
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@ Importante:
- os testes de integração e validação de conexão vivem fora do `flutter test` e utilizam a stack Docker/VM;
- a validação contra a stack real está na skill `validacao-e2e` (`scripts/qa/e2e.sh`, `tool/live_check.dart`, `integration_test` no emulador);
- o CI ([.github/workflows/ci.yml](.github/workflows/ci.yml)) roda em toda PR, em push para `main`/`develop` e à mão (`gh workflow run CI --ref <branch>`), e tem nove jobs: `workflow-lint`, `serverpod-backend`, `backend-docker-build`, `patient-app`, `acs-app`, `admin-app`, `coverage-report`, `android-e2e` (único que sobe emulador Android contra a stack) e `admin-android-build` (compila o APK do admin);
- 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>`;
- 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/`, se a chave do cache de AVD não terminar em `-<RUNNER>`, ou se um check obrigatório tiver `if:`, `continue-on-error: true`, `strategy.matrix` ou `needs:` apontando para fora do conjunto obrigatório (issue #20 — qualquer um desses faz o job reportar `skipped`/verde sem ter rodado, e a proteção de branch conta isso como aprovado). Logo depois roda `scripts/qa/ci_invariants_test.sh`, com fixture positiva e negativa para cada checagem nova, pra essas checagens não ficarem sem teste nenhum;
- `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 #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).
- 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, #21, #23 e #24 (#20 corrigida — ver `check_checks_obrigatorios` e `scripts/qa/ci_invariants_test.sh`; #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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ Product/architecture source of truth (PRD, UX flows, LGPD design, stack decision
- Keep triage/prioritization logic deterministic and consistent with the Manchester Protocol model referenced in the PRD — do not make risk classification probabilistic or user-overridable.
- 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>`. `workflow-lint` also runs `scripts/ci/decode_secret_file_test.sh`, `scripts/qa/ci_push_e2e_test.sh` and `scripts/qa/ci_invariants_fcm_test.sh`, and `ci_invariants.sh` additionally fails when the secrets `FCM_CREDENTIALS_BASE64`/`GOOGLE_SERVICES_JSON_BASE64` appear inside a `run:` or in the job-level `env:`, when a decode step comes after the E2E step, when the `if: always()` cleanup step is missing, when the emulator loses `target: google_apis` (Play Services; the action's default image is AOSP and FCM returns no token) or when the AVD cache key does not contain it. `android-e2e` decodes both secrets (service-account key to a temp file + `GOOGLE_APPLICATION_CREDENTIALS`, `google-services.json` into `apps/patient/android/app/`) and, when they exist, ends with `scripts/qa/ci_push_e2e.sh` (real Gorush and FCM); without them (fork PRs) it skips.
- `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).
- 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; the AVD cache key does not end in `-<RUNNER>`; or a required check has `if:`, `continue-on-error: true`, `strategy.matrix`, or a `needs:` pointing outside the required set (issue #20 — each of those makes a required job report `skipped`/green without actually running, which branch protection counts as approval). `scripts/qa/ci_invariants_test.sh` runs right after it, with a positive and negative fixture per new case, so those checks don't drift silently either. `workflow-lint` also runs `scripts/ci/decode_secret_file_test.sh`, `scripts/qa/ci_push_e2e_test.sh` and `scripts/qa/ci_invariants_fcm_test.sh`, and `ci_invariants.sh` additionally fails when the secrets `FCM_CREDENTIALS_BASE64`/`GOOGLE_SERVICES_JSON_BASE64` appear inside a `run:` or in the job-level `env:`, when a decode step comes after the E2E step, when the `if: always()` cleanup step is missing, when the emulator loses `target: google_apis` (Play Services; the action's default image is AOSP and FCM returns no token) or when the AVD cache key does not contain it. `android-e2e` decodes both secrets (service-account key to a temp file + `GOOGLE_APPLICATION_CREDENTIALS`, `google-services.json` into `apps/patient/android/app/`) and, when they exist, ends with `scripts/qa/ci_push_e2e.sh` (real Gorush and FCM); without them (fork PRs) it skips.
- `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 (#20 fixed — see `check_checks_obrigatorios` and `scripts/qa/ci_invariants_test.sh`; #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
47 changes: 40 additions & 7 deletions scripts/qa/ci_invariants.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
# 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.
# CI_WORKFLOW_PATH aponta para um workflow diferente do real — só para
# scripts/qa/ci_invariants_test.sh e ci_invariants_fcm_test.sh testarem as
# checagens contra fixtures sintéticas, sem tocar em .github/workflows/ci.yml.
set -euo pipefail

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# CI_WORKFLOW_PATH é o nome usado por ci_invariants_fcm_test.sh; CI_INVARIANTS_WORKFLOW
# continua aceito (nome anterior).
# continua aceito (nome anterior, usado por ci_invariants_test.sh).
caminho_workflow="${CI_WORKFLOW_PATH:-${CI_INVARIANTS_WORKFLOW:-$repo_root/.github/workflows/ci.yml}}"

# Sem isto, o Python usa a codificação do locale do host para stdout/stderr —
Expand Down Expand Up @@ -213,6 +213,16 @@ CHECKS_OBRIGATORIOS = sorted(JOBS_DOCUMENTADOS - {'android-e2e'})
# publique um status com o mesmo nome e destrave o merge.
APP_GITHUB_ACTIONS = 15368

def _lista_needs(needs):
# `needs:` aceita string, lista ou (sem chave) None — normaliza pra uma
# lista só, sempre.
if needs is None:
return []
if isinstance(needs, str):
return [needs]
return list(needs)


def check_checks_obrigatorios():
if 'android-e2e' in CHECKS_OBRIGATORIOS:
falhas.append('FINDING-4: android-e2e não pode ser obrigatório enquanto for instável')
Expand All @@ -221,12 +231,35 @@ def check_checks_obrigatorios():
if job is None:
falhas.append(f'FINDING-5: check obrigatório {nome} não existe no workflow')
continue
# O nome do check é o `name:` do job, se houver; e um job com `if:`
# pode não rodar e nunca reportar.
# O nome do check é o `name:` do job, se houver.
if job.get('name', nome) != nome:
falhas.append(f"FINDING-5: {nome} tem name: {job['name']!r}; o check obrigatório não casaria")
if 'if' in job:
falhas.append(f'FINDING-5: {nome} tem if: no nível do job; pode nunca reportar')
# Um job pulado pelo próprio `if:` reporta `skipped` — e a
# proteção de branch conta `skipped` como aprovado. Não é "pode
# nunca reportar": ele sempre reporta, só que reporta verde do
# jeito errado, e o merge destrava sem o job ter rodado nada
# (issue #20 corrige esta mensagem, que dizia o contrário).
falhas.append(
f'FINDING-5: {nome} tem if: no nível do job; skipped conta como aprovado e destrava o merge sem rodar'
)
# issue #20: três outros jeitos de um check obrigatório passar verde
# sem ter, de fato, rodado.
if job.get('continue-on-error') is True:
falhas.append(
f'{nome} (obrigatório) tem continue-on-error: true; uma falha do job reporta sucesso'
)
if 'matrix' in (job.get('strategy') or {}):
falhas.append(
f'{nome} (obrigatório) tem strategy.matrix; o nome do check vira "{nome} (valor)" '
'e não casa com o contexto exigido pela proteção — a PR espera para sempre'
)
for dep in _lista_needs(job.get('needs')):
if dep not in CHECKS_OBRIGATORIOS:
falhas.append(
f'{nome} (obrigatório) tem needs: {dep}, que não é obrigatório; se {dep} falhar, '
f'{nome} fica skipped e conta como aprovado'
)


# Credenciais do FCM (chave de conta de serviço e google-services.json) no android-e2e.
Expand Down
Loading
Loading