# Audit pré-build 3.3 — équipe senior (20 agents, adversarial, sur le VRAI code) — 16/06

Verdict global : **build-readiness = aucun blocker** (les 2 pièges historiques vérifiés neutralisés). Mais l'audit a trouvé **3 blockers commerciaux** (dont 1 majeur sur le cœur du produit) + des risques. Tout ci-dessous est **confirmé contre le code/DB/DNS réels**, pas sur parole, avec re-vérification adversariale.

## 🔴 BLOCKERS

1. **[CORRIGÉ 16/06] Dictée gratuite à vie — la garde licence ne couvrait QUE l'UI.**
   `_is_activated()` gardait les 4 entrées UI (start_dictation, transcribe_file, import_meeting_audio, reunion_start) MAIS PAS le **raccourci** (`begin()`, app.py) ni le **menu-bar** (`_on_dictate_toggle()`). Or l'usage normal = « maintiens Ctrl+Cmd » sans ouvrir l'UI → le cœur du produit était utilisable sans licence. **Fix appliqué** : garde `_is_activated()` ajoutée sur les 2 chemins (overlay « Activez Vlocal » au raccourci, toast au menu-bar), + 2 entrées `_BT` (i18n EN). py_compile OK. **Part avec le build.**

2. **[TON ACTION, post-build] Le download sert encore la 1.0.3.**
   `app_config.download_url = .../Vlocal-1.0.3.dmg` (vérifié en DB). track-download (302) ET public-config servent cette valeur ; merci.html annonce « ≈ 2,9 Go » mais livre la 1.0.3. Fix : après build+notarise 3.3, upload R2 → `UPDATE app_config SET value='.../Vlocal-3.3.0.dmg'` + insérer app_versions 3.3.0 publié. (Je peux faire les UPDATE Supabase une fois le DMG sur R2.)

3. **[TON ACTION — Stripe] portal_url = ID du checkout, pas un portail.**
   `app_config.portal_url = billing.stripe.com/p/login/8x2aEYgFc2Ib2Gb29MdQQ00` — cet ID est **identique** au Payment Link mensuel (index.html:254). Donc « Gérer mon abonnement » (factures/résiliation) est cassé → risque litige/chargeback. Fix : activer le portail client dans Stripe (Settings > Billing > Customer portal), copier le VRAI login link, `UPDATE app_config portal_url` + le fallback compte.html:53. (Alternative robuste : une edge function qui crée une billing_portal.session par client.)

## 🟡 RISQUES confirmés (hardening avant le trafic payant)

- **Idempotence checkout** : pas de UNIQUE sur `licenses.stripe_session_id` + read-then-insert non atomique → 2 retries Stripe concurrents = 2 licences/2 emails. Latent (1 licence, 0 dup aujourd'hui). Fix : UNIQUE index partiel + upsert onConflict.
- **Anti-rejeu manquant** (stripe-webhook ET inbound-email) : le timestamp Svix/Stripe n'est pas comparé à l'heure → rejeu théorique d'une requête signée capturée. Fix : rejeter si |now - t| > 300 s.
- **Multi-device illimité** : `validate-license` enregistre le device_id mais ne limite jamais → 1 clé = N Macs (partage de clé). Décision produit (assumer ou limiter à 2-3).
- **Pas de filet par date** : `current_period_end` stocké mais jamais utilisé côté app ; si un webhook Stripe est manqué, la licence reste « valid » indéfiniment. Fix : faire dépendre la validité de la date (serveur : active = status==active && now<period_end+grâce).
- **DMARC absent** sur vlocal.org (+ pas de SPF racine) → délivrabilité dégradée des mails support/feedback (Gmail/Yahoo 2024). Fix : publier `_dmarc.vlocal.org TXT "v=DMARC1; p=none; rua=mailto:..."` (DNS Vercel). DKIM présent, donc auth OK, seul DMARC manque.

## ✅ VÉRIFIÉ SOLIDE (rien à faire)

- **Build-readiness, AUCUN blocker** : entitlement `audio-input` présent + garde-fou notarize.sh (pas de dictée muette) ; wheel **mlx-metal macOS 14** (core.so minos=14.0) + garde-fou build_app.sh bloquant même sous SKIP_GATES (GPU OK chez les clients) ; **version 3.3.0 cohérente partout** (source unique VERSION → spec → Info.plist) ; py_compile OK 22 modules ; spec thin correcte (diarisation embarquée, blindage hidden imports) ; notarize.sh correct ; manifest R2 joignable + checksums alignés.
- **« Blocker Sparkle/RCE » = FAUX POSITIF** : l'app n'embarque PAS Sparkle. La MAJ = `check_updates` (GET) + `open_url` https dans le navigateur, install manuelle d'un DMG notarisé (Gatekeeper). Donc **pas de vecteur RCE in-app** ; l'appcast non signé est du code mort. → blocker #3 de l'ancienne checklist **requalifié**.
- **i18n** : 239 clés FR = 239 EN, 108 data-i18n couverts, JS valide ; flux activation + écran modèles gèrent tous les cas (réseau, clé invalide, email mismatch, disque plein, reprise) ; overlay + menu-bar bilingues.
- **Landing** : Geist 100% auto-hébergée (0 Google), 0 em-dash, pages bien formées, liens corrects.
- **Sécurité edge** : anti-oracle validate-license OK ; get-license non énumérable ; pas de fuite de secret ; pas de boucle de mail (forward → Gmail, jamais @vlocal.org) ; signature Svix/Stripe cryptographiquement correcte.
- **Reste** : 1 seul artefact = 3.2.9 bundle dans dist/ → la **3.3.0 n'a jamais été buildée** (d'où ce build) ; tester le 1er-lancement thin sur machine vierge après build.

## Ordre d'exécution
1. ✅ Blocker #1 (licence raccourci/menu-bar) — fait.
2. Hardening serveur (idempotence + anti-rejeu + filet date) — edge functions.
3. Build + notarise 3.3 (feu vert donné) → upload R2 → publier app_versions → aligner download_url (blocker #2).
4. Portal Stripe (blocker #3) + DMARC — tes comptes.
5. Test machine vierge + 1 achat carte réelle.
