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 CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project overview

SinalACS is a platform that prioritizes Primary Health Care visits in Brazil by turning structured clinical signals into a risk-ranked work queue for the Community Health Agent (ACS). Project documentation, code comments, and UI copy are in Portuguese — match that language for anything user-facing or spec-related. Current state: functional prototype, validated locally via Docker Compose; not production-ready. Already delivered: ACS institutional login (RF07, matrícula + senha/Argon2id, credentials kept locally — no integration with a real identity provider), patient passwordless login (RF01, CPF + birth date + OTP), RBAC per micro-area (RNF06), TLS on the RPC via Traefik (RNF04) and on the broker (password + ACL + client certificate on the local stack, `scripts/qa/mtls_invariants.sh`), TOTP MFA for the ACS login, RF08 micro-area persisted in the encrypted local database (72 h, per owner), the ACS refresh token (rotating, idle window 2 h, absolute cap 8 h from the password+TOTP login, family revoked on reuse/other device/inactive account; the app keeps no password) bound to the Keystore: an AES-256-GCM + RSA-OAEP envelope whose RSA key requires user authentication on every use, so the token is decrypted only through `BiometricPrompt` + `CryptoObject` on cold start (API 30+ also accepts the device PIN; on API 30+ a newly enrolled fingerprint does **not** invalidate the key; proved on the API 36 emulator only, `scripts/qa/acs_keystore_bound_e2e.sh`), plus the 30 s background lock, which stays an interface gate over the token held in RAM and "Sair e encerrar o turno", `FLAG_SECURE` on the ACS window in every build (only a debug build made with `./scripts/dev/run_acs.sh --build --captura` opens it, by a compile-time constant that release refuses), release builds that refuse the debug key unless explicitly allowed (a release keystore is still to be provisioned), the UBS call button, the per-owner offline visit queue (schema v7, legacy rows quarantined and uploaded with unknown authorship, deferred upload token, wipe only when everything is sent) and the closing of the `login()` race (`BackendClient._exclusive`), plus the backoffice account management (#43: listing, ACS create/link/activate/deactivate and password/MFA reset, staff MFA reset with the activation code emitted by the screen, and the coordinator's audit scoped to his UBS — every operation audited, refusals included). Still missing: invalidation of the token key by a newly enrolled fingerprint on API 30+ and a proof of the Keystore binding on API 24–29 (the SQLCipher key and the deferred upload token stay unbound by design), a refresh token for the patient (OTP stays 1 h), iOS, a physical-device proof on Android ≤ 8.1 (API 24–27) (the emulator on API 24 and 27 showed no theme breakage; see `PROGRESS.md`, section Tema em API 24–27), background (OS-level) upload of visits left on the device (WorkManager is outside `spec/stack.md`), a supervisor-forced device wipe (no supervisor login exists), unblocking of a queue whose 7-day upload token expired or was refused (it needs the owner to log in again and blocks the wipe) and LGPD purge of an owner who never returns (see `PROGRESS.md`, section Fila de visitas por dono e corrida do login), per-device client-certificate provisioning in production (mTLS is delivered only on the local stack), creating coordinator accounts and assigning their `ubsId` through the UI (a coordinator still exists only in the seeds and by SQL; #43 covers the ACS lifecycle and the MFA resets), the ACS's own password change (a lost password is reset by the coordination), the audit trail's append-only enforcement in the database (the chain detects tampering, it does not prevent it; no trigger/`REVOKE`), a real file download of the CSV export (it goes through the clipboard), the owner's acceptance of the green "Cadeia íntegra" exception to `spec/ui_design.md` and the data-protection officer's review of the export (`PROGRESS.md`, section #45), and a production deploy (`backend/DEPLOY.md` is only a free-tier demo path). The data-subject request handling (#42) is delivered; the validation of its anonymization rules with the data-protection officer is still open (`PROGRESS.md`).
SinalACS is a platform that prioritizes Primary Health Care visits in Brazil by turning structured clinical signals into a risk-ranked work queue for the Community Health Agent (ACS). Project documentation, code comments, and UI copy are in Portuguese — match that language for anything user-facing or spec-related. Current state: functional prototype, validated locally via Docker Compose; not production-ready. Already delivered: ACS institutional login (RF07, matrícula + senha/Argon2id, credentials kept locally — no integration with a real identity provider), patient passwordless login (RF01, CPF + birth date + OTP), RBAC per micro-area (RNF06), TLS 1.3 on the RPC via Traefik (RNF04), on the Traefik → backend hop (one-way TLS 1.3 with the chain verified; no mTLS, Serverpod cannot request a client certificate — #78) and on the broker (TLS 1.3 only, password + ACL + client certificate on the local stack, `scripts/qa/mtls_invariants.sh` and `scripts/qa/tls_invariants.sh`), TOTP MFA for the ACS login, RF08 micro-area persisted in the encrypted local database (72 h, per owner), the ACS refresh token (rotating, idle window 2 h, absolute cap 8 h from the password+TOTP login, family revoked on reuse/other device/inactive account; the app keeps no password) bound to the Keystore: an AES-256-GCM + RSA-OAEP envelope whose RSA key requires user authentication on every use, so the token is decrypted only through `BiometricPrompt` + `CryptoObject` on cold start (API 30+ also accepts the device PIN; on API 30+ a newly enrolled fingerprint does **not** invalidate the key; proved on the API 36 emulator only, `scripts/qa/acs_keystore_bound_e2e.sh`), plus the 30 s background lock, which stays an interface gate over the token held in RAM and "Sair e encerrar o turno", `FLAG_SECURE` on the ACS window in every build (only a debug build made with `./scripts/dev/run_acs.sh --build --captura` opens it, by a compile-time constant that release refuses), release builds that refuse the debug key unless explicitly allowed (a release keystore is still to be provisioned), the UBS call button, the per-owner offline visit queue (schema v7, legacy rows quarantined and uploaded with unknown authorship, deferred upload token, wipe only when everything is sent) and the closing of the `login()` race (`BackendClient._exclusive`), plus the backoffice account management (#43: listing, ACS create/link/activate/deactivate and password/MFA reset, staff MFA reset with the activation code emitted by the screen, and the coordinator's audit scoped to his UBS — every operation audited, refusals included). Still missing: invalidation of the token key by a newly enrolled fingerprint on API 30+ and a proof of the Keystore binding on API 24–29 (the SQLCipher key and the deferred upload token stay unbound by design), a refresh token for the patient (OTP stays 1 h), iOS, a physical-device proof on Android ≤ 8.1 (API 24–27) (the emulator on API 24 and 27 showed no theme breakage; see `PROGRESS.md`, section Tema em API 24–27), background (OS-level) upload of visits left on the device (WorkManager is outside `spec/stack.md`), a supervisor-forced device wipe (no supervisor login exists), unblocking of a queue whose 7-day upload token expired or was refused (it needs the owner to log in again and blocks the wipe) and LGPD purge of an owner who never returns (see `PROGRESS.md`, section Fila de visitas por dono e corrida do login), per-device client-certificate provisioning in production (mTLS is delivered only on the local stack), creating coordinator accounts and assigning their `ubsId` through the UI (a coordinator still exists only in the seeds and by SQL; #43 covers the ACS lifecycle and the MFA resets), the ACS's own password change (a lost password is reset by the coordination), the audit trail's append-only enforcement in the database (the chain detects tampering, it does not prevent it; no trigger/`REVOKE`), a real file download of the CSV export (it goes through the clipboard), the owner's acceptance of the green "Cadeia íntegra" exception to `spec/ui_design.md` and the data-protection officer's review of the export (`PROGRESS.md`, section #45), and a production deploy (`backend/DEPLOY.md` is only a free-tier demo path). The data-subject request handling (#42) is delivered; the validation of its anonymization rules with the data-protection officer is still open (`PROGRESS.md`).

Two core flows:
- **Paciente (patient)**: passwordless auth (OTP), urgency alert, structured triage, request status tracking, "Meus Dados" (LGPD).
Expand Down
1 change: 1 addition & 0 deletions PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ Plano: `docs/superpowers/plans/2026-10-01-finalizacao-app-acs.md`. Medido no `em
- **RF08 — cache persistido e cifrado (72 h, por dono) (2026-10-02).** `MicroAreaDirectory` + `MicroAreaCacheStore` guardam a última lista da microárea na base SQLCipher do ACS (schema v6, tabelas `micro_area_cache`/`micro_area_cache_meta`), chaveada por `userId|microAreaId`, validade de 72 h, só no lugar de falha recuperável de rede; a tela avisa de quando é a lista. Decisão de LGPD em `spec/lgpd_design.md` §5.11, escrita antes do código. Provado no emulador (`full_journey_e2e.dart`): serve sem rede e o arquivo do banco não contém o nome do paciente.
- **MFA do ACS (2026-10-02).** MFA/TOTP do ACS entregue (RF07, LGPD-RT06): TOTP no `loginInstitutional`, ativação por matrícula+senha+código, replay barrado, exigida fora de `development` (`REQUIRE_ACS_MFA`); refresh token e redefinição por coordenador seguem abertos. **Com MFA ligada, o ACS precisa se autenticar de novo (matrícula + senha + código) mais ou menos a cada 15 minutos**, porque ainda não existe refresh token e o código TOTP é de uso único: `renewSession` recebe `MfaRequiredException`, esquece a credencial em memória e falha de forma não recuperável, e `AcsBackend.onSessionExpired` faz o painel EMPILHAR a tela de reautenticação por cima (uma só por vez). O painel não é desmontado: fila de alertas já recebidos por MQTT (o broker não reentrega o que foi confirmado com PUBACK), feed MQTT (autentica por senha+certificado, não pelo JWT), fila de visitas e formulários em andamento sobrevivem; login do MESMO usuário só dá `pop`, de OUTRO usuário descarta o painel (RNF06). Confirmar um alerta com a sessão vencida mostra a mensagem e o alerta segue sem confirmação. No app: o login ganha o campo `totp_field` (só dígitos, 6) quando o servidor responde `MfaRequiredException`; `MfaEnrollmentRequiredException` abre `MfaEnrollmentScreen` (QR + chave + código; o segredo não é gravado no aparelho). Provas: `test/mfa_login_test.dart` (5), `test/totp_support_test.dart`, casos de fonte ampliada em `test/text_scale_test.dart` e o último teste de `integration_test/full_journey_e2e.dart` (liga a MFA pelo RPC e entra pela tela com o código do passo atual, depois de esperar o passo avançar); `test/session_reauth_test.dart` (5: alerta e formulário preservados, ACK com mensagem, outro usuário, sem rota dupla). Ruling: a tela de ativação é provada por teste de widget, não no emulador (o e2e mantém `REQUIRE_ACS_MFA=false` para os outros testes).
- **mTLS do broker (2026-10-02).** Entregue **na stack local**: `require_certificate true`, certificado de cliente para o backend e para o ACS (asset de desenvolvimento `assets/certs/acs_client.*`, copiado por `sync_dev_ca.sh`, gitignorado), senha e ACL mantidos; `scripts/qa/mtls_invariants.sh` prova os quatro casos. **Aberto:** provisionamento por aparelho em produção (CSR no cadastro, chave no Keystore); o release com a chave de dev é barrado pelo Gradle (`-Psinalacs.allowDevClientKey=true` só para teste local).
- **TLS 1.3 no broker e no Traefik → backend (2026-10-16, issue #78).** Plano: `docs/superpowers/plans/2026-10-16-issue-78-tls13-broker-e-backend.md`. (1) **Broker:** `tls_version tlsv1.3` no listener 8883; o 1.2 com certificado de cliente válido é recusado (`SSL alert number 70`), e o backend e o ACS reconectam em 1.3. (2) **Traefik → backend:** o backend escuta TLS 1.3 na 8080 interna quando `API_TLS_CERT_PATH`/`API_TLS_KEY_PATH` estão definidas (`lib/src/config/api_tls_config.dart`; só uma das duas é erro de boot; nenhuma = HTTP, caminho do Render). A folha é emitida pelo `traefik-init` em `runtime/backend/` com a CA do RPC (o backend nunca enxerga `ca.key`); o Traefik verifica a cadeia pelo `serversTransport` `backend-tls` (`rootCAs` + `serverName: sinalacs-serverpod`), e o healthcheck do compose passou a `https`. **Decisão:** TLS de mão única, **sem mTLS**, porque o Serverpod 3.4.13 sobe o socket pelo `relic_io` (`HttpServer.bindSecure` sem `requestClientCertificate`); sidecar e fork foram descartados (fora do `spec/stack.md`). Provas: Grupos E e F de `scripts/qa/tls_invariants.sh` (15/15; mutação: `serverName` errado dá 500 com `x509: certificate is valid for … not nome-errado`, e `insecureSkipVerify` é acusado), `mtls_invariants.sh` (4/4), `test/unit/api_tls_config_test.dart` (7; sem o mínimo 1.3 o teste acusa o 1.2), `dev_key_permissions_test.sh`, e no `emulator-5554` (API 36): `e2e.sh --emulator`, `acs_full_e2e.sh` e `admin_alertas_escalonar_e2e.sh`. **Aberto:** mTLS Traefik ↔ backend; produção (no Render o TLS termina na borda); aparelho físico e API 24–29 (o 1.3 só foi medido no emulador API 36). **O RNF04 não fechou inteiro:** na stack local seguem em texto claro backend → Postgres (`SERVERPOD_DATABASE_REQUIRE_SSL: "false"`), backend → Gorush (`http://gorush:8088`), Insights (8083) e webServer (8082) internos e o dashboard do Traefik (8081, só dev) — fora do escopo da #78. `e2e_stack.sh up` passou a rodar o `traefik-init` antes da subida com `--no-deps` (num `runtime/` anterior à #78 o backend entrava em laço de restart sem a folha; reproduzido e corrigido).
- **Aberto fora do ACS:** `apps/patient` — manifesto `main` sem `INTERNET` explícito (hoje herdado de dependência); medir com `aapt2 dump permissions` no APK de release antes de mexer.

## Pendências do ACS (2026-10-02)
Expand Down
Loading
Loading