Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
28e18d9
docs: plano da limpeza remota e rotação da chave do cache (#81)
hbgit Oct 17, 2026
4748ce5
feat(backend): pedido de limpeza remota do aparelho do ACS pelo backo…
hbgit Oct 17, 2026
8dbce32
feat(backend): aparelho recebe e confirma a limpeza remota; login e r…
hbgit Oct 17, 2026
3832fbf
fix(backend): carimbo da limpeza remota e sua linha de auditoria na m…
hbgit Oct 17, 2026
d010db9
feat(acs): limpar o aparelho apaga o banco e troca a chave do SQLCiph…
hbgit Oct 17, 2026
68357b0
fix(acs): destroy do banco exclusivo contra open concorrente (#81)
hbgit Oct 17, 2026
3263f98
feat(acs): coordenador da limpeza remota com diário e confirmação (#81)
hbgit Oct 17, 2026
791f698
feat(acs): app aplica a limpeza remota na partida, na retomada, na vo…
hbgit Oct 17, 2026
a9f2814
fix(acs): limpeza remota sela as filas antes de apagar e não espera q…
hbgit Oct 17, 2026
1ead368
feat(admin): aparelhos do ACS e limpeza remota pelo backoffice (#81)
hbgit Oct 17, 2026
8bbe69d
test(acs): e2e no emulador da limpeza remota com visita não enviada (…
hbgit Oct 17, 2026
5cabe73
docs: registra a limpeza remota e a rotação da chave do cache (#81)
hbgit Oct 17, 2026
eb8e22c
docs: R3 segue parcial na auditoria do PRD e ajusta afirmações sobre …
hbgit Oct 17, 2026
128162a
fix(acs): lista da microárea e pull atrasados não regravam o aparelho…
hbgit Oct 17, 2026
09851c1
fix(acs): diário preso em erasing não repete a limpeza e a confirmaçã…
hbgit Oct 17, 2026
7e93e61
docs: registra o selo das escritas tardias, o diário preso e os limit…
hbgit Oct 17, 2026
e344e7b
docs: aponta o certificado por aparelho para a issue #94 (#81)
hbgit Oct 17, 2026
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
6 changes: 3 additions & 3 deletions CLAUDE.md

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1695,3 +1695,24 @@ Backlog priorizado na seção final do relatório; conferência por `python3 scr
- O teto de 200 não pagina: numa microárea com mais de 200 abertos, os mais ANTIGOS de cada grupo ficam de fora (não confirmados primeiro; o corte nunca tira o pendente mais novo).
- Um alerta concluído em outro aparelho do mesmo ACS some da resposta (o servidor não devolve `resolved`) mas o cartão local não muda, porque a fusão nunca remove: segue contando até sair do app.
- Revisão final (2026-10-17), corrigido com teste que falhou antes: (1) com `autoReconnect`, a queda do broker DEPOIS da primeira conexão não chamava `onDisconnected` no `mqtt_client` 10.5.1 — o painel seguia "em linha" e a reserva nunca ligava; `MqttSecureClient` agora trata `onAutoReconnect` como queda e usa `disconnectOnNoResponsePeriod = 10` para a conexão meio aberta do 3G (`test/mqtt_drop_after_connect_test.dart`, broker falso em TCP real); (2) o teto de 200 cortava o pendente mais NOVO — agora cada grupo é lido do mais novo para trás (`acs_open_alerts_test.dart`); (3) ao armar, a reserva consulta na hora e zera o status anterior, em vez de esperar 30 s mostrando a hora de quando o MQTT funcionava (`alert_http_fallback_shell_test.dart`).

## Limpeza remota do aparelho e rotação da chave (#81) — 2026-10-17

- Entregue: o coordenador da UBS ou o administrador ordena a limpeza de um aparelho do ACS pelo diálogo "Aparelhos" do cartão do ACS no backoffice (`admin.acsDevices`, `admin.orderDeviceWipe`; motivo Perdido/Roubado/Substituído). O pedido grava `device_wipe_orders` (migração `20261017171640323`) e revoga, na mesma transação, os tokens de todos os usuários daquele `deviceId`. O app ACS (`devices.pendingWipe`/`confirmWipe`, `RemoteWipeCoordinator`, `WipeJournal`) aplica na partida, na retomada, na volta da rede e quando a renovação/login/envio diferido recebe `DeviceWipeRequiredException`: desmonta o painel, descarta as filas em RAM sem gravar, apaga o banco SQLCipher, sidecars e `.recuperado`, troca a chave, apaga os tokens de envio e o `deviceId`, mostra `device_wiped` e confirma ao servidor. Aparelho limpo entra de novo com um `deviceId` novo. "Limpar este aparelho" (local) também passou a apagar o arquivo e trocar a chave (`VisitDatabase.destroy`).
- Decisões: D1 identidade do aparelho = `deviceId` da instalação, sem tabela de aparelhos (lista = `acs_refresh_tokens` ∪ `acs_upload_tokens`; índice único em `device_wipe_orders.deviceId`); D2 o wipe remoto ignora o bloqueio de visitas não enviadas (o diálogo do admin avisa; o local segue recusando); D3 com o aparelho online a entrega ocorre em até 15 min (JWT vivo não é revogado por chamada; a renovação seguinte recebe a exceção; partida, retomada e volta da rede perguntam na hora); D4 sem cancelamento; D5 revogação por aparelho, não por conta (cortar a conta é `setAcsActive(false)`, #43); D6 rotação da chave **por evento, não por calendário** (sem `PRAGMA rekey` periódico, que reescreveria o arquivo com visitas pendentes dentro; registrado em `spec/lgpd_design.md` §5.1); D7 diário gravado antes de apagar (`erasing`/`erased`, só `orderId` e `deviceId` antigo); D8 certificado de cliente por aparelho adiado para a issue #94, com o certificado de dev `acs-area-12` ainda no APK de debug e o release recusando a chave privada no asset. INV-03: os alertas seguem `pending` no servidor e o escalonamento da #44 continua valendo.
- Prova: `./scripts/qa/acs_remote_wipe_e2e.sh` no emulador API 36 contra o banco de teste, 2026-10-17: ACS com uma visita não enviada e o painel aberto, pedido `stolen` do coordenador, ciclo de vida inactive→resumed, tela `device_wiped`, arquivo do banco ausente após a limpeza, chave trocada, `deviceId` novo, tabelas reabertas vazias, login novo sem visita pendente; no banco, tokens do id antigo revogados, linhas `wipe_ordered`/`wipe_delivered`/`wipe_confirmed` com o mesmo `orderId`, nenhuma visita do ACS no servidor, `audit_chain_check` verde (18 linhas). Log em `docs/superpowers/plans/2026-10-17-issue-81-e2e.log`. Testes: `device_wipe_service_test.dart`, `device_wipe_store_test.dart`, `device_wipe_endpoint_test.dart` (backend); `device_wipe_test.dart`, `remote_wipe_*`/painel aberto, `device_wiped` (ACS); `acs_devices_dialog_test.dart`, `touch_targets_test.dart` (admin). Screenshots das telas ainda não capturadas (`docs/telas-admin.md`, `docs/telas-acs.md`).
- Aberto:
- Aparelho offline para sempre não é alcançável (inerente ao modelo, D3).
- O JWT vivo vale até 15 min depois do pedido; a limpeza só chega na renovação seguinte ou numa consulta do app.
- A partida do ACS com o servidor mudo espera até 5 s pela confirmação pendente do diário (`resumePending(confirmTimeout:)`; a limpeza em si não tem teto e termina antes de qualquer uso do banco) e mais até 5 s pela consulta `pendingWipe`, antes de mostrar o login: até ~10 s, no lugar dos ~25 s de antes (`remote_wipe_ui_test.dart`, "servidor mudo na confirmação da partida").
- Certificado de cliente MQTT por aparelho (D8, STRIDE-S): issue #94.
- R3 do PRD segue **parcial** (`docs/auditoria-prd`): faltam também a chave derivada de biometria que o PRD pede (a do SQLCipher fica no Keystore sem amarração, §5.1).
- Prova só na API 36 do emulador; API 24–29 e aparelho físico sem prova.
- `pendingWipe`/`confirmWipe` são públicos: quem tem o `deviceId` pode carimbar entregue/confirmado sem apagar nada (o acesso segue bloqueado, porque o id antigo fica marcado para sempre). "Aparelho limpo" no admin é a afirmação do aparelho, não prova.
- Os dois endpoints públicos não têm limite de taxa: cada chamada é uma consulta ao banco, sem autenticação, como os demais endpoints públicos do backend. Um limite por IP/`deviceId` fica para quando houver um para todos.
- D5 tem consequência entre UBS: o pedido de um coordenador revoga os tokens e apaga as visitas de **todos** os donos daquele `deviceId`, inclusive ACS de fora da UBS dele que tenham usado o mesmo aparelho. É do desenho (a revogação é do aparelho, não da conta), mas o diálogo de confirmação do admin não diz isso; o texto foi fixado pelo plano e não mudou.
- O diário é de uma vaga só: um pedido mais novo sobrescreve a confirmação pendente de um mais antigo (o `wipe_confirmed` do antigo se perde). Se a gravação de `erased` e a confirmação falharem juntas, o diário fica em `erasing`, mas a retomada não apaga de novo: o pedido já apagado nesta execução, ou um `deviceId` diferente do diário (o apagador o troca no último passo), contam como limpeza feita, e só a confirmação é reenviada (`remote_wipe_test.dart`, `remote_wipe_ui_test.dart`, "diário preso em erasing").
- Escritas tardias do cache da microárea e do cursor de `pull`: o apagador sela `MicroAreaDirectory` e o `VisitPullService` de cada escopo (`LocalWriteSeal`) antes de destruir o banco, e espera (até 5 s) a gravação do cache que já tinha começado; a resposta que volta da rede depois não lê, não grava e não reabre o arquivo, e o selo do cache só abre no próximo login (`remote_wipe_late_writes_test.dart`, banco real FFI: nenhuma chave nova, arquivo ausente, e o cache volta a gravar depois de "Entrar de novo"). Fica aberto: `MicroAreaCacheStore` tem a própria instância de `SecureStorageDatabaseKeyStore`, e no primeiro uso depois de uma destruição (ou da instalação) o `readOrCreate` dele pode correr com o do `VisitDatabase` e gerar duas chaves; na prática a lista vem da rede antes de gravar, mas nada garante a ordem.
- Teste de dispositivo que já falhava antes desta branch (medido na base dela): "banco v6 ... migra para v7" em `apps/acs/integration_test/encrypted_storage_test.dart` espera `user_version` 7 e o schema é 8.
- O motivo escolhido não aparece no histórico do app admin (só no servidor); se a recarga da lista falha depois de um pedido bem-sucedido, a tela mostra a falha genérica.
- Conferidor `scripts/qa/cobertura_prd.py --final` falha por uma linha do RNF04 sem negrito no status; falhava igual com as mudanças desta issue guardadas (medido na base da branch).
Loading
Loading