mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:45:18 +00:00
- /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.
56 lines
2.2 KiB
JavaScript
56 lines
2.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* C234 — copies the `@vladmandic/face-api` model weights into
|
|
* `public/fotos/modelos/` so the selfie search runs 100% same-origin (no CDN
|
|
* executing in the visitor's browser; the e2e suite never touches the network).
|
|
*
|
|
* Same contract as `copy-card-vision-assets.mjs` (S15): the models are a
|
|
* gitignored build artifact — this script runs in `pnpm dev` (preflight), in
|
|
* `pnpm build` (npm `prebuild`) and in the Docker builder before `next build`.
|
|
* Idempotent and cheap: a file already present with the same byte size is
|
|
* skipped. The list must match the nets `faceSearchEngine.ts` (browser) and
|
|
* `scripts/lib/faceEngine.mjs` (CLI) load.
|
|
*/
|
|
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
|
|
export const FACE_MODEL_FILES = [
|
|
'tiny_face_detector_model-weights_manifest.json',
|
|
'tiny_face_detector_model.bin',
|
|
'face_landmark_68_model-weights_manifest.json',
|
|
'face_landmark_68_model.bin',
|
|
'face_recognition_model-weights_manifest.json',
|
|
'face_recognition_model.bin',
|
|
]
|
|
|
|
export const copyFaceVisionAssets = async (repoRoot) => {
|
|
const sourceDir = path.join(repoRoot, 'node_modules/@vladmandic/face-api/model')
|
|
const targetDir = path.join(repoRoot, 'public/fotos/modelos')
|
|
await mkdir(targetDir, { recursive: true })
|
|
|
|
for (const name of FACE_MODEL_FILES) {
|
|
const source = path.join(sourceDir, name)
|
|
const target = path.join(targetDir, name)
|
|
|
|
try {
|
|
const [sourceStat, targetStat] = await Promise.all([
|
|
stat(source),
|
|
stat(target).catch(() => null),
|
|
])
|
|
if (targetStat && targetStat.size === sourceStat.size) continue
|
|
await writeFile(target, await readFile(source))
|
|
console.log(`[copy-face-vision-assets] ${name}`)
|
|
} catch (error) {
|
|
throw new Error(
|
|
`[copy-face-vision-assets] failed to copy ${name}: ${error instanceof Error ? error.message : error}`,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href
|
|
if (isDirectRun) {
|
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
await copyFaceVisionAssets(repoRoot)
|
|
}
|