prompt inicial do projeto completo
This commit is contained in:
@@ -202,7 +202,7 @@ Três identidades distintas, três mecanismos:
|
||||
| **Morador / operador** | OIDC (Spring Security) + refresh token no keystore do dispositivo | Access 15 min, refresh 30 dias |
|
||||
| **Admin** | OIDC + MFA obrigatório | Sessão de 8h |
|
||||
|
||||
**Busca cega de unidade:** o endpoint de destino recebe bloco + unidade e **nunca** confirma se existe ou quem mora lá. Resposta idêntica para unidade válida e inválida; se não existir, a chamada simplesmente não é atendida. Isso impede que qualquer pessoa com o QR enumere quem mora onde — um problema de segurança física antes de ser de privacidade. Detalhes em `06-LGPD-E-SEGURANCA.md`.
|
||||
**Busca cega de unidade:** o endpoint de destino recebe bloco + unidade e **nunca** confirma se existe ou quem mora lá. Resposta idêntica para unidade válida e inválida — **inclusive na linha do tempo de estados**: a visita-sombra percorre os mesmos estados nos mesmos tempos sem notificar ninguém (`03-FLUXOS-E-CONTRATOS.md` §2). Isso impede que qualquer pessoa com o QR enumere quem mora onde — um problema de segurança física antes de ser de privacidade. Detalhes em `06-LGPD-E-SEGURANCA.md`.
|
||||
|
||||
## 7. Multi-tenant preparado, não ativado
|
||||
|
||||
|
||||
@@ -56,7 +56,9 @@ CREATE TABLE condominiums (
|
||||
name text NOT NULL,
|
||||
address text NOT NULL,
|
||||
timezone text NOT NULL DEFAULT 'America/Sao_Paulo',
|
||||
quiet_hours int4range, -- janela de silêncio, ex.: [22,7)
|
||||
-- janela de silêncio ("quiet_hours" nos demais docs); start > end cruza a meia-noite (22 → 7)
|
||||
quiet_start smallint CHECK (quiet_start BETWEEN 0 AND 23),
|
||||
quiet_end smallint CHECK (quiet_end BETWEEN 0 AND 23),
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
version integer NOT NULL DEFAULT 0
|
||||
);
|
||||
@@ -82,7 +84,7 @@ CREATE TABLE units (
|
||||
);
|
||||
```
|
||||
|
||||
`quiet_hours` alimenta a defesa contra "tocar em todos os apartamentos de madrugada": dentro da janela, visitas não-pré-autorizadas vão direto para a fila de operador em vez de acordar moradores.
|
||||
`quiet_start`/`quiet_end` alimentam a defesa contra "tocar em todos os apartamentos de madrugada": dentro da janela, visitas não-pré-autorizadas vão direto para a fila de operador em vez de acordar moradores. Duas colunas em vez de `int4range` de propósito: a janela típica cruza a meia-noite (22 → 7), e `int4range(22, 7)` é um range inválido no Postgres. A verificação "está na janela?" vive em `shared/validation`, testada nos dois sentidos.
|
||||
|
||||
## 3. Pessoas e dispositivos
|
||||
|
||||
@@ -258,7 +260,7 @@ CREATE TABLE access_grants (
|
||||
visit_id uuid NOT NULL REFERENCES visits(id),
|
||||
gate_id uuid NOT NULL REFERENCES gates(id),
|
||||
granted_by uuid NOT NULL REFERENCES persons(id),
|
||||
pin text, -- 6 dígitos, conferência humana
|
||||
pin_hash text, -- hash do PIN de 6 dígitos; o PIN em claro só existe na tela e na notificação
|
||||
valid_until timestamptz NOT NULL,
|
||||
used_at timestamptz,
|
||||
device_result text, -- resultado do AccessControlDevice (v2)
|
||||
@@ -361,6 +363,8 @@ CREATE INDEX idx_audit_entity ON audit_log (tenant_id, entity_type, entity_id, c
|
||||
|
||||
**Append-only.** Nenhum `UPDATE` ou `DELETE`, garantido por permissão do usuário de aplicação no banco. É a defesa jurídica em caso de autorização indevida.
|
||||
|
||||
**Papéis de banco separados — sem isso o revoke é decorativo.** Dono de tabela ignora `REVOKE` e RLS (salvo `FORCE`). Portanto: o Flyway conecta como `portaria` (dono do schema, roda migrations); a aplicação conecta como **`portaria_app`**, papel sem ownership, criado no init do Postgres (ver `05-INFRA-DOCKER.md` §2). Se o backend conectar como dono, nem o append-only do `audit_log` nem as policies de RLS valem nada — e nenhum teste funcional percebe.
|
||||
|
||||
## 9. Row Level Security — preparada, desativada
|
||||
|
||||
```sql
|
||||
|
||||
@@ -42,7 +42,7 @@ Implementada em `shared/state/VisitStateMachine.kt` — a mesma classe roda no a
|
||||
|
||||
| Job | Dispara | Ação |
|
||||
|---|---|---|
|
||||
| `VISIT_RING_TIMEOUT` | 20s após `TOCANDO` | → `ESCALONADA`, toca para os demais `unit_members` por `ring_order` |
|
||||
| `VISIT_RING_TIMEOUT` | 20s após `TOCANDO` (kind `VISITA`) | → `ESCALONADA`, toca para os demais `unit_members` por `ring_order` |
|
||||
| `VISIT_ESCALATE` | 15s após `ESCALONADA` | → `FILA_OPERADOR` se `operator_queue` ativo, senão → `RECADO_EM_VIDEO` |
|
||||
| `VISIT_QUEUE_TIMEOUT` | 120s em `FILA_OPERADOR` | → `RECADO_EM_VIDEO` |
|
||||
| `VISIT_EXPIRE` | `expires_at` (10 min) | → `EXPIRADA` |
|
||||
@@ -66,7 +66,7 @@ VISITANTE BACKEND MORADOR
|
||||
│ {kind, nome, bloco, │ BUSCA CEGA: │
|
||||
│ unidade, foto} │ resolve unit_id ou NULL │
|
||||
├───────────────────────────►│ resposta idêntica sempre │
|
||||
│◄─── {visitId, roomToken} ──┤ │
|
||||
│◄──────── {visitId} ────────┤ │
|
||||
│ │ ── outbox: VisitaCriada ──┤
|
||||
│ │ │
|
||||
│ │ push FCM / VoIP+CallKit │
|
||||
@@ -74,6 +74,8 @@ VISITANTE BACKEND MORADOR
|
||||
│ │ │
|
||||
│ 4. WS /ws/visitor/{id} │ │ 5. atende
|
||||
│◄══ estado em tempo real ══►│◄═══════ WS /ws/app ═══════►│
|
||||
│ │ sala criada em EM_CHAMADA │
|
||||
│◄── ROOM_READY + token ─────┼──── ROOM_READY + token ───►│
|
||||
│ │ │
|
||||
│◄────── LiveKit: sala compartilhada ────────────────────►│
|
||||
│ │ │
|
||||
@@ -83,7 +85,23 @@ VISITANTE BACKEND MORADOR
|
||||
│ ├── notifica responsável ────►
|
||||
```
|
||||
|
||||
**Passo 3 é o ponto crítico de segurança.** A resposta é byte-a-byte idêntica para unidade existente e inexistente, com o mesmo tempo de resposta (comparação em tempo constante e delay artificial se necessário). Se a unidade não existe, a visita entra em `TOCANDO`, ninguém é notificado, e ela expira normalmente. Quem tem o QR não consegue mapear o prédio.
|
||||
**Passo 3 é o ponto crítico de segurança.** A resposta é byte-a-byte idêntica para unidade existente e inexistente, com o mesmo tempo de resposta (comparação em tempo constante e delay artificial se necessário). Quem tem o QR não consegue mapear o prédio.
|
||||
|
||||
### Visita-sombra — a busca cega vale também para a linha do tempo
|
||||
|
||||
Não basta a resposta HTTP ser idêntica: se a visita de unidade inexistente ficasse parada em `TOCANDO` até expirar enquanto a real anda `TOCANDO → ESCALONADA → FILA_OPERADOR`, o atacante distinguiria os dois casos **observando a sequência de estados no WebSocket**. A busca cega tem que valer para a linha do tempo inteira.
|
||||
|
||||
Por isso, quando `unit_id` é `NULL` — ou quando a unidade existe mas não tem nenhum dispositivo ativo — a visita percorre **exatamente os mesmos estados, nos mesmos tempos**, sem notificar ninguém:
|
||||
|
||||
```
|
||||
visita-sombra: TOCANDO (20s, sem push) → ESCALONADA (15s, sem push)
|
||||
├─ operator_queue ativo → FILA_OPERADOR (de verdade,
|
||||
│ marcada para o operador como "unidade não cadastrada")
|
||||
└─ sem módulo → RECADO_EM_VIDEO
|
||||
(listado no admin como "recado sem unidade")
|
||||
```
|
||||
|
||||
Com o módulo de operador, a visita-sombra entra na fila **de verdade**: o operador vê a marcação privada "unidade não cadastrada" e trata como um porteiro humano trataria quem errou o número — pergunta, corrige a unidade e redireciona. Efeito colateral desejável: erro de digitação legítimo ("101A" em vez de "101-A") é **recuperado** em vez de perdido, e a indistinguibilidade fica perfeita — nos dois casos alguém atende.
|
||||
|
||||
## 3. Fluxo B — Entrega (funil rápido)
|
||||
|
||||
@@ -161,7 +179,7 @@ Na v1 a abertura é humana: quem abre confere o PIN na tela do visitante contra
|
||||
| `GET` | `/visits/{id}` | Estado atual (fallback de polling se o WS cair) |
|
||||
| `POST` | `/visits/{id}/message` | Envia recado em vídeo |
|
||||
| `POST` | `/visits/{id}/cancel` | Visitante desiste |
|
||||
| `GET` | `/visits/{id}/room-token` | Token LiveKit com escopo da sala |
|
||||
| `GET` | `/visits/{id}/room-token` | Token LiveKit com escopo da sala — disponível a partir de `EM_CHAMADA`; antes disso, `409` |
|
||||
|
||||
### Morador e operador — `/api/v1/app`
|
||||
|
||||
|
||||
@@ -43,8 +43,9 @@ services:
|
||||
image: postgis/postgis:17-3.5
|
||||
environment:
|
||||
POSTGRES_DB: portaria
|
||||
POSTGRES_USER: portaria
|
||||
POSTGRES_USER: portaria # dono do schema — usado só pelo Flyway
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_APP_PASSWORD: ${POSTGRES_APP_PASSWORD} # lido por init/02-roles.sh → cria portaria_app
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
- ./postgres/init:/docker-entrypoint-initdb.d:ro
|
||||
@@ -60,9 +61,13 @@ services:
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: prod
|
||||
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/portaria
|
||||
SPRING_DATASOURCE_USERNAME: portaria
|
||||
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
LIVEKIT_URL: ws://livekit:7880
|
||||
# aplicação NUNCA conecta como dona do schema — senão REVOKE e RLS são inócuos (02 §8)
|
||||
SPRING_DATASOURCE_USERNAME: portaria_app
|
||||
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_APP_PASSWORD}
|
||||
SPRING_FLYWAY_USER: portaria
|
||||
SPRING_FLYWAY_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
# livekit está em network_mode:host — o nome de serviço não resolve; alcançado via gateway do host
|
||||
LIVEKIT_URL: ws://host.docker.internal:7880
|
||||
LIVEKIT_API_KEY: ${LIVEKIT_API_KEY}
|
||||
LIVEKIT_API_SECRET: ${LIVEKIT_API_SECRET}
|
||||
STORAGE_ENDPOINT: http://minio:9000
|
||||
@@ -73,6 +78,8 @@ services:
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4317
|
||||
volumes:
|
||||
- ./secrets:/secrets:ro
|
||||
extra_hosts:
|
||||
- host.docker.internal:host-gateway
|
||||
depends_on:
|
||||
postgres: { condition: service_healthy }
|
||||
minio: { condition: service_started }
|
||||
@@ -110,6 +117,8 @@ services:
|
||||
redis: # exclusivo do LiveKit e do rate limit
|
||||
image: redis:7-alpine # NUNCA para dados que não podem se perder
|
||||
command: redis-server --save "" --appendonly no
|
||||
ports:
|
||||
- 127.0.0.1:6379:6379 # livekit está em host network e alcança via localhost; nunca exponha fora do host
|
||||
restart: unless-stopped
|
||||
|
||||
minio:
|
||||
@@ -185,6 +194,8 @@ room:
|
||||
|
||||
**Redis não é opcional** com múltiplas instâncias do LiveKit: sem ele, o estado das salas se divide silenciosamente entre nós e dois participantes da mesma sala não se enxergam.
|
||||
|
||||
**Capacidade de relay do coturn:** a faixa 49160–49200 são 40 portas ≈ **20 chamadas simultâneas via TURN**. Suficiente para o piloto (TURN só entra quando o UDP direto falha, tipicamente 10–20% das chamadas), mas amplie a faixa em produção junto com o firewall.
|
||||
|
||||
## 4. Dimensionamento de banda
|
||||
|
||||
Uma chamada de portaria tem 2 participantes, vídeo em 480p a ~600kbps mais áudio. O SFU recebe e reenvia:
|
||||
@@ -218,6 +229,7 @@ GH_OWNER=sua-org
|
||||
TAG=latest
|
||||
|
||||
POSTGRES_PASSWORD=
|
||||
POSTGRES_APP_PASSWORD=
|
||||
LIVEKIT_API_KEY=
|
||||
LIVEKIT_API_SECRET=
|
||||
TURN_SECRET=
|
||||
|
||||
@@ -93,6 +93,7 @@ Prazos são configuráveis por tenant, com **teto** definido pela plataforma. Um
|
||||
- Visita criada mesmo com `unit_id` nulo, preservando `unit_input`
|
||||
- Resposta idêntica em corpo, código e headers
|
||||
- **Tempo de resposta constante** (delay artificial iguala os caminhos — sem isso o ataque vira timing attack)
|
||||
- **Linha do tempo de estados idêntica** — a visita-sombra percorre os mesmos estados nos mesmos tempos, sem notificar ninguém (`03-FLUXOS-E-CONTRATOS.md` §2). Sem isso, o atacante distingue os casos observando a sequência no WebSocket, e o tempo constante da resposta HTTP não protege nada
|
||||
- Nome do morador nunca aparece antes do atendimento
|
||||
- Rate limit por IP e por dispositivo, com bloqueio progressivo
|
||||
|
||||
@@ -128,9 +129,11 @@ REVOKE UPDATE, DELETE ON audit_log FROM portaria_app;
|
||||
|
||||
Nem a aplicação comprometida altera o log. Somado ao `sha256` da mídia, isso é o que dá valor probatório ao registro.
|
||||
|
||||
**Só funciona se a aplicação não for dona do schema** — dono ignora `REVOKE` e RLS. A aplicação conecta como `portaria_app`; o Flyway, como `portaria` (dono). Ver `02-MODELO-DE-DADOS.md` §8.
|
||||
|
||||
### A.6 — Roubo de token
|
||||
|
||||
**Mitigação:** JWT de visitante com 15 min, escopo de uma visita, atado ao `gateId` e ao IP de origem. Refresh do morador no Keystore/Keychain com biometria de dispositivo. Rotação de refresh token e revogação por dispositivo no admin.
|
||||
**Mitigação:** JWT de visitante com 15 min, escopo de uma visita, atado ao `gateId` e à sessão — **não ao IP de origem**: IP de celular muda no meio da sessão (troca de torre, CGNAT) e a amarração derrubaria visitante legítimo no meio do fluxo. A defesa é a posse do token somada à validade curta e ao escopo mínimo. Refresh do morador no Keystore/Keychain com biometria de dispositivo. Rotação de refresh token e revogação por dispositivo no admin.
|
||||
|
||||
### A.7 — QR vazado ou clonado
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ Um condomínio só entra em produção com todos os itens verificados **em campo
|
||||
- [ ] **Wi-Fi aberto de visitante** com cobertura medida em cada portaria
|
||||
- [ ] SSID óbvio: `Portaria-<NomeDoCondominio>`
|
||||
- [ ] **Captive portal** que abre direto a página do visitante
|
||||
- [ ] **Portal instrui a abrir no navegador padrão** — o mini-navegador do captive portal (CNA do iOS, e vários Androids) **bloqueia câmera e geolocalização**, as duas permissões de que o fluxo depende. O portal exibe botão/instrução "abrir no Safari/Chrome", e o teste de ativação percorre o fluxo completo **a partir do portal**, não de um navegador já aberto
|
||||
- [ ] Rede **isolada** da rede administrativa (VLAN separada, sem acesso à LAN interna)
|
||||
- [ ] Banda mínima 5 Mbps simétricos reservados para o visitante
|
||||
- [ ] Teste de sinal ≥ -70 dBm no ponto exato onde o visitante fica
|
||||
@@ -47,6 +48,8 @@ A rede de visitante isolada não é detalhe: uma rede aberta com acesso à LAN d
|
||||
|
||||
O botão físico é o que atende quem não tem celular, está sem bateria ou não consegue usar o QR. **Sem ele, o produto exclui uma parcela real de visitantes** — e cria a situação em que alguém legítimo simplesmente não consegue entrar.
|
||||
|
||||
**Na v1, o botão é deliberadamente um interfone direto (GSM/4G ou SIP) para o responsável/central — fora da plataforma.** É proposital: ele precisa continuar funcionando exatamente quando a plataforma cai, então não pode depender dela. Integrá-lo como dispositivo da plataforma (disparando o fluxo de visita com operador) é v2, atrás de `AccessControlDevice`.
|
||||
|
||||
### 2.4 Aviso legal — obrigatório
|
||||
|
||||
- [ ] **Placa física** com o aviso de tratamento de dados, visível antes da coleta
|
||||
|
||||
@@ -40,7 +40,7 @@ Um controller `GET /api/v1/health` devolvendo `{"status":"UP"}`, só para provar
|
||||
|
||||
### 6. `infra/`
|
||||
|
||||
`docker-compose.yml` exatamente como em `docs/05-INFRA-DOCKER.md` §2, mais `docker-compose.observability.yml` e `.env.example`. Configs em `livekit/livekit.yaml`, `traefik/`, `postgres/init/01-extensions.sql` (`CREATE EXTENSION postgis; CREATE EXTENSION pgcrypto;`).
|
||||
`docker-compose.yml` exatamente como em `docs/05-INFRA-DOCKER.md` §2, mais `docker-compose.observability.yml` e `.env.example`. Configs em `livekit/livekit.yaml`, `traefik/`, `postgres/init/01-extensions.sql` (`CREATE EXTENSION postgis; CREATE EXTENSION pgcrypto;`) e `postgres/init/02-roles.sh` (cria o papel `portaria_app`, sem ownership, com a senha de `POSTGRES_APP_PASSWORD` — ver `02-MODELO-DE-DADOS.md` §8).
|
||||
|
||||
### 7. CI — GitHub Actions
|
||||
|
||||
|
||||
@@ -73,7 +73,9 @@ Filtro/interceptor que lê `Idempotency-Key`, consulta `idempotency_keys`, e:
|
||||
|
||||
Spring Security com três cadeias de filtro conforme `06-LGPD-E-SEGURANCA.md` §6: `/api/v1/visitor/**` (JWT efêmero), `/api/v1/app/**` (OIDC), `/api/v1/admin/**` (OIDC + MFA).
|
||||
|
||||
Emissão e validação do JWT de visitante: 15 min, escopo de uma visita, atado a `gateId` e IP.
|
||||
Emissão e validação do JWT de visitante: 15 min, escopo de uma visita, atado a `gateId` e à sessão — **não ao IP**: IP de celular muda no meio da sessão (CGNAT, troca de torre) e derrubaria visitante legítimo.
|
||||
|
||||
**Conexões ao banco:** Flyway como `portaria` (dono), aplicação como `portaria_app` — sem essa separação, o revoke do `audit_log` e a RLS futura são decorativos (dono ignora ambos). Ver `02-MODELO-DE-DADOS.md` §8.
|
||||
|
||||
### 8. Auditoria
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ Localização ausente ou negada → `403 LOCATION_REQUIRED`. Fora do raio → `4
|
||||
- Resolve `unit_id` a partir de bloco + unidade; **se não existir, cria a visita com `unit_id = NULL`** e preserva `unit_input`
|
||||
- Resposta **idêntica** — mesmo corpo, mesmo status, mesmos headers — nos dois casos
|
||||
- **Tempo de resposta constante:** meça o caminho mais lento e aplique delay artificial no mais rápido. Sem isso o ataque vira timing attack e a busca cega não serve para nada
|
||||
- **Visita-sombra:** com `unit_id` NULL — ou unidade sem nenhum dispositivo ativo — os jobs percorrem a **mesma linha do tempo de estados**, nos mesmos tempos, sem notificar ninguém; com `operator_queue` ativo, entra na fila marcada "unidade não cadastrada" (`03-FLUXOS-E-CONTRATOS.md` §2). Sem isso, a sequência de estados no WebSocket entrega o que o tempo constante escondeu
|
||||
- Nome de morador nunca aparece na resposta
|
||||
- Rate limit por IP e por dispositivo, com bloqueio progressivo
|
||||
|
||||
@@ -91,6 +92,7 @@ Importação CSV de unidades com **dry-run obrigatório** — devolve o que seri
|
||||
|
||||
- [ ] Testes de integração com Testcontainers cobrindo os fluxos A, B e C ponta a ponta
|
||||
- [ ] **Teste de busca cega:** unidade existente e inexistente produzem resposta idêntica, e a diferença de tempo fica abaixo do ruído de medição
|
||||
- [ ] **Teste de visita-sombra:** unidade inexistente percorre a mesma sequência de estados, nos mesmos tempos, que uma visita real não atendida — e ninguém é notificado
|
||||
- [ ] Teste: geofence rejeita fora do raio e aceita dentro
|
||||
- [ ] Teste: `quiet_hours` desvia do morador
|
||||
- [ ] Teste: dois `resolve` concorrentes — um vence, outro recebe `409` com o autor
|
||||
|
||||
@@ -22,7 +22,7 @@ Login OIDC com **MFA obrigatório** — este perfil vê fotos de todos os visita
|
||||
|
||||
### 2. Auditoria de visitas — a tela mais importante
|
||||
|
||||
Tabela virtualizada com filtros persistidos na URL: período, condomínio, bloco, unidade, tipo, estado, **dentro/fora do geofence**, e quem resolveu.
|
||||
Tabela virtualizada com filtros persistidos na URL: período, condomínio, bloco, unidade, tipo, estado, **dentro/fora do geofence**, quem resolveu, e **"unidade não cadastrada"** (visitas-sombra: erro de digitação legítimo ou tentativa de enumeração — recorrência do mesmo dispositivo em unidades diferentes é padrão de varredura e deve gerar destaque).
|
||||
|
||||
Colunas: data/hora, tipo, visitante, unidade, estado, quem resolveu, duração, selo de geofence.
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ Mesmo binário Compose Multiplatform, perfil decidido por `GET /me`. Layout mais
|
||||
|
||||
**Contexto** à direita, antes de assumir: histórico da unidade, visitas recentes, regra de entrega, nomes dos moradores, telefone. **O operador nunca atende sem contexto** — atender às cegas é o que faz a portaria remota parecer pior que a física.
|
||||
|
||||
**Visitas-sombra** (`unit_id` nulo — busca cega, `03-FLUXOS-E-CONTRATOS.md` §2) chegam à fila marcadas como **"unidade não cadastrada"**, visível só para o operador. Ele trata como um porteiro trataria quem errou o número: pergunta, corrige a unidade (`PATCH` que resolve o `unit_id` e registra em `audit_log`) e redireciona — ou nega. Para o visitante, nada distingue esse atendimento de um normal.
|
||||
|
||||
Presença: `DISPONIVEL` / `EM_ATENDIMENTO` / `OFFLINE`, com heartbeat. Operador que perde conexão volta para `OFFLINE` automaticamente e suas visitas retornam à fila.
|
||||
|
||||
Ao resolver, o operador registra `resolution_reason` — obrigatório, e é o que alimenta a análise de por que o modelo autônomo falhou naquele caso.
|
||||
|
||||
@@ -18,7 +18,7 @@ Playwright para os webs, Maestro para os apps. Cenários obrigatórios:
|
||||
2. Entrega em um toque, resolvida pela notificação, em menos de 10s
|
||||
3. Escalonamento completo: ninguém atende → outros moradores → fila → operador → autorizado
|
||||
4. Recado: ninguém atende, sem módulo de operador → grava → morador resolve depois
|
||||
5. Unidade inexistente: visita criada, ninguém notificado, expira — **resposta indistinguível de unidade válida**
|
||||
5. Unidade inexistente: visita criada, ninguém notificado, e a **sequência de estados vista pelo visitante é idêntica** à de uma visita real não atendida (visita-sombra) — incluindo os tempos das transições
|
||||
6. Fora do geofence: bloqueado com mensagem acionável
|
||||
7. Quiet hours: desvia sem tocar em ninguém
|
||||
8. Dois moradores respondendo juntos: um vence, o outro vê quem resolveu
|
||||
@@ -56,7 +56,7 @@ Derrube cada peça, uma por vez, com uma visita em andamento, e verifique o comp
|
||||
|
||||
Percorra o modelo de ameaças de `06-LGPD-E-SEGURANCA.md` §5 e prove cada mitigação:
|
||||
|
||||
- [ ] **A.1 Busca cega** — tempo de resposta constante medido estatisticamente, não no olho. Este é o teste mais importante da fase
|
||||
- [ ] **A.1 Busca cega** — tempo de resposta constante medido estatisticamente, não no olho, **e** linha do tempo de estados idêntica entre unidade real não atendida e visita-sombra. Este é o teste mais importante da fase
|
||||
- [ ] **A.2 Geofence** — rejeita fora do raio; GPS falsificado é registrado
|
||||
- [ ] **A.3 DoS social** — rate limit e quiet hours funcionam; varredura gera alerta
|
||||
- [ ] **A.4 Mídia** — nenhuma URL pública; assinada expira; acesso auditado
|
||||
|
||||
Reference in New Issue
Block a user