Skip to content

feat: limpeza remota do aparelho do ACS e rotação da chave do cache (#81) - #93

Merged
hbgit merged 17 commits into
developfrom
feat/issue-81-wipe-remoto
Oct 17, 2026
Merged

hbgit merged 17 commits into
developfrom
feat/issue-81-wipe-remoto

Conversation

@hbgit

@hbgit hbgit commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Refs #81

Resumo

A coordenação (o coordenador, só para ACS da própria UBS) ou o administrador pode pedir pelo backoffice a limpeza remota de um aparelho do ACS. O aparelho perde o acesso na hora. No próximo contato com o servidor, ele apaga tudo o que o SinalACS guarda nele, inclusive visitas não enviadas, troca a chave do SQLCipher e o próprio id de instalação, e confirma ao servidor. Pedido, entrega e confirmação ficam na trilha de auditoria. A chave do cache passa a ser trocada por evento (CRIPTO-APP-CACHE): no wipe remoto e no "Limpar este aparelho".

O certificado de cliente MQTT por aparelho (STRIDE-S) não está aqui. Ele depende de uma biblioteca X.509 fora de spec/stack.md e de uma PKI de produção, e foi para a #94. Por isso este PR não fecha a #81, e o R3 continua parcial na auditoria do PRD.

Plano: docs/superpowers/plans/2026-10-17-issue-81-wipe-remoto-e-rotacao-de-chave.md (D1–D8).

Backend

  • Tabela nova device_wipe_orders, uma linha por deviceId (índice único). Migração 20261017171640323.
  • admin.acsDevices / admin.orderDeviceWipe:
  • devices.pendingWipe / devices.confirmWipe:
    • São públicos e só devolvem orderId ou nada.
    • A marca de entrega ou de confirmação e a linha de auditoria gravam numa transação só.
  • DeviceWipeRequiredException em auth.loginInstitutional, auth.refreshSession e visits.syncDeferred. A checagem roda antes da senha ou da renovação, e um id que já foi marcado fica marcado para sempre.

App do ACS

  • VisitDatabase.destroy(): fecha o banco e apaga o arquivo, os sidecars, a cópia .recuperado e a chave. É exclusivo contra open() concorrente.
  • RemoteWipeCoordinator:
    • Uma limpeza por vez (single-flight). Antes de apagar, grava um diário (erasing/erased), e só apaga o diário depois que o servidor confirma.
    • Confirma com o id antigo.
    • Um diário preso em erasing não apaga de novo quando a limpeza daquele pedido já terminou.
  • Gatilhos: na partida (antes de qualquer abertura do banco), na volta do segundo plano, na volta da rede e na renovação.
  • Desmontagem do painel durante o wipe:
    • As filas, o pull e o cache da microárea são selados, para que nenhuma gravação atrasada recrie o banco com dados.
    • Os alertas não são confirmados nem concluídos (INV-03).
    • Com o app em segundo plano, a limpeza não espera frame.
  • Tela device_wiped ("Aparelho limpo pela coordenação"), fora do bloqueio por inatividade.
  • O "Limpar este aparelho" continua recusando quando há visita não enviada (WipeBlocked) e agora também troca a chave.

Admin

  • No cartão do ACS, o botão "Aparelhos" abre a lista de aparelhos com estado e último acesso.
  • "Limpar remotamente": escolha do motivo (Perdido, Roubado, Substituído), depois uma confirmação destrutiva que avisa que as visitas não enviadas serão apagadas.

Provas

  • Backend dart test: 1438 passando.
  • ACS flutter test: 685 passando; flutter analyze limpo.
  • Admin flutter test: 473 passando.
  • scripts/qa/ci_invariants.sh: ok.
  • scripts/qa/acs_remote_wipe_e2e.sh no emulator-5554 (API 36), contra o banco de teste. O teste deixa uma visita pendente com o painel aberto; o coordenador pede a limpeza; o app, ao voltar do segundo plano, mostra a tela de aparelho limpo. No aparelho: o arquivo do banco sumiu, a chave foi trocada, os tokens de envio foram apagados e o id é novo. O ACS entra de novo com o id novo. No banco, as 11 checagens passaram (o pedido foi entregue e confirmado, nenhum token vivo ficou com o id antigo, as três linhas de trilha existem, a visita não chegou ao servidor) e audit_chain_check saiu íntegro. Log em docs/superpowers/plans/2026-10-17-issue-81-e2e.log.
  • Regressões: acs_full_e2e.sh (6/6) e admin_acs_gestao_e2e.sh (3/3). O full_journey_e2e.dart foi ajustado à rotação da chave: agora confere que a conexão foi fechada e que a chave mudou, e conta as tabelas num banco reaberto.

Limites conhecidos (detalhe no PROGRESS.md, seção #81)

  • Um aparelho que nunca volta a ficar online não é alcançado, e o JWT vivo dura até 15 min depois do pedido (D3).
  • "Aparelho limpo" no admin é o que o aparelho afirma, não uma prova: quem tem o deviceId consegue confirmar sem limpar nada. O acesso continua bloqueado.
  • O pedido apaga dados de todos os donos daquele aparelho, inclusive ACS de outra UBS (D5). Os dois métodos públicos não têm limite de taxa.
  • Só está provado no emulador API 36. API 24–29 e aparelho físico seguem sem prova.
  • Falhas que já existiam antes deste branch: o teste de aparelho "banco v6 … migra para v7" (espera user_version 7, mas o schema é 8) e a linha do RNF04 no cobertura_prd.py --final.

hbgit added 17 commits October 17, 2026 13:14
)

remote_wipe_e2e.dart roda o SinalAcsApp com o id da instalação, a chave do
SQLCipher, os tokens de envio e o diário de produção; deixa uma visita
pendente (envio fora do ar pelo decorator, sem "Sair") com o painel aberto;
o coordenador da fixture pede a limpeza pela rede real e a volta do segundo
plano a aplica. Confere no aparelho banco vazio com chave nova, nenhum token
de envio e id novo, e entra de novo pela tela. acs_remote_wipe_e2e.sh confere
no banco o pedido entregue e confirmado, os tokens do id antigo revogados, a
trilha dos três eventos, a visita ausente de visits e a cadeia de auditoria;
acs_remote_wipe_e2e_test.sh guarda a estrutura sem Docker nem aparelho.

O decorator de envio vai para support/envio_fora_do_ar.dart, compartilhado
com full_journey_e2e.dart. A fila por dono do full_journey passa a reabrir o
banco depois do "Limpar este aparelho": desde a D6 ele fecha e apaga o arquivo
e troca a chave, e a conexão antiga do teste ficava fechada.
… limpo (#81)

O apagador da limpeza remota sela o cache da microárea e o pull de cada
escopo antes de destruir o banco: a resposta que volta da rede depois não
lê, não grava e não reabre o arquivo, então nenhuma chave nova é criada e
nenhum nome ou condição crônica fica no aparelho que o servidor registra
como limpo. O selo do cache abre no próximo login.
…o da partida tem teto (#81)

Com o diário em erasing, a limpeza conta como feita quando o pedido já foi
apagado nesta execução ou quando o id da instalação já é outro (o apagador
o troca no último passo): a retomada depois de "Entrar de novo" só reenvia
a confirmação e não leva as visitas do login novo. Na partida, só a
confirmação ganha o teto de startupWipeCheckTimeout; a limpeza segue sem
teto, antes de qualquer uso do banco.
@hbgit
hbgit merged commit 0e50369 into develop Oct 17, 2026
10 checks passed
@hbgit
hbgit deleted the feat/issue-81-wipe-remoto branch October 17, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant