teqo-backup/docs/changelog/2026-09-27-c234.md
Francisco Solla 9e9864f420 feat(C234): busca por selfie no álbum público — índice A/C consentido, selfie on-device
- /fotos/encontre + POST /api/fotos/selfie: só o vetor 128-d, sem imagem, sem score e
  sem nomes de terceiros; fail-closed pela flag photoAlbum.selfieSearchEnabled (default
  false) e pelos Consents por chave estável (busca-selfie-fotos/busca-selfie-indice, não
  seedados — a superfície nasce fechada até o aval jurídico/DPIA).
- Índice A/C do gate (PR #1370): só quem aderiu é indexado; descriptors do acervo são
  transitórios. Collection faceSubject (admin-only, sem vínculo com Contact), vínculos
  matchedPhotos e marker faces.checkedKey no archivePhoto (modelo + enrolledAt).
- Engine @vladmandic/face-api nos dois lados: browser lazy com modelos same-origin
  (copy-face-vision-assets no prebuild/predev/Docker) e CLI node-wasm sem tfjs-node.
  CLIs ops-only faces:enroll / faces:index --apply|--verify com guardas C231/C232.
- Opt-out self-service por reconhecimento (leave-index) + canal de remoção do C233;
  rate limit próprio (namespace selfie:<hash>) e bounded body extraído do beacon.
- UI portada classe-a-classe do design estendido (paridade certificada, tier primário);
  e2e frontendFotosSelfie (manifest+curado+projeto, engine stubada) e canário do engine
  real; runbook §C234, contrato em AGENTS-public.md e changelog.
- Migration aditiva add_face_subject (SQL revisado); hard-stops aprovados na sessão.
2026-09-27 20:04:41 -03:00

3.2 KiB

C234 — Busca por selfie — visitante encontra as fotos em que aparece (2026-09-27): o álbum público do C233 ganha a busca por selfie fechada por padrão: /fotos/encontre (noindex, 404 com a flag photoAlbum.selfieSearchEnabled desligada — default false — ou o álbum fechado; estado fail-closed da cena 7 sem o Consent) e POST /api/fotos/selfie, que aceita só o vetor 128-d (JSON ≤4 KB, same-origin, rate limit 20/10 min por IP hasheado com chave própria selfie:<hash> sobre o dono do beacon; sem imagem, sem score, sem nomes de terceiros). A selfie nunca sai do aparelho: o engine @vladmandic/face-api (mesmos três nets do CLI, tinyFaceDetector+faceLandmark68Net+faceRecognitionNet, opções compartilhadas em src/lib/faceSearch.ts) roda no browser com os modelos same-origin (/fotos/modelos, artefato de build copiado por scripts/copy-face-vision-assets.mjs no prebuild/predev/Docker) e no lote pela build node-wasm (sem tfjs-node, sem browser headless). Escopo A/C do gate (PR #1370): só quem aderiu é indexado; índice anônimo do acervo não existe. A collection nova faceSubject (admin-only, sem vínculo com Contact) guarda o descriptor consentido, o snapshot do consentHash de busca-selfie-indice e os vínculos derivados matchedPhotos; o enrollment e a indexação são ops-only (pnpm faces:enroll, pnpm faces:index --apply|--verify, guardas C231/C232, recibos sem vetor/bytes em data/face/reports/), com descriptors do acervo transitórios (nunca persistidos) e o marker archivePhoto.faces.checkedKey (modelo + enrolledAt de cada sujeito elegível — os writes do próprio lote não invalidam a revisão). A consulta resolve os dois Consents por chave estável (busca-selfie-fotos/busca-selfie-indice, não seedados: a superfície nasce fechada até o aval jurídico/DPIA), exige status: active + modelo corrente + hash vigente (texto editado ⇒ inelegível até re-consentimento) e cruza o match com o read aprovado do C233 (draft/removida nunca aparecem; a foto removida some sem deploy). Opt-out self-service por reconhecimento ("Minha presença" → leave-index apaga vetor e vínculos, status removed; sem match, encaminha ao canal de remoção do álbum). Migration aditiva add_face_subject (tabela + join + colunas do marker + flag do global; SQL revisado; nada do acervo muda). UI portada classe-a-classe do design estendido (entrada entre hero e facetas com o submit do álbum secundário, consentimento com o richText do Consent, escolha com prévia local, processando, resultado em grade larga sem "Quem aparece", vazio honesto A/C, presença + diálogo destrutivo com trap de Tab, e os estados de navegador incompatível/erro/limite/rede) e Design tier: openai/gpt-5.6-sol com paridade certificada contra o app renderizado (3 rodadas, 390/1280). Testes: unit do contrato puro/limiar/schema/parsers/guardas, int do enrollment+índice e do endpoint sobre teqo_test (só aprovadas, sem nomes/score, opt-out, rate limit, same-origin), e2e novo frontendFotosSelfie (manifest + curado + projeto, engine stubada) e canário manual do engine real (enroll→index→vínculo). Runbook §C234 e contrato em AGENTS-public.md. Impl plan: docs/plans/busca-fotos-por-selfie-impl.md.