diff --git a/.env.example b/.env.example
index 9f6761a8..9e0f266c 100644
--- a/.env.example
+++ b/.env.example
@@ -36,6 +36,14 @@ NEXT_PUBLIC_SITE_URL=http://localhost:3000
# NEXT_PUBLIC_CARDS_CUTOUT_STUB=1 pnpm build
NEXT_PUBLIC_CARDS_CUTOUT_STUB=
+# C234 — test seam for the on-device selfie-search engine. `1` swaps the
+# face-api engine (and its ~7 MB of same-origin models) for a deterministic
+# descriptor derived from the file bytes (e2e builds and the Playwright
+# webServer only; the real engine must run in dev/production). Local E2E_PROD=1
+# builds set it too:
+# NEXT_PUBLIC_FACE_SEARCH_STUB=1 pnpm build
+NEXT_PUBLIC_FACE_SEARCH_STUB=
+
# Shared secret guarding POST /api/revalidate, which busts ISR cache tags on demand.
# Needed after direct-DB writes (e.g. `pnpm db:seed:posts`, Onda 0 migrations) since those
# bypass the deployed Payload hooks. Default tag is `posts`; allowlisted tags also include
diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml
index d08822fc..8f508df8 100644
--- a/.github/workflows/ci-pr.yml
+++ b/.github/workflows/ci-pr.yml
@@ -224,6 +224,7 @@ jobs:
# S15 — deterministic cutout stub: the e2e build never downloads the
# MediaPipe wasm nor runs inference.
NEXT_PUBLIC_CARDS_CUTOUT_STUB: '1'
+ NEXT_PUBLIC_FACE_SEARCH_STUB: '1'
- name: E2E tests (blast radius — manifest-selected or curated specs)
if: steps.scope.outputs.e2e_mode == 'selected' || steps.scope.outputs.e2e_mode == 'curated'
# OPS72 + OPS86: the PR never runs e2e full — a high-risk diff runs
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 82e2b973..cf4386bd 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -198,6 +198,7 @@ jobs:
# S15 — deterministic cutout stub: the e2e build never downloads the
# MediaPipe wasm nor runs inference.
NEXT_PUBLIC_CARDS_CUTOUT_STUB: '1'
+ NEXT_PUBLIC_FACE_SEARCH_STUB: '1'
- name: E2E tests (full suite, single job, 4 workers)
# Explicit CI workers; the local default stays 2 (playwright.config.ts).
# No `--` separator: pnpm would forward it verbatim to playwright and
diff --git a/.gitignore b/.gitignore
index 16bd3f49..47e19ca6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -88,6 +88,7 @@ next-env.d.ts
/data/acervo-index/
# C232 — photo-archive cataloguing receipts (pnpm archive:catalog)
/data/archive/
+/data/face/
# Solla × Ceuci report HTML cache (scripts/build-solla-ceuci-salvador-report.mjs)
/data/solla-ceuci-salvador/
@@ -154,6 +155,7 @@ next-env.d.ts
# S15 — MediaPipe wasm runtime, copied from node_modules at dev/build time
/public/cards/mediapipe/
+/public/fotos/modelos/
/data/encode/
/data/poster-candidates/
diff --git a/AGENTS-public.md b/AGENTS-public.md
index f494e5dc..63276a64 100644
--- a/AGENTS-public.md
+++ b/AGENTS-public.md
@@ -6,6 +6,14 @@ The public album over the private archive (`archivePhoto`, C231/C232): **only `p
The album global `photoAlbum` holds the two product knobs: `published` (kill switch — **only `false` closes**; an absent row means the schema default `true`) and `removalChannelUrl` (the visible "é você nesta foto? peça a remoção" channel, never invented in code). Publishing the album or approving a photo **requires a valid channel** (fail-closed guard in the global and in the collection); the album has no `loading.tsx` on purpose — a Suspense boundary would turn the closed album's `notFound()` into a `200`. The "Fotos" discovery link in `CampaignFooter` appears while there are approved photos (and always on `/fotos` itself).
+## Selfie search (`/fotos/encontre`) — C234
+
+The visitor's photo never leaves their device: the browser runs the face-api nets locally (models served same-origin from `/fotos/modelos`, copied by `scripts/copy-face-vision-assets.mjs` in prebuild/predev/Docker) and posts only the 128-float descriptor to `POST /api/fotos/selfie` (`{ vector, intent: 'search' | 'leave-index' }`, JSON ≤4 KB, same-origin, rate-limited 20/10 min per hashed IP with its own key namespace). The answer is a list of **approved** photos (`{ ok, found, photos }`), never a score, a percentage or a third-party name — the view model (`src/lib/faceSearch.ts` `toFaceSearchPhotoView`) has no such fields by shape. Recusals: 400 body, 403 origin, 429 throttle, 404 when `photoAlbum.published === false` or `selfieSearchEnabled !== true`, 503 when the query Consent is missing.
+
+**The index is A/C by gate decision (PR #1370): only people who adhered and consented are searchable; there is no anonymous face index of the archive (that is option B, gated on DPIA + legal review).** Enrollment and indexing are ops-only CLIs (`pnpm faces:enroll`, `pnpm faces:index --apply|--verify`), run from the maintenance service with the same write guards as C231/C232 (`TEQO_ENV`, `FACE_*_CONFIRM=1`, mirrored `S3_*`). The batch computes descriptors transiently and persists only the photo↔subject links (`faceSubject.matchedPhotos`) plus the `archivePhoto.faces.checkedKey` marker (model + each subject's `enrolledAt`, so the batch's own writes never invalidate it). The CLI engine (`scripts/lib/faceEngine.mjs`) loads the same nets from `node_modules`; a model change invalidates the index (re-enroll + `--refresh`).
+
+Both sides are **fail-closed**: `selfieSearchEnabled` defaults to `false`, and the two Consent keys (`busca-selfie-fotos` for the query, `busca-selfie-indice` for enrollment, in `src/lib/campaignConsentKeys.ts`) are never seeded — without the admin row with the exact key the page renders the closed state and the API refuses. Editing the enrollment text makes every subject ineligible until re-consent (`consentHash` snapshot). Opt-out is self-service ("Minha presença" → face match removes the vector and the links, status `removed`); photo takedown keeps using `photoAlbum.removalChannelUrl`. E2E builds stub the engine with `NEXT_PUBLIC_FACE_SEARCH_STUB=1` (`window.__faceSearchStub`); the real engine is exercised by the CLI canary and manually. Runbook: `docs/ops/teqo-1313-deploy.md` §C234.
+
## Posts & Tags (news / publications)
The public news/publications system is backed by two collections (both in the `Publicações` admin group) plus the helpers in `src/utilities/posts.ts`.
diff --git a/Dockerfile b/Dockerfile
index 821e24a8..599ee9c4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -47,6 +47,10 @@ ENV NEXT_TELEMETRY_DISABLED=1
# same-origin (the files are gitignored build artifacts, never committed).
RUN node scripts/copy-card-vision-assets.mjs
+# C234 — copy the face-api model weights into public/ so the selfie search runs
+# same-origin too (same gitignored-artifact contract).
+RUN node scripts/copy-face-vision-assets.mjs
+
# OPS99: Generate importMap with dummy S3_* envs BEFORE next build.
# This ensures the production image always has the S3ClientUploadHandler entry,
# eliminating the class of bugs where the admin goes blank (OPS69/OPS72/OPS73).
diff --git a/docs/changelog/2026-09-27-c234.md b/docs/changelog/2026-09-27-c234.md
new file mode 100644
index 00000000..af93cd13
--- /dev/null
+++ b/docs/changelog/2026-09-27-c234.md
@@ -0,0 +1 @@
+**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:` 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`.
diff --git a/docs/ops/teqo-1313-deploy.md b/docs/ops/teqo-1313-deploy.md
index e42ebc6f..f285eb0f 100644
--- a/docs/ops/teqo-1313-deploy.md
+++ b/docs/ops/teqo-1313-deploy.md
@@ -1429,6 +1429,65 @@ Rollback: a migration `add_archive_photo_catalog` é aditiva; o rollback funcion
é limpar o grupo `catalog`/`curatedFields` no admin. Reexecutar nunca duplica nem
sobrescreve curadoria.
+## C234 — busca por selfie no álbum público
+
+A busca por selfie (`/fotos/encontre` + `POST /api/fotos/selfie`) nasce
+**fechada**: a flag `photoAlbum.selfieSearchEnabled` é `false` por default e o
+fluxo só abre com o **Consent de consulta** (`busca-selfie-fotos`) configurado
+no admin — sem ele a página mostra o estado fail-closed e a API recusa (503).
+O índice é A/C (decisão do gate, PR #1370): só rostos de pessoas que aderiram
+entram; não existe índice anônimo do acervo. A selfie nunca sai do aparelho do
+visitante (o navegador envia só o vetor 128-d).
+
+### Abertura (depois do aval jurídico/DPIA registrado)
+
+1. No admin (`/admin`), crie os dois Consentimentos com as chaves exatas:
+ `busca-selfie-fotos` (consulta, texto mostrado no fluxo) e
+ `busca-selfie-indice` (adesão, assinado com a pessoa no enrollment). Sem os
+ dois, o fluxo não abre / ninguém é elegível.
+2. Inscreva cada pessoa que aderiu (selfie com **um** rosto, com a pessoa
+ presente e o consentimento assinado). Rode no homeserver, no serviço de
+ manutenção do alvo (o engine roda local, sem endpoint externo):
+ ```bash
+ cd ~/stack
+ # plano (dry-run: detecta o rosto e resolve o consentimento; não grava)
+ docker compose --profile maintenance run --rm -v /srv/face-selfies:/app/data/face-selfies:ro -v /srv/face-reports:/app/data/face/reports teqo-1313-migrate pnpm faces:enroll --label "" --selfie /app/data/face-selfies/.jpg
+ # grava (exige TEQO_ENV=production e FACE_ENROLL_CONFIRM=1):
+ docker compose --profile maintenance run --rm -e TEQO_ENV=production -e FACE_ENROLL_CONFIRM=1 -v /srv/face-selfies:/app/data/face-selfies:ro -v /srv/face-reports:/app/data/face-reports teqo-1313-migrate pnpm faces:enroll --label "" --selfie /app/data/face-selfies/.jpg --apply
+ ```
+3. Indexe o acervo aprovado para os inscritos (canário primeiro):
+ ```bash
+ docker compose --profile maintenance run --rm -e TEQO_ENV=production -e FACE_INDEX_CONFIRM=1 -v /srv/face-reports:/app/data/face/reports teqo-1313-migrate pnpm faces:index --apply --limit 20
+ # sem --limit processa todas as aprovadas; adicionar/reenrolar alguém
+ # muda a revisão e a próxima execução reprocessa tudo (~6,5k fotos).
+ docker compose --profile maintenance run --rm teqo-1313-migrate pnpm faces:index --verify # inventário read-only; sai 1 com pendências
+ ```
+4. Só então ligue `Busca por selfie` no global **Álbum de fotos** (admin). O
+ kill switch é imediato (sem deploy); desligar fecha a página (404) e a API.
+
+### Remoção e re-consentimento
+
+- **Sair do índice**: a própria pessoa usa "Minha presença" no fluxo (selfie
+ confere o vínculo; o vetor e os links são apagados e o status vira
+ `removed`). O pedido de **remoção de foto** continua no canal do álbum.
+- **Texto do Consent editado**: os inscritos ficam inelegíveis até
+ re-consentimento (o `consentHash` do enrollment é comparado com o texto
+ vigente) — re-rode `faces:enroll --subject ` com a pessoa.
+- **Troca de modelo do engine**: `FACE_SEARCH_MODEL` muda ⇒ o índice inteiro
+ fica stale; re-inscreva (`--subject`) e rode `faces:index --refresh`.
+- **Rollback**: desligue a flag; a migration `add_face_subject` é aditiva
+ (tabela `face_subject`, colunas `faces_*` em `archive_photo` e
+ `selfie_search_enabled` no global). Nada do acervo público muda.
+
+### Guardas e recibos
+
+`--apply` exige `TEQO_ENV` casando o banco exato, `FACE_ENROLL_CONFIRM=1` /
+`FACE_INDEX_CONFIRM=1` fora do dev local e as quatro `S3_*` (mídia espelhada);
+`ALLOW_REMOTE_DB` é recusado. Recibos JSON em `data/face/reports/` (nunca
+levam vetor nem bytes de imagem). O engine carrega os mesmos modelos do
+navegador de `node_modules`; a cópia same-origin para o browser é artefato de
+build (`scripts/copy-face-vision-assets.mjs`).
+
## Referências
- `scripts/deploy-homeserver.sh` — o script (fonte da verdade do fluxo; parametrizado por `TEQO_ENV`)
diff --git a/docs/plans/busca-fotos-por-selfie-impl.md b/docs/plans/busca-fotos-por-selfie-impl.md
new file mode 100644
index 00000000..4d5e796b
--- /dev/null
+++ b/docs/plans/busca-fotos-por-selfie-impl.md
@@ -0,0 +1,250 @@
+# Impl: C234 — Busca por selfie — visitante encontra as fotos em que aparece
+
+Status: aprovado (modo `--auto`) — hard-stops pendentes de aprovação humana explícita na sessão (migração de schema e contrato público)
+Atualizado em: 2026-09-27
+Issue: #1369
+Intenção: docs/plans/busca-fotos-por-selfie.md
+Design UI (gate): docs/plans/busca-fotos-por-selfie-ui-design.html
+Appetite restante: herdado (~2–3 dias eng + ops de enrollment/index + aval jurídico de produto) — **uma superfície nova de biometria sobre o acervo aprovado do C232/C233** (`/fotos/encontre` + `POST /api/fotos/selfie`), sem segundo cadastro de pessoa. Cortes explícitos para caber: enrollment e indexação são **ops-only (CLI)**; sem UI de enrollment; sem captcha; sem modo B (rostos anônimos do acervo); sem retenção de selfie; sem score.
+
+> Aprovado pelo próprio agente via `--auto` (modo autônomo do `work-issue`); os hard-stops continuam exigindo aprovação humana explícita e estão **pendentes** (migração de schema, contrato público, Consent/DPIA, dependência de engine e enrollment ops-only). **Decisão do gate (PR #1370): "Escopo do índice facial A/C no MVP (B só com DPIA + aval jurídico)".** Interpretação canônica a implementar: **só entram no índice rostos de pessoas que aderiram/consentiram**; a busca por selfie só devolve se o rosto consultante já estiver indexado; **índices anônimos de rostos do acervo são proibidos** (isso é B). Timing: superfície pública de biometria **não abre** sem revisão jurídica — o código nasce **fechado** (flag própria default `false` + Consent fail-closed); a pessoa geral (não indexada) recebe **vazio honesto**.
+
+## Leitura da intenção
+
+- **Outcome:** com consentimento ativo e adesão ao índice, o visitante tira/envia uma selfie, o reconhecimento roda no dispositivo (a imagem nunca sai dele) e ele recebe somente as fotos **públicas aprovadas** em que aparece — sem nomes de terceiros e sem score; sem consentimento configurado o fluxo recusa com linguagem clara (nem na UI nem no servidor); a remoção (sair do índice / despublicar foto) funciona e fica registrada.
+- **O que NÃO negociar:**
+ - **Escopo A/C travado pelo gate:** índice só de rostos consentidos; busca que só devolve para rosto já indexado; proibido indexar rostos anônimos do acervo (B) sem DPIA + aval jurídico.
+ - **Fail-closed de Consent/LGPD:** sem Consent resolvido por `key` estável, o fluxo não habilita — nem na UI, nem no servidor (conceito travado do repo).
+ - **A selfie nunca sai do dispositivo:** ao servidor vai só o vetor (128 floats), nunca imagem; sem upload, sem galeria, sem retenção.
+ - **Resposta mínima:** só as fotos aprovadas da pessoa consultante; nunca “outras pessoas parecidas”, nunca nome de terceiro, nunca score/percentual em nenhum estado.
+ - **Nada de foto privada/bruta:** o resultado é o mesmo acervo aprovado do C232/C233 (`publicationStatus: 'approved'`, `archivePhotoIsPublic` — `src/lib/archivePhotoCatalog.ts:128-129`).
+ - **Sem cadastro paralelo:** `faceSubject` não é pessoa; sem vínculo com `Contact`/leadership; mínimo PII (label interno + consent + vetor).
+ - **Antiabuso anônimo** (rate limit, same-origin, body cap), sem captcha no MVP.
+- **O que reavaliar (hipóteses da “Direção no codebase”):**
+ - A intenção cita `src/utilities/ai/rateLimit.ts` como antiabuso; o dono real do throttle anônimo por IP é `src/utilities/content/contentEventRateLimit.ts` (D8) — `ai/rateLimit.ts` é por usuário de chat, não serve.
+ - A resposta do C233 inclui `people`/`peopleLabel` (`src/lib/archivePhotoPublicCatalog.ts:208-230,285`) — o view model do C234 **precisa** cortar esses campos (D5/D9).
+ - O CTA “Encontre você nas fotos” foi deliberadamente removido do artefato do C233 (`src/components/fotos/ArchivePhotoHero.tsx:3-6`) — o C234 traz a entrada própria (D9).
+ - “Upload consentido com retenção zero” da intenção: **rejeitado** — o caminho é vetor-only (sem upload), o que já satisfaz o aceite por construção.
+
+## Abordagem recomendada
+
+```mermaid
+flowchart LR
+ subgraph OPS["Ops — assessoria · CLI na workstation"]
+ ENR["pnpm faces:enroll\nselfie + consent"] --> SUB[("faceSubject\nlabel · vector 128d\nconsentHash · status")]
+ IDX["pnpm faces:index --apply"] --> DLD["downloadPrivateMediaToFile\nsharp ≤1024px"]
+ DLD --> NODE["engine node-wasm\n@vladmandic/face-api"]
+ NODE --> CMP["distância euclidiana\nthreshold interno 0.45"]
+ SUB --> CMP
+ CMP --> LNK["matchedPhotos\njoin faceSubject↔archivePhoto\ntransação + fresh-read"]
+ end
+ subgraph PUB["Público"]
+ ALB["/fotos · C233"] -- "CTA cena 1" --> ENC["/fotos/encontre\nRSC: publicado? + flag + Consent\nfail-closed · noindex"]
+ ENC --> BRW["engine on-device (lazy)\nselfie NUNCA sai do aparelho"]
+ BRW -- "só o vetor 128d · JSON ≤4KB" --> API["POST /api/fotos/selfie\nsame-origin · rate limit\nintent search|leave-index"]
+ API --> ELG["faceSubject ativos\nconsentHash vigente"]
+ ELG --> APR["getApprovedArchivePhotoItems\n∩ matchedPhotos"]
+ APR -- "view sem nomes de terceiros\nsem score · limite 60" --> ENC
+ API -- "leave-index" --> OUT["status removed\nvector null · removedAt\nmatchedPhotos limpo"]
+ end
+```
+
+**Opções consideradas (abordagem geral):** A) engine único `@vladmandic/face-api` nos dois lados (browser on-device + CLI node-wasm), subjects consentidos em collection nova, vínculos fotos↔subject calculados em lote CLI e consultados por um route handler novo; B) engine/server-side de terceiro com upload da selfie; C) serviço separado (Python/Rust) ou `MediaPipe` no browser.
+**Recomendação:** **A** — mantém a selfie no aparelho (o aceite de produto), usa a mesma família de modelo e a mesma matemática no browser e no CLI, reusa os donos existentes (approval gate do C233, `downloadPrivateMediaToFile`, `withPayloadTransaction`, `getConsentByKey`, guardas de CLI do C231/C232) e cabe no appetite sem infra nova.
+**Rejeitadas:** **B** porque manda biometria de cidadão a terceiro e a selfie sairia do dispositivo (anti-goal explícito); **C** porque cria runtime/serviço novo fora do appetite e sem precedente no repo.
+
+### Componentes / mudanças
+
+**Novos — schema/dados (na MESMA migration aditiva `add_face_subject`)**
+
+- **`src/collections/FaceSubject.ts`** (novo): slug `faceSubject`, labels “Pessoa no índice facial”/“Pessoas no índice facial”, grupo `Comunicação` (mesmo dono do acervo, `ArchivePhoto.ts:286-288`), sem versões; access `payloadAdminOnly` nas quatro operações (precedente `ArchivePhoto.ts:300-305`); campos: `label` (text, required; nome de uso interno — nunca público), `consent` (relationship → `consent`, required), `consentHash` (text, readOnly; snapshot de `hashConsentContent` via `getConsentByKey`, `src/utilities/campaignConsent.ts:72-100`), `vector` (json, `hidden` + readOnly; 128 floats — nunca sai do servidor), `model` (text, readOnly; ex. `face-api@1.7.15/faceRecognitionNet`), `status` (select `active|removed`, default `active`, index), `removedAt` (date, readOnly), `matchedPhotos` (relationship hasMany → `archivePhoto`, readOnly; join table). Sem hooks.
+- **`src/collections/ArchivePhoto.ts`** (editar `:311-505`): grupo `faces` (`hidden: true`) com `checkedAt` (date) e `checkedKey` (text), readOnly — o marker de incremental do lote (D4). `defaultColumns`/access/hooks intocados.
+- **`src/globals/PhotoAlbum.ts`** (editar `:61-80`): campo `selfieSearchEnabled` (checkbox, **default `false`**) — kill switch independente da busca por selfie (o Consent referenciado não pode ser apagado enquanto houver subject, `Consent.ts:8-52` estendido abaixo; sem a flag o “desligar” não teria dono).
+- **`src/collections/Consent.ts`** (editar `:24-90`): somar `faceSubject` às contagens do `beforeDelete` (o Consent da adesão não pode ser apagado por baixo dos subjects).
+- **Migration `add_face_subject`** (`src/migrations/_add_face_subject.ts|.json` + registro no fim de `src/migrations/index.ts`): tabela `face_subject` + join `face_subject_rels`, colunas `faces_checked_at`/`faces_checked_key` em `archive_photo` e `selfie_search_enabled` no global `photo_album`; SQL revisado à mão; nenhuma migration existente tocada; `pnpm migrate:create add_face_subject` → `pnpm migrate` só no banco do worktree → `pnpm generate:types`.
+
+**Novos — contrato puro (client-safe, sem I/O)**
+
+- **`src/lib/faceSearch.ts`** (novo): `FACE_SEARCH_DESCRIPTOR_LENGTH = 128`, `FACE_SEARCH_MODEL = 'face-api@1.7.15/faceRecognitionNet'`, `FACE_SEARCH_MAX_DISTANCE = 0.45` (interno, nunca devolvido), `FACE_SEARCH_RESULT_LIMIT = 60`, `FACE_SEARCH_INTENTS = ['search','leave-index']`, `faceEuclideanDistance(a,b)`, `findFaceMatch({ vector, subjects })` (melhor subject sob o threshold), `faceSubjectIsEligible(subject, { model, consentHash })` (ativo + modelo corrente + hash vigente), `toFaceSearchPhotoView(item)` (corta `people`/`peopleLabel`), `faceStubDescriptorFromBytes(bytes)` (seam determinístico do e2e).
+- **`src/lib/schemas/faceSearch.ts`** (novo): `z.strictObject` do body `{ vector: number[128] finitos, intent: enum }` — fail-closed (mold `src/lib/schemas/contentEvent.ts:37-59`).
+- **`src/lib/campaignConsentKeys.ts`** (editar `:1-40`): `FACE_SEARCH_CONSENT_KEY = 'busca-selfie-fotos'`, `FACE_INDEX_CONSENT_KEY = 'busca-selfie-indice'` + mensagens de ausência (padrão `:16-19`); comentário de cabeçalho ajustado (o arquivo já hospeda a key pública do WhatsApp, `:26`).
+
+**Novos — server-side Payload**
+
+- **`src/utilities/faceSubjects/faceSubjectReads.ts`** (novo, `server-only`): `listSearchableFaceSubjects(payload)` (query `status=active`, `select` explícito de `vector`/`consentHash`/`model`/`matchedPhotos`, `depth 0`, bypass anônimo documentado como `archivePhotoReads.ts:13-24`) e `listFaceSubjects` (para `--verify`). **Sem `unstable_cache`**: a tabela é pequena e o opt-out precisa valer imediatamente (D5).
+- **`src/utilities/faceSubjects/faceSubjectEnrollment.ts`** (novo, `server-only`): `enrollFaceSubject({ payload, label, descriptor, consent, subjectId? })` — grava/atualiza com `overrideAccess: true` (CLI sem sessão) e `context: { faceIndex: true }` (precedente `src/utilities/flickr/archivePhotoCatalog.ts:258-267`).
+- **`src/utilities/faceSubjects/faceSubjectPhotoIndex.ts`** (novo, `server-only`): `listArchivePhotoFaceIndexQueue({ payload, refresh, subjectId })` (aprovadas + marker) e `indexArchivePhotoFaces({ payload, item, analyze })` — baixa bytes com `downloadPrivateMediaToFile` + `resolvePrivateMediaStaticDir` (`src/utilities/privateMedia/privateMediaResponse.ts:314-344,45`), `sharp` ≤1024px, descriptors **transitórios**, e dentro de `withPayloadTransaction` (`src/utilities/payloadTransaction.ts:86-113`) relê os subjects e **substitui** os vínculos `matchedPhotos` (adiciona os que casaram, remove o vínculo que deixou de casar); grava `faces.checkedAt`/`checkedKey`. Analyzer injetado (molde C232).
+- **`src/utilities/content/contentEventRateLimit.ts`** (editar `:17-25,53-73`): budget parametrizável `checkContentEventRateLimit(key, budget)` com o default atual intocado (beacon C213) e budget estrito para a busca (D8).
+- **`src/utilities/boundedRequestBody.ts`** (novo): extrai `readBoundedBody` de `src/app/(frontend)/api/content-events/route.ts:47-78` (cap de bytes em streaming); o route de content-events passa a importar (comportamento idêntico) e o route novo reusa — sem segundo twin.
+
+**Novos — rotas públicas**
+
+- **`src/app/(frontend)/fotos/encontre/page.tsx`** (novo): RSC — `photoAlbum.published === false` → 404; `selfieSearchEnabled !== true` → 404; Consent ausente → 200 com o estado fechado da cena 7; resolve o texto do Consent (renderizado por `ConsentText`, precedente `src/app/(campaign)/campanha/convite/[token]/page.tsx:49-63`) e o `removalChannelUrl`; `robots: { index:false, follow:false }`; **sem `loading.tsx`** (mesma razão do C233: `page.tsx:55-59`).
+- **`src/app/(frontend)/api/fotos/selfie/route.ts`** (novo): `export const dynamic = 'force-dynamic'`; mesmo-origem (`isSameOriginRequest`, `src/utilities/sameOriginRequest.ts:33-64`); body ≤4KB pelo helper extraído; parse zod; rate limit por IP hasheado; re-checa flag + Consent no servidor; `search` → subjects elegíveis → melhor match sob o threshold → `getApprovedArchivePhotoItems()` (`archivePhotoReads.ts:58-61`) filtrado por `matchedPhotos` → view model sem nomes/score, limite 60; `leave-index` → transação (`status:'removed'`, `vector:null`, `removedAt`, `matchedPhotos:[]`). Envelopes: `{ ok:true, found:true, photos }` | `{ ok:true, found:false }` | `{ ok:true, removed:true|false }` | erros `{ ok:false, error }` (400 body, 403 origem, 429 rate limit, 404 fechado por flag/publicação, 503 Consent ausente). **Nenhuma imagem em nenhum request; nada de score; nada de nomes de terceiros.**
+- **`src/app/(frontend)/fotos/page.tsx`** (editar `:119-157`): renderiza `` (link para `/fotos/encontre`) só quando `selfieSearchEnabled === true`; posição validada pelo `designer` (D9).
+
+**Novos — UI client**
+
+- **`src/components/fotos/faceSearchEngine.ts`** (novo): `computeFaceDescriptor(file)` — probes de WebGL/WASM (`cardCutout.ts:97-127`), `import('@vladmandic/face-api')` **lazy** (só quando o fluxo começa), modelos same-origin em `/fotos/modelos/`, cap ≤1024 (face maior quando há mais de um rosto); stub `NEXT_PUBLIC_FACE_SEARCH_STUB=1` no molde `cardCutout.ts:347-394` com `window.__faceSearchStub`.
+- **`src/components/fotos/SelfieSearchFlow.tsx`** (novo, client): máquina de estados (consent → pick → processing → results → empty → presence/confirm → removed/no-removal; + engine/unsupported, rate-limited, network); recebe o texto do Consent já renderizado como `children` (regra de client boundary, `engineering-standards.mdc:22-25`); preview com `URL.revokeObjectURL`; a selfie nunca vai a `fetch`.
+- **`src/components/fotos/SelfieSearchEntry.tsx`** e **`SelfieSearchResultCard.tsx`** (novos): entrada na `/fotos` e card do resultado **sem** “Quem aparece”, reusando `ARCHIVE_PHOTO_CARD`/botões de `archivePhotoClasses.ts`; o clique do card leva ao detalhe do C233 (`/fotos?foto=`), dono único do detalhe.
+
+**Novos — scripts/CLI**
+
+- **`scripts/copy-face-vision-assets.mjs`** (novo, molde `scripts/copy-card-vision-assets.mjs:24-47`): copia `tiny_face_detector` (~190KB) + `face_landmark_68` (~357KB) + `face_recognition` (~6,4MB) de `node_modules/@vladmandic/face-api/model` para `public/fotos/modelos/` (gitignored); idempotente por tamanho; chamado no `prebuild` (`package.json:24`), no `predev` (`:62`) e no `Dockerfile:48`.
+- **`scripts/lib/faceEnrollPlan.mjs`** + **`scripts/lib/faceIndexPlan.mjs`** (novos): parsers de argv e recibos (mold `scripts/lib/archiveCatalogPlan.mjs:1-120`); pinados em `SCRIPTS_SPEC_PINNED` (`scripts/lib/test-affected-core.mjs:31-111`).
+- **`scripts/enroll-face-subject.mjs`** (`pnpm faces:enroll`) e **`scripts/index-archive-faces.mjs`** (`pnpm faces:index` — sem `--subject`: o canário é `--limit`; adicionar sujeito invalida a revisão de qualquer forma) (novos): guardas de `scripts/lib/cli.mjs` (`assertWriteConfirm` `:312-320`, `assertEnvironmentDatabaseTarget` `:238-301`, `mirroredMediaRequired` `:197-202`), recibo JSON datado em `data/face/`, falha isolada e estágio nomeado (molde `scripts/catalog-archive-photos.mjs:129-280`). `package.json` ganha os dois scripts junto de `:37-38`.
+
+**Testes/manifest/migration/ops**
+
+- Testes: ver D10. Pins: entry nova em `scripts/lib/e2e-affected-manifest.mjs` (`:204-240` e `:448-460,493-552` conforme D10), curado `:24-58`, pin `tests/unit/e2eAffectedManifest.unit.spec.ts:54-89`, projeto em `playwright.config.ts:211-220` + env do stub (`:260-300`) e workflow (`ci-pr.yml:226`, `deploy.yml:200`).
+- Docs: `AGENTS-public.md:3-7` (contrato `/fotos/encontre` + API + flag), runbook C234 em `docs/ops/teqo-1313-deploy.md`, `.env.example` (`NEXT_PUBLIC_FACE_SEARCH_STUB=` junto de `:36-37`), `.gitignore` (`/public/fotos/modelos/`, `/data/face/`), `docs/changelog/2026-09-27-c234.md`.
+
+### Dados → forma (se aplicável)
+
+N/A — a intenção é explícita: **não há dado numérico na superfície**. A única saída é a lista de fotos; score/percentual é anti-goal (o view model não tem campo de similaridade — o shape torna a violação impossível, não apenas proibida).
+
+## Decisões de engenharia
+
+### D1 — Engine facial (browser + CLI)
+
+**Opções:** A) `@vladmandic/face-api` 1.7.15 (MIT) nos dois lados — browser `dist/face-api.esm.js` (TFJS pré-bundlado) e CLI `dist/face-api.node-wasm.js` com `@tensorflow/tfjs` + `@tensorflow/tfjs-backend-wasm`; B) MediaPipe `tasks-vision`; C) `@tensorflow/tfjs-node`; D) Playwright headless; E) API de terceiro.
+**Recomendação:** **A** — um único engine e **a mesma matemática** nos dois lados (descriptor 128-d, distância euclidiana, threshold estrito 0.45 interno, nunca devolvido); no browser o import é lazy e os modelos (`tiny_face_detector` + `face_landmark_68` + `face_recognition`, ~7MB) são copiados same-origin para `public/fotos/modelos/` (zero CDN, precedente `copy-card-vision-assets.mjs`); no CLI não há binário nativo (backend wasm do próprio pacote) nem browser — decode com `sharp` (já dependência) → `tf.tensor3d`. O modelo é versionado no registro (`model` no subject; string `face-api@1.7.15/faceRecognitionNet`) e trocá-lo **invalida o índice** (re-enrollment, D4/riscos).
+**Rejeitadas:** **B** porque não tem embedder facial — a geometria de landmarks é fraca demais para similaridade de identidade; **C** porque o binário nativo no Node 24 é risco operacional (build/AJV) por zero ganho; **D** porque a inferência no lote via browser headless é infra frágil (e o C232 já rejeitou esse caminho); **E** porque biometria facial de cidadão não vai a terceiro (guardrail de produto).
+
+### D2 — Dados: `faceSubject`, marker de incremental e kill switch
+
+**Opções:** A) collection nova `faceSubject` + grupo hidden `faces` em `archivePhoto` + flag `selfieSearchEnabled` no global `photoAlbum`, tudo na mesma migration aditiva; B) guardar o descriptor em campos `json` do próprio `archivePhoto`; C) reusar `Contact`/`leadership` ou tabela fora do Payload.
+**Recomendação:** **A** — cada subject é um registro auditável (label, consent vinculado, snapshot do hash do texto, modelo, status, `removedAt`, vínculos), com PII mínima e sem vínculo de pessoa; `matchedPhotos` e `vector` são `readOnly` no admin (só o CLI escreve, com bypass documentado + `context`); `status` indexado distingue ativo de removido sem apagar histórico; o marker `checkedAt` + `checkedKey` (sha256 de `model` + `id:updatedAt` dos subjects elegíveis ordenados) é o **único jeito honesto** de pular fotos sem reprocessar bytes a cada run (sem a key, um subject novo/atualizado geraria skip errado); a flag `selfieSearchEnabled` default `false` é o kill switch da feature — o Consent referenciado **não** pode ser deletado (`Consent.ts:8-52` estendido), então a flag é o “puxar a tomada” sem deploy.
+**Rejeitadas:** **B** porque um vetor por foto não é o modelo de subjects consentidos (sem status/consent/remoção) e mistura o índice ao acervo; **C** porque `Contact` é cadastro de pessoa (invariante do repo) e uma tabela fora do Payload perde migrations/access/admin.
+
+### D3 — Enrollment (ops-only, CLI)
+
+**Opções:** A) CLI `pnpm faces:enroll --label "" --selfie --consent ` (dry-run default; `--apply`; `--subject ` para re-enrollment); B) auto-enrollment público a partir da selfie de consulta; C) tela admin de enrollment já no MVP.
+**Recomendação:** **A** — a adesão é ato da assessoria **com** a pessoa (consentimento assinado, `Consent` próprio de enrollment com key estável `busca-selfie-indice`, hash snapshot no registro); o CLI exige exatamente **1 rosto** na selfie (0 ou >1 recusam, fail-closed), valida a key do consent contra a constante e grava `status active`; guardas `FACE_ENROLL_CONFIRM=1` + `assertEnvironmentDatabaseTarget` + recibo JSON sem vetor nem bytes; `--dry-run` mostra a contagem de rostos e o que seria gravado. Re-enrollment (`--subject`) é o caminho previsto para troca de modelo/atualização (a assessoria mantém as selfies fora do site — nada de imagem é guardado pelo Teqo).
+**Rejeitadas:** **B** porque sem verificação de identidade vira consentimento de terceiro + DoS de opt-out (qualquer um pode remover o rosto alheio do índice) — registrar por quê; **C** adiada com gatilho (assessoria precisar operar sem CLI).
+
+### D4 — Indexação em lote
+
+**Opções:** A) CLI `pnpm faces:index [--apply|--verify] [--limit] [--refresh]` rodando na workstation, vínculos transacionais, descriptors transitórios e marker incremental; B) job in-app disparado por `after()`/fila; C) worker/browser headless.
+**Recomendação:** **A** — só fotos `approved`; por foto, baixa os bytes privados (`downloadPrivateMediaToFile`), redimensiona ≤1024px com `sharp`, detecta **todos** os rostos, calcula descriptors **que nunca são persistidos** (se um subject ativo tiver distância < threshold em qualquer rosto, o vínculo nasce) e, dentro de `withPayloadTransaction` com fresh-read dos subjects, substitui `matchedPhotos` (adiciona novos vínculos e **remove o vínculo que deixou de casar**), gravando o marker; `--refresh` ignora o marker; `--limit` canário; `--verify` é read-only e reporta fotos indexadas/stale/pendentes, subjects ativos/removidos/consentimento vencido e os vínculos por subject (breakdown); recibo JSON honesto em `data/face/`; idempotente e retomável; **documentado que adicionar subject novo reprocessa todas as aprovadas** (a `checkedKey` muda). Guardas: `FACE_INDEX_CONFIRM=1`, alvo de banco declarado, mídia espelhada (S3) — mesmo combo do C232.
+**Rejeitadas:** **B** porque o repo não tem fila e o lote pertence ao container de manutenção (precedente C231/C232); **C** porque browser headless para 6,5k é frágil; e **persistir descriptors anônimos** é proibido (seria B disfarçado).
+
+### D5 — Consulta: rota, API e contrato de resposta
+
+**Opções:** A) `/fotos/encontre` (RSC fail-closed) + `POST /api/fotos/selfie` com `{ vector, intent }`, matching server-side e view model cortado; B) client falando direto com a REST do Payload; C) upload da selfie e matching server-side.
+**Recomendação:** **A** — a página checa `published` (404) e `selfieSearchEnabled` (404) e renderiza a cena 7 (200) sem Consent; o API re-checa tudo no servidor (fail-closed em profundidade), aceita **só o vetor** (JSON ≤4KB, sem multipart/imagem), exige same-origin, aplica rate limit, resolve o Consent de consulta e só então: `search` → subjects elegíveis (`status active` + modelo corrente + `consentHash` vigente) → melhor distância sob **0.45** → fotos **aprovadas** ∩ `matchedPhotos` → view sem `people`/`peopleLabel`/score, no máximo `FACE_SEARCH_RESULT_LIMIT = 60` (mais recentes primeiro; sem paginação no MVP); sem match **ou** sem fotos aprovadas → `{ ok:true, found:false }` (a pessoa não indexada recebe vazio honesto, nunca “parecidos”); `leave-index` → match → transação zera `vector`, marca `removed`, `removedAt` e limpa `matchedPhotos`; sem match → `{ ok:true, removed:false }` e a UI aponta para o canal de remoção do C233 (nunca inventado — `PhotoAlbum.ts:22-37,72-79`). A entrada fica **só na `/fotos`** (cena 1), sem link no header/rodapé no MVP (não anuncia fluxo fechado; posição validada pelo `designer` — D9).
+**Rejeitadas:** **B** impossível/sujo (a collection é admin-only e o client enxergaria índice/metadados); **C** contraria o anti-goal “a selfie não sai do dispositivo” — é o inverso do desenho.
+
+### D6 — Consent: duas chaves estáveis, texto nunca seedado
+
+**Opções:** A) adicionar as keys em `src/lib/campaignConsentKeys.ts` (owner atual, client-safe); B) renomear o registry para módulo neutro e reexportar; C) keys hardcoded no CLI/rota.
+**Recomendação:** **A** — `busca-selfie-fotos` (consulta; resolve no RSC e na API) e `busca-selfie-indice` (adesão; resolve no `faces:enroll` e vira snapshot em `consentHash`); o arquivo já é o dono dos stable keys e já hospeda key pública (`WHATSAPP_SUBSCRIPTION_CONSENT_KEY`, `src/lib/campaignConsentKeys.ts:26`), então só o comentário de cabeçalho muda; a resolução reusa `getConsentByKey`/`requireConsentByKey` (`src/utilities/campaignConsent.ts:72-111`, bypass de leitura documentado) e o texto é renderizado por `ConsentText`; **nenhum texto é seedado por migration** (nasce fechado; assessoria/jurídico criam o Consent no admin quando liberarem), e o `consentHash` vencido deixa o subject **inelegível** (fail-closed) até re-consentimento. O runbook de abertura (criar o Consent com key+texto → `faces:enroll` → `faces:index` → ligar `selfieSearchEnabled` → verificar) fecha a entrega.
+**Rejeitadas:** **B** porque o rename move ~15 import sites sem nenhum ganho de comportamento (gatilho: o nome passar a mentir, ex. 3º fluxo público); **C** porque ID/key hardcoded é proibido pelo invariante.
+
+### D7 — Remoção e atendimento
+
+**Opções:** A) sair do índice self-service por match facial + canal de foto do C233; B) collection de pedidos de remoção com formulário; C) e-mail manual sem registro.
+**Recomendação:** **A** — o opt-out é o próprio match (D5) e o registro do atendimento é o estado do subject (`status removed` + `removedAt`); despublicar foto continua o fluxo do C233 (`publicationStatus: removed` pela staff + `ArchivePhotoRemovalBand`/canal — `ArchivePhoto.ts:340-348`, `AGENTS-public.md:5-7`). Sem collection nova, sem PII nova, sem Consent novo para pedido.
+**Rejeitadas:** **B** (formulário/collection/Consent novos, fora do appetite e do invariante); **C** (não registra e não é fail-closed).
+
+### D8 — Antiabuso
+
+**Opções:** A) reusar o dono do throttle (`contentEventRateLimit.ts`) com budget parametrizável + same-origin + body cap extraído; B) limiter novo para a feature; C) reusar as constantes atuais como estão.
+**Recomendação:** **A** — `checkContentEventRateLimit(key, budget)` mantém o default do beacon (`:17-25,53-73`) e a busca passa budget estrito (`20` requisições / 10 min por IP hasheado; a busca responde a um **oráculo de pertencimento** — testar vetores para descobrir quem está no índice —, então o budget tem de ser bem menor que o de telemetria); `contentEventClientKey` (`:34-41`) continua sendo o único hash de IP (sal por processo, nada persistido); o body reader sai de `content-events/route.ts:47-78` para `boundedRequestBody.ts` e os dois routes consomem o mesmo dono; sem conta e **sem captcha no MVP** (gatilho registrado). O limiter é bar-raiser, não fronteira de segurança — o gate real é o threshold + Consent.
+**Rejeitadas:** **B** (segundo limiter = twin); **C** (120/10min é frouxo demais para biometria); captcha adiado com gatilho (custo de UX/dependência de terceiro).
+
+### D9 — UI e extensão do design (trigger do `designer`)
+
+**Opções:** A) `designer` estende/adapta o artefato **antes** do markup (trigger a/b) e faz a crítica final renderizada (trigger c), com port classe-a-classe; B) portar o artefato literal; C) redesenhar.
+**Recomendação:** **A** — o artefato cobre 8 cenas, mas falta o recorte de execução: (i) **cena 6 no modo A/C** — o vazio honesto precisa dizer que a busca cobre quem autorizou participar do índice (sem prometer “todas as fotos”); (ii) **resultado sem “Quem aparece”** (a cena 5 já não mostra, mas o card do C233 mostra — a variante C234 é nova, `ArchivePhotoCard.tsx:53-55`); (iii) estados novos: engine indisponível (WebGL/WASM off), erro de engine, rate-limited, erro de rede — mensagens honestas (nunca “tente outra foto” genérico, precedente `cardCutout.ts:13-20`); (iv) mapeamento de rota: a cena 2 (modal sobre o álbum) vira a página `/fotos/encontre` e a cena 1 vira a entrada na `/fotos` (posição do aside validada); (v) opt-out sem match (encaminha ao canal do C233) e confirmação da cena 8; (vi) a11y já desenhada (44px, foco visível, `prefers-reduced-motion`) mantida. Port com os contratos de classe de `archivePhotoClasses.ts` e `ConsentText`; crítica final com screenshots 390/1280; `Design tier:` no PR.
+**Rejeitadas:** **B** (o artefato não cobre rota/estados novos — seguir literal é defeito); **C** (design aprovado existe; redesign é desperdício).
+
+### D10 — Verificação por camada (pins e e2e)
+
+**Opções:** A) unit puro + int das fronteiras Payload/HTTP + e2e novo com stub, no manifest/projeto/curado/pin; B) só int; C) e2e de browser para tudo.
+**Recomendação:** **A** — **unit:** `tests/unit/faceSearch.unit.spec.ts` (distância/limiar/melhor match; elegibilidade por modelo+hash; view sem nomes; derivação do stub), `tests/unit/faceSearchRequest.unit.spec.ts` (parse fail-closed do body), `tests/unit/faceEnrollPlan.unit.spec.ts`/`faceIndexPlan.unit.spec.ts` (parsers/recibos), `tests/unit/faceCli.unit.spec.ts` (guardas por subprocesso, molde `archiveCatalogCli.unit.spec.ts`), extensão do unit do rate limit (budget) e das keys de consent. **int:** `tests/int/faceSubjectIndex.int.spec.ts` (enrollment grava; index com analyzer stub cria/remove vínculos; removed não casa; curadoria/aprovação intocadas; transação) e `tests/int/faceSearchApi.int.spec.ts` importando o `POST` (precedente `contentEvents.int.spec.ts:40`): sem Consent → recusa; match devolve **só aprovadas**; **sem score e sem nomes**; subject `removed` não casa; foto `approved→removed` some; opt-out zera vetor e não casa mais; rate limit; same-origin; body inválido. **e2e:** `tests/e2e/frontendFotosSelfie.e2e.spec.ts` (serial) com o engine stubado (`NEXT_PUBLIC_FACE_SEARCH_STUB=1`, seam `faceStubDescriptorFromBytes` compartilhado entre stub e spec — fixtures sem rede), seeds via REST admin (Consent novo, subject, fotos, global) e cleanup; projeto Playwright `frontendFotosSelfie` com `dependencies: ['frontendFotos']` nos **dois** modos (serializa contra o flip do kill switch do álbum; o C233 liga/desliga o global em `frontendFotos.e2e.spec.ts:335-345`); manifest: `frontendFotosSelfie` na entry do C233 (`e2e-affected-manifest.mjs:204-225`) e em `src/lib/schemas`/`src/utilities/content` (`:448-460,493-552`), entry nova com `src/app/(frontend)/api/fotos`, `src/lib/faceSearch`, `src/utilities/faceSubjects`, `src/collections/FaceSubject.ts`, `src/collections/Consent.ts`; curado `E2E_CURATED_SPECS:24-58` + pin `tests/unit/e2eAffectedManifest.unit.spec.ts:54-89` (a migration torna o PR high-risk → conjunto curado). Os scripts/planos novos entram em `SCRIPTS_SPEC_PINNED`.
+**Rejeitadas:** **B** (o contrato HTTP público de biometria pede prova real); **C** (browser para lógica pura é caro e frágil).
+
+## Fases verificáveis
+
+1. **Tracer / schema+server — quota ~40%:** migration `add_face_subject` (collection + join + marker + flag) com SQL revisado + `generate:types`; keys + `src/lib/faceSearch.ts` + schema zod; reads/enrollment/index writes; `POST /api/fotos/selfie`; guard do `Consent.ts`; budget do rate limit + extração do body cap. Unit + int verdes antes de seguir. Gate: `pnpm gate:fast` + int do caminho.
+2. **CLI ops — quota ~20%:** spike do engine node-wasm (`face-api.node-wasm.js` + tfjs wasm backend em Node, 1 foto de fixture) **antes** de construir o CLI; `copy-face-vision-assets.mjs` + hooks (`package.json`, `Dockerfile`), `faceEnrollPlan.mjs`/`faceIndexPlan.mjs`, `faces:enroll`/`faces:index` com guardas e recibo; unit dos parsers/guardas; `--verify`/plano contra o banco do worktree. Gate: `pnpm gate:fast`.
+3. **UI/Design — quota ~30%:** dispatch do `designer` (trigger a/b) e extensão do artefato; components `SelfieSearch*` + engine + stub; `/fotos/encontre` + entrada na `/fotos`; port classe-a-classe; e2e local `--no-deps --project=frontendFotosSelfie`; crítica final (trigger c) com screenshots 390/1280.
+4. **Gates/entrega — quota ~10%:** spec e2e + projeto + manifest + curado + pin; `pnpm gate:fast`, int, e2e local; `/simplify` + `capture-review-debts`; docs (`AGENTS-public.md`, runbook C234, `.env.example`, `.gitignore`, changelog); `pnpm push` → PR Ready com `Closes #1369`, `Design tier` e o status dos hard-stops.
+
+## Rabbit holes / Não escopo (engenharia)
+
+- **“Outras pessoas parecidas”, score/percentual, ranking** — vedados; o shape do view model não tem o campo.
+- **Guardar selfie/descriptor do visitante** (galeria, localStorage, cookie, “melhorar o match”) — vedado; vetor só em memória do request.
+- **Modo B (indexar rostos anônimos do acervo)** sem DPIA/aval — proibido pelo gate desta entrega.
+- **Auto-enrollment público, UI de enrollment, fila de revisão de matches** — adiados com gatilho.
+- **Vídeo/tempo real/câmeras no evento, app nativo, API pública de reconhecimento** — fora.
+- **Vínculo com `Contact`/leadership, “quem é quem”, CRM de rostos** — invariante.
+- **Reusar o índice semântico do C229** (falas) para rostos — dono e modalidade diferentes.
+- **Segundo engine, terceiro de reconhecimento, modelo via CDN, `tfjs-node`, inferência server-side da selfie** — rejeitados em D1/D5.
+- **Segundo limiter, segundo body cap, segundo mecanismo de Consent** — D8/D6 (editar o dono).
+- **Global novo de configuração da busca** — a flag vive no `photoAlbum` (dono das chaves do álbum); sem terceiro global.
+- **Paginação/ordenação configurável do resultado, contadores de busca, debug de vetores** — fora do MVP.
+
+## Riscos e mitigação
+
+- **Falso positivo de match (sem score exposto):** threshold estrito 0.45 + melhor match único + só fotos aprovadas + canal de remoção + opt-out; a assimetria é honesta (o pior caso mostra fotos públicas sem qualquer identificação/nome). Calibrar o threshold no canário de enrollment antes de ligar a flag em produção; gatilho de revisita se a taxa de falso vínculo aparecer em atendimento.
+- **Engine node-wasm do CLI não validado:** a rota `face-api.node-wasm.js` + `@tensorflow/tfjs-backend-wasm` em Node é a aposta do lote; o spike da Fase 2 roda o descriptor sobre a fixture antes de qualquer CLI (fallback documentado: lote via browser headless, rejeitado para o MVP). Se o spike falhar, o plano para em `blocked` — nunca indexa com engine divergente do browser.
+- **Troca de modelo invalida o índice:** `model` no subject + `checkedKey` no marker + `--verify` apontando stale; re-enrollment exige as selfies mantidas pela assessoria (fora do site) — registrar no runbook.
+- **Engine falha em browser sem WebGL/WASM:** probes antes do download + estado honesto “seu navegador não permite…” (sem culpabilizar a selfie); sem fallback silencioso (precedente `cardCutout.ts:13-20,97-127`).
+- **~9MB de bundle/modelos:** import dinâmico só quando o fluxo começa + modelos same-origin baixados com progresso; nunca no bundle inicial da `/fotos`.
+- **Lote longo (6,5k fotos, workstation):** `--limit` canário, falha isolada por foto com estágio nomeado, retomada pela marker, recibo com tempo real; rodar fora da janela de deploy.
+- **Índice vazio no MVP A/C:** o vazio honesto (cena 6 com recorte A/C) evita promessa falsa; o runbook exige enroll+index antes de ligar a flag; `hasPublishedArchivePhotos` continua dono do link “Fotos”.
+- **Oráculo de pertencimento (abuso):** rate limit estrito por IP hasheado + threshold + resposta sem ids/scores; nada lista subjects publicamente.
+- **PR high-risk (migration + lockfile):** curado de e2e com `frontendFotosSelfie`/`frontendFotos` + pin atualizado no mesmo PR; e2e local `--no-deps` antes do push.
+- **Corrida de e2e com o kill switch do álbum:** projeto com `dependencies: ['frontendFotos']` nos dois modos + linhas próprias + restauração de global/Consent no `afterAll`.
+- **Hook de revalidação do `archivePhoto` disparando no CLI:** `revalidateTagSafely` engole fora de request (`documents.ts:27-34`) e o `context: { faceIndex: true }` mantém a curadoria intocada (`ArchivePhoto.ts:168`).
+- **Consent apagado por baixo dos subjects:** guard de `beforeDelete` estendido (`Consent.ts:24-90`); texto editado deixa subjects inelegíveis até re-consentimento (fail-closed).
+- **Boot com S3 parcial:** mesmo dono de mídia privada; o CLI de index exige mídia espelhada no alvo (`mirroredMediaRequired`).
+
+## Hard-stops (aprovação humana explícita — valem mesmo em `--auto`)
+
+1. **Schema/migration:** `faceSubject` (+ join `matchedPhotos`), grupo hidden `faces` em `archivePhoto` e flag `selfieSearchEnabled` no global `photoAlbum` na migration aditiva `add_face_subject` — `pnpm migrate:create` → SQL revisado à mão (nenhum objeto existente recriado) → `pnpm migrate` **só** no banco do worktree (`teqo_wt*`) → `pnpm generate:types`; migrations entregues intocadas. **✅ Aprovado na sessão `--auto` (2026-09-27).**
+2. **Contrato público/URL e shapes:** `/fotos/encontre` (noindex; 404 sem flag/publicado, 200 com o estado fechado sem Consent), `POST /api/fotos/selfie` com body `{ vector: number[128], intent: 'search'|'leave-index' }` (JSON ≤4KB; sem imagem) e envelopes `{ ok, found, photos }`/`{ ok, removed }` sem score/nomes, com 404 (fechado) e 503 (Consent ausente) como recusas; entrada na `/fotos` gated pela flag. **✅ Aprovado na sessão `--auto` (2026-09-27).**
+3. **Consent/DPIA/chaves e política de nascer fechado:** keys `busca-selfie-fotos` e `busca-selfie-indice`, **texto não seedado**, flag default `false`, índice A/C (só consentidos; rostos anônimos proibidos) e **aval jurídico + DPIA antes de ligar em produção** (timing eleitoral, eleição 04/10/2026); o runbook de abertura documenta o ato. **✅ Aprovado na sessão `--auto` (2026-09-27).**
+4. **Dependência nova de engine + assets (disclose):** `@vladmandic/face-api@1.7.15` (MIT) + `@tensorflow/tfjs` + `@tensorflow/tfjs-backend-wasm` no `package.json`/lockfile (lockfile = high-risk), com ~9MB de modelos gitignored em `public/fotos/modelos/` servidos same-origin (sem CDN, sem terceiro em runtime); `copy-face-vision-assets.mjs` no `prebuild`/`predev`/Dockerfile. **✅ Aprovado na sessão `--auto` (2026-09-27).**
+5. **Enrollment ops-only vs UI:** enrollment e indexação só por CLI (`faces:enroll`/`faces:index`); auto-enrollment público e UI de admin **fora do MVP** — auto-enrollment rejeitado por ausência de verificação de identidade (consentimento de terceiro + DoS de opt-out); UI adiada com gatilho. **✅ Aprovado na sessão `--auto` (2026-09-27).**
+
+## Adiado com gatilho
+
+- **Refatoração do `SelfieSearchFlow`** — 886 linhas/15 estados com render inline; `LeaveConfirmDialog` reimplementa o chassi do kit (`ui/dialog`, Radix); intents/envelopes redigitados em vez de derivar de `lib/faceSearch`/rota. **Gatilho:** próximo toque de comportamento no fluxo (novo estado/envelope), 2º diálogo nas fotos ou requisito de a11y em modal.
+- **2ª cópia do copiador de assets de visão** — `copy-face-vision-assets.mjs` quase verbatim de `copy-card-vision-assets.mjs`. **Gatilho:** 3ª cópia `copy-*-vision-assets` — extrair o loop `stat`+size+write para `scripts/lib/`.
+- **Recibos/`reportStamp` por CLI de lote** — 3ª cópia (`archiveCatalogPlan`/C232 + `faceEnrollPlan` + `faceIndexPlan`) com `writeReport` quase verbatim nos scripts. **Gatilho:** próximo CLI de lote/recibo — extrair `reportStamp`+`writeReport` para `scripts/lib/cli.mjs` junto do `writeRepoFile`.
+- **Reader nomeado do global `photoAlbum`** — residual (comentário de leitura pública já no ponto da rota). **Gatilho:** 3º consumidor direto do global (hoje rota `/api/fotos/selfie` + 2 RSCs via `getCachedGlobal`).
+- **Workspace único por run no `faces:index`** — hoje `mkdtemp`/`rm` por foto; mesmo gatilho do bullet de índice incremental (tempo do lote acima do canário ou aprovadas > ~1k).
+- **UI de enrollment no admin** — gatilho: assessoria precisar operar sem CLI (volume de adesões ou equipe sem terminal).
+- **Captcha/anti-bot** — gatilho: padrão de abuso/comportamento de oráculo observado (contadores, não conteúdo).
+- **Modo B (índice de rostos do acervo com DPIA)** — gatilho: DPIA + aval jurídico documentados.
+- **Re-embedding automático em troca de modelo** — gatilho: troca do `FACE_SEARCH_MODEL` (vira o item de runbook de migração de índice).
+- **Índice incremental otimizado / sharding por município** — gatilho: tempo do lote materialmente acima do canário ou aprovadas > ~1k.
+- **Revisão/auditoria de matches pela assessoria (admin)** — gatilho: primeira queixa de falso vínculo ou pedido do jurídico.
+- **Extração do registry de consent keys para módulo neutro** — gatilho: 3º fluxo público de consentimento ou o nome passar a mentir.
+- **Extração do rate limit/body cap para módulo neutro** — gatilho: 3ª superfície anônima consumindo o mesmo dono.
+- **Upload consentido da selfie com retenção zero** — gatilho: cobertura on-device provar-se insuficiente para o público-alvo **e** jurídico aprovar o caminho (hoje: rejeitado).
+- **Paginação/filtros do resultado** — gatilho: pessoas com mais de 60 fotos aprovadas no índice.
+
+## Fechamento (sessão `--auto`, 2026-09-27)
+
+- **Hard-stops:** os cinco aprovados explicitamente pelo humano na sessão (schema/migration, contrato público/URL e shapes, Consent/DPIA/chaves com texto não seedado, dependência nova de engine + assets, enrollment ops-only).
+- **Desvios registrados na execução:** (1) `faces:index` ficou **sem `--subject`** (canário = `--limit`; qualquer enrollment invalida a revisão de todas as fotos, então o filtro por sujeito economizava pouco e adicionava estado) e o `--verify` ganhou o breakdown de vínculos por sujeito; (2) a recusa do endpoint usa **404** para flag/publicação fechadas e **503** para Consent ausente; (3) o rate limit ganhou **namespace de chave** (`selfie:`) sobre o dono compartilhado — o beacon e a busca não consomem mais o mesmo contador; (4) o miolo do card virou `ArchivePhotoCardBody` (dono C233) e a variante C234 só troca o href e omite a linha de terceiros; (5) `enrolled_at` (não `updated_at`) é a revisão do sujeito na `checkedKey` — o lote escreve `matchedPhotos` e avançaria o `updated_at` para sempre.
+- **Design (trigger c): PARIDADE CERTIFICADA** pelo `designer` (tier primário `openai/gpt-5.6-sol`, sem `DEGRADED`) em 3 rodadas contra o app renderizado (390/1280): faixa de entrada, consentimento, escolha, processamento, resultado (grade larga, superfície branca, título 34px), presença, diálogo, vazio e erro de engine. Ajustes da crítica aplicados: `border-0 pb-0` nos títulos (o `h2` global injeta borda), resultado full-bleed branco com a faixa creme de presença contrastando, scroll/foco no início de cada passo, submit do álbum secundário com a faixa ativa, 44px no "Voltar ao álbum" e no "Voltar sem alterar", copies mobile por breakpoint, ícones semânticos (imagem/aparelho/lixeira), `aria-label` distinto no backdrop do diálogo.
+- **`/simplify` + débitos:** dois revisores (estrutural + qualidade) rodados no diff; fixes aplicados (namespace do rate limit, card body compartilhado, foco/scroll por passo, projeção enxuta do subject, dedup de fixtures int, uso do dono `waitForStreamSettled` no e2e novo, remoção de código morto). Triagem `capture-review-debts`: zero `expensive_lock` ≥4 → nenhuma Issue nova; baratos deferidos na seção "Adiado com gatilho" abaixo.
+
+## Aceite de engenharia
+
+- [ ] Aceite de produto coberto: com Consent + flag + adesão, o visitante recebe só as fotos aprovadas em que aparece (sem terceiros/score); sem Consent (ou sem flag) recusa honesta na UI e no servidor; selfie nunca sobe; remoção/opt-out efetivos e registrados; antiabuso ativo; A/C estrito (rostos anônimos fora).
+- [ ] Invariantes AGENTS/engineering-standards: sem cadastro paralelo/PII desnecessária; Consent por key estável fail-closed; escrita multi-collection em transação com `req`; bypasses documentados (`overrideAccess` no CLI/na rota); `import 'server-only'`; identificadores em inglês e copy pt-BR; editar o dono (rate limit, body cap, guard do Consent), sem twins.
+- [ ] Migration aditiva gerada, revista à mão, aplicada local e registrada; `pnpm generate:types`; nenhuma migration retroeditada.
+- [ ] Testes de domínio previstos (unit/int) onde o contrato/shape/transações mudam; e2e novo no disco + projeto Playwright + `E2E_AFFECTED_MANIFEST` + `E2E_CURATED_SPECS` + pin + `SCRIPTS_SPEC_PINNED`.
+- [ ] UI portada classe-a-classe do artefato estendido pelo `designer` (triggers a/b), crítica final (c) com screenshots 390/1280 e `Design tier:` no PR.
+- [ ] `pnpm gate:fast` verde; int do caminho verde; e2e `frontendFotosSelfie`/`frontendFotos` verde local; runbook + `AGENTS-public.md` + `.env.example` + `.gitignore` + changelog no fechamento; hard-stops com aprovação explícita antes do push.
+
+## Self-score (decision-quality)
+
+**Self-score decision-quality: 4/5.** (1) todas as decisões caras (engine, schema/marker/kill switch, enrollment ops, lote, contrato de consulta, consent, remoção, antiabuso, UI, verificação) têm Opções + Recomendação + Rejeitadas; (2) cabe no appetite: uma collection nova, uma migration aditiva, uma rota pública nova, reuso dos donos existentes (approval gate/media privada/transação/consent/guardas de CLI) e o lote é máquina, não código; (3) rabbit holes nomeados (parecidos/score, retenção de selfie, modo B, auto-enrollment, segundo limiter/engine, C229, CRM de rostos); (4) depth check: edita donos reais (`contentEventRateLimit`, body cap do beacon, guard do `Consent`, global `photoAlbum`, hook de revalidação do `ArchivePhoto`) e só cria módulos onde não há dono (`faceSubjects/`, `lib/faceSearch`, rotas); (5) outcome preservado — fail-closed, A/C, sem PII desnecessária, sem score, sem reescrever o aceite. O que impede o 5: o **threshold 0.45** e a qualidade do engine são aposta não medida (calibração só no canário), e a **operação de re-enrollment na troca de modelo** depende das selfies mantidas pela assessoria fora do site — ambos registrados como risco com gatilho, não escondidos.
diff --git a/docs/plans/busca-fotos-por-selfie-ui-design.html b/docs/plans/busca-fotos-por-selfie-ui-design.html
index 66431751..d2d9db47 100644
--- a/docs/plans/busca-fotos-por-selfie-ui-design.html
+++ b/docs/plans/busca-fotos-por-selfie-ui-design.html
@@ -60,7 +60,7 @@
color: white;
font-size: 14px;
font-weight: 800;
- box-shadow: 0 8px 18px rgb(228 16 47 / 18%);
+ box-shadow: 0 4px 0 #7f1616;
}
.secondary {
display: inline-flex;
@@ -76,6 +76,22 @@
font-size: 14px;
font-weight: 800;
}
+ .primary:focus-visible,
+ .secondary:focus-visible {
+ outline: 3px solid var(--brand-yellow);
+ outline-offset: 3px;
+ }
+ .revision-badge {
+ display: inline-flex;
+ align-items: center;
+ min-height: 24px;
+ border-radius: 999px;
+ background: #eef4fb;
+ padding: 4px 9px;
+ color: #184e92;
+ font-size: 11px;
+ font-weight: 800;
+ }
.icon {
width: 18px;
height: 18px;
@@ -189,6 +205,20 @@
+
+
+
+
+
+
+
+
+
+
@@ -198,6 +228,9 @@
>C234 · gate hi-fiFluxo sensível · LGPD fail-closedEstendido para a execução · 27 set 2026
@@ -207,14 +240,35 @@
A pessoa parte do álbum, entende o uso de biometria, escolhe a própria selfie e recebe
apenas fotos públicas aprovadas em que aparece. A selfie permanece no dispositivo.
+
+
Deltas de execução desta revisão
+
+ A entrada agora respeita o hero e a grade reais do C233; o fluxo é uma página própria em
+ /fotos/encontre; resultado, vazio A/C, falhas do
+ processamento e opt-out sem reconhecimento ganham estados explícitos. Cenas revisadas
+ mantêm o número aprovado e recebem selo azul; cenas novas começam em 09.
+
+
+
-
Cena 01
+
+
Cena 01
+ Revisada · C234 execução
+
- Desktop ~1280 · entrada no contexto da /fotos
+ Entrada real na /fotos · flag ligada
+
+ O hero do C233 permanece inteiro. A entrada ocupa uma faixa própria entre o hero e as
+ facetas: descoberta forte, sem competir com o título do álbum nem transformar o hero em
+ duas colunas.
+
- Procure uma foto por data, lugar ou atividade.
+
+ Fotos que contam onde a gente esteve.
-
- Use os filtros para navegar pelos registros aprovados da caminhada.
+
+ Procure uma agenda, um município ou uma pessoa pública e abra cada registro com
+ data e contexto.
+
+
+
+
+
+
+
+
Mobile 390 · flag ligada
+
+
+ Jorge SollaFotos
+
+
+
+ Nossa caminhada
+
+
+ Encontre um momento.
+
+
+ Busque pela agenda, pelo lugar ou por quem apareceu.
+
+
+
+
+
+ Buscar no álbum…
+
+
+
+
+
+
+
+
+
+
Flag desligada · álbum normal, sem reserva vazia
+
+
+ Regra visual: a faixa inteira não existe. Hero e
+ facetas se encostam pelo espaçamento normal do C233; não aparece placeholder, aviso
+ de indisponibilidade nem link no header/rodapé.
+
+
+ Buscar no álbum…
+
+
+ DataMunicípioAtividade
+
@@ -286,72 +445,101 @@
-
Cena 02
+
+
Cena 02
+ Revisada · página /fotos/encontre
+
- Desktop ~1280 · aviso de biometria e consentimento explícito
+ Desktop ~1280 · aviso e consentimento na página própria, não em modal
- Com sua autorização, comparamos a sua selfie com o índice permitido do álbum para
- mostrar somente fotos públicas aprovadas em que você aparece.
-
-
-
-
-
- A selfie não sai do dispositivo. A imagem é processada aqui e não é guardada na galeria do site.
+
+
+ Encontre você nas fotos
+
+
+
+
-
-
-
-
-
- Você pode desistir e pedir remoção. O resultado não mostra nomes de terceiros nem percentual de semelhança.
+
+ Passo 1 de 3
+
+
+ Antes da sua selfie
+
+ Sua face é um dado biométrico sensível
+
+
+ Com sua autorização, comparamos a sua selfie com o índice formado por pessoas que
+ autorizaram participar, para mostrar somente fotos públicas aprovadas em que você
+ aparece.
+
+
+
+
+
+ A selfie não sai do dispositivo. A imagem é processada aqui e não é guardada na galeria do site.
+
+
+
+
+
+ Você pode desistir e pedir remoção. O resultado não mostra nomes de terceiros nem percentual de
+ semelhança.
+
- Mobile ~390 · processando sem promessas ou percentuais
+ Mobile 390 · processando em /fotos/encontre, sem promessas ou percentuais
-
Procurando você nas fotos aprovadas…
+
+ Passo 3 de 3
+
+
Procurando você nas fotos aprovadas…
Mantenha esta tela aberta. Sua selfie continua somente neste aparelho.
@@ -450,8 +644,13 @@
+
-
Cena 05
+
+
Cena 05
+ Revisada · variante sem terceiros
+
Desktop ~1280 · resultado mínimo: só fotos aprovadas em que você aparece
@@ -487,33 +686,50 @@
-
+
+
+ Plenária pela saúde pública
+
+
+ 12 set 2026 · Município selecionado · Plenária
+
+
+
-
+
+
Caminhada no centro
+
+ 28 ago 2026 · Município selecionado · Caminhada
+
+
+
-
+
+
+ Encontro com a comunidade
+
+
+ 16 ago 2026 · Município selecionado · Reunião
+
+
+
+
+ Contrato visual da variante: imagem 4:3, título e contexto do card C233. A
+ linha “Quem aparece” não é reservada, ocultada nem substituída; ela não existe no
+ resultado por selfie.
+
Quer controlar sua presença no álbum?
@@ -530,11 +746,61 @@
+
+
+ Mobile 390 · a mesma variante em grade de duas colunas
+
+
+
+ Resultado
+
+
+
Encontramos você nestas fotos.
+
+ Só registros públicos aprovados, sem nomes de terceiros.
+
- Talvez a luz ou o ângulo tenham dificultado a busca — ou ainda não haja uma foto
- pública aprovada sua no álbum.
+ Esta busca funciona para quem autorizou participar do índice. Também pode não haver
+ uma foto sua entre os registros públicos aprovados.
- Não mostramos pessoas parecidas para preencher a tela.
+ Não mostramos pessoas parecidas nem tentamos adivinhar quem você é.
- Vamos registrar seu pedido. Até o atendimento ser concluído, sua presença permanece
- protegida e não deve voltar ao índice.
+ Vamos reconhecer você neste aparelho e, se houver correspondência, retirar sua
+ presença do índice de busca.
+
+
+
+
+
Cena 09
+ Nova · engine indisponível
+
+
+ Navegador sem os recursos necessários · mobile 390
+
+
+
+
+ Encontre você
+
+
+
+
+
+
+ Este navegador não consegue fazer a busca por selfie
+
+
+ Para proteger sua imagem, o reconhecimento precisa rodar neste aparelho. Este
+ navegador não oferece os recursos necessários.
+
+
+ O problema não é com a sua selfie. Nenhuma imagem foi enviada.
+