Living operational document tracking the path from v1.0.8 to public
launch as a SoundCloud-alternative. Compresses the original 24-week
plan to 6 weeks by explicit scope-control:
- §2 Scope contract: IN/OUT/COMPRESSED matrix (what ships, what
defers post-launch v1.1+, what's MVP-but-shippable)
- §1 External actions EX-1 to EX-12 (legal, pentest, DMCA agent,
DNS, TLS, CDN, OAuth secrets, Stripe live, transactional email,
status page, coturn) with cycle estimates
- §4 Day-by-day sprint breakdown for 6 weeks (W1 v1.0.9 + Ansible,
W2 Postgres HA + obs, W3 storage HA + signature features,
W4 PWA + HLS + faceted search + load test, W5 pentest + game day
+ canary + status page, W6 GO/NO-GO + soft launch + go-live)
- §6 Risk register (R-1 to R-10) with mitigations
- §7 Defended scope (refused additions during the 6 weeks)
- §8 37 absolute Production-Ready criteria
Daily updates expected: tick acceptance criteria as they land, commit
each update with `docs: roadmap launch — <jour X> done`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
44 KiB
ROADMAP — Veza v1.0 Public Launch (4-6 semaines)
Cible : ouverture publique grand-public en 4-6 semaines, positionnée comme alternative sérieuse à SoundCloud, sur infra self-hosted Incus (R720 + débordement Hetzner si nécessaire).
Auteur : architecte principal Veza + Claude Code Opus 4.7 Dernière mise à jour : 2026-04-26 État de départ : v1.0.8 + sprint 1 v1.0.9 partiellement livré (Item G subscription en cours, items 1.2/1.3/1.4/1.5/1.6 commit-prêts)
0. Cadrage honnête
Compression : le plan original (/home/senke/.claude/plans/quelles-sont-les-prochaines-playful-beaver.md)
visait 24 semaines pour atteindre une vraie maturité opérationnelle. On
compresse 4× ici. Ça implique de trancher sur ce qui rentre et ce
qui n'y rentre pas — la liste explicite est en §2. Si tu doutes pendant
les 6 semaines, reviens à §2 : tout ce qui n'y est pas listé comme IN
attend post-launch.
Réalité :
- Solo dev, ~50h/semaine, pas de marges pour rattraper du retard
- 30 jours ouvrés disponibles, dont ~20% absorbés par l'imprévu (CI rouge, dette qui remonte, decisions externes)
- Effort réel = 24 jours productifs sur 30
- Budget temps perdu sur la légal/pentest/ops externes : 0 (ces tâches doivent tourner en parallèle, JAMAIS sur le critical path code)
Verdict honnête :
- 4 semaines = irréaliste sauf à accepter risques élevés (pas de pentest externe, ToS template non-revu, infra single-instance)
- 6 semaines = atteignable si discipline scope-control + démarrage immédiat des tâches externes
- 8 semaines = confortable
Je rédige pour 6 semaines avec marge à droite.
1. À démarrer AUJOURD'HUI en parallèle (externe au code)
Ces actions ont des cycles longs incompressibles. Elles doivent partir maintenant sinon le lancement glisse de 2-4 semaines.
| # | Action | Cycle | Owner | Statut |
|---|---|---|---|---|
| EX-1 | Contacter avocat pour ToS / Privacy / DMCA / DSA | 2-4 sem | toi | ⏳ |
| EX-2 | Demander devis pentest externe (ouvert sur surface v1.0.9) | 1 sem devis + 2 sem exec | toi | ⏳ |
| EX-3 | Enregistrer agent DMCA US Copyright Office (~120 USD, 2-3 sem traitement) | 2-3 sem | toi | ⏳ |
| EX-4 | Désigner point de contact DSA (UE) — soit toi soit représentant local | 1 sem | toi | ⏳ |
| EX-5 | DNS production : veza.fr, api.veza.fr, cdn.veza.fr, turn.veza.fr, embed.veza.fr |
1 j | toi | ⏳ |
| EX-6 | TLS certs (Let's Encrypt) pour tous les sous-domaines | 1 j | toi | ⏳ |
| EX-7 | Compte CDN edge (Cloudflare R2 + Workers ou Bunny.net) | 1 j | toi | ⏳ |
| EX-8 | Rotation OAuth secrets prod (Google, GitHub, Discord, Spotify) | 0.5 j | toi | ⏳ |
| EX-9 | Stripe / Hyperswitch live mode setup + KYC marketplace | 2-3 sem | toi | ⏳ |
| EX-10 | Email transactionnel : domaine custom + SPF/DKIM/DMARC (Mailjet/Postmark/Brevo) | 1 sem | toi | ⏳ |
| EX-11 | Status page (statuspage.io free OU Cachet self-hosted) | 1 j | toi | ⏳ |
| EX-12 | Coturn déploiement Incus (cf. infra/coturn/README.md) |
1 j | toi | ⏳ |
Critical path externe : EX-1 (légal) et EX-2 (pentest) sont les deux contraintes majeures. Si tu démarres EX-1 le lundi semaine 1, tu auras les drafts ToS/Privacy semaine 3 et la version finale signée semaine 5. EX-2 même calendrier : devis semaine 1, exécution semaines 4-5, rapport semaine 6. Sans ces deux-là on ne tag pas v2.0.0-public.
2. Scope contract — ce qui rentre vs sort
IN (livré avant le tag v2.0.0-public)
Code applicatif
- Sprint 1 v1.0.9 (subscription Item G + items 1.2/1.3/1.4/1.5/1.6 + 1.7 flake stab)
- DMCA notice workflow (formulaire + admin queue + take-down handler)
- Embed widget
/embed/track/:id+ oEmbed - Track sharing tokens privés (parité avec playlist share)
- Service worker offline track cache (Workbox CacheFirst)
- HLS streaming activé par défaut + ABR validé
- Faceted search UI sur
/searchet/discover - Open Graph tags dynamiques sur les pages tracks/playlists
- Status page côté ops + endpoint
/api/v1/statuspublic
Infrastructure
- IaC Ansible playbooks (Postgres, Redis, MinIO, RabbitMQ, coturn, HAProxy, backend-api, stream-server)
- Postgres streaming replica + pg_auto_failover (RTO < 60s)
- pgBackRest WAL archive + restore drill cron hebdo
- PgBouncer transaction-mode pool
- Redis Sentinel 3-node
- MinIO erasure-coded 4-node EC4+2
- HAProxy frontal + 2 backend-api containers actif/actif (sticky WS)
- CDN edge externe (Cloudflare R2 + Workers OU Bunny.net) câblé
- OpenTelemetry collector → Tempo (traces visibles)
- Loki HA 3 réplicas + S3 backend (sur ton MinIO)
- SLO burn-rate alerts (remplacement seuils statiques)
- Backup restore drill automatisé hebdo
- Synthetic monitoring (blackbox exporter rejouant les 6 parcours)
- Game day #1 réalisé + runbooks testés
- Canary release script
Légal
- Terms of Service (avocat-revu)
- Privacy Policy RGPD + CCPA + DPA template processeurs
- DMCA notice + agent enregistré
- DSA point de contact + transparency report template
- CGV vendeur marketplace
- Mentions légales + politique cookies + bannière consentement
Sécurité
- Pentest externe rapport reçu, 0 critique/haut ouvert
- Secrets hors git (Vault sur conteneur Incus OU sops/age versionné)
- Rotation OAuth secrets effectuée
- JWT RS256 prod (déjà en place — vérifier rotation key)
Business
- Stripe live mode actif + KYC créateurs testé E2E avec vrais fonds
- Hyperswitch live mode actif + flux paiement E2E réel
- Email transactionnel sur domaine custom (SPF/DKIM/DMARC OK)
- 5-10 testeurs beta privée semaines 5-6 avec retours documentés
OUT (deferred post-launch v1.1+)
- Native mobile (iOS/Android) — Capacitor wrap après launch (~3 semaines)
- Audio fingerprinting (Chromaprint) — DMCA workflow MANUEL au launch ; fingerprinting v1.1
- Public API SDK (
@veza/sdk-js,@veza/sdk-py) + dev portal v2 — v1.1 - OAuth2 client credentials + rate limit tiers per-app — v1.1
- Multi-region failover — single region (R720 + Hetzner debordement) au launch
- Tipping / fan support — décision business post-launch
- Distribution intégrale DSP (TuneCore, CD Baby, etc.) — v1.1
- Subscription per-creator (Patreon-style) — v1.1+
- CarPlay / Android Auto — nécessite app native d'abord
- Lossless tier FLAC subscriber — codec déjà supporté, exposer post-launch
- "Fresh from your follows" tab discovery — déferred (chronologique du feed suffit au launch)
- Editorial collections admin UI — défferred (curation manuelle DB au launch)
- Faceted search saved searches — UI faceted suffit, "saved" v1.1
COMPRESSED (livré mais minimum viable, à durcir post-launch)
- MinIO erasure-coded : 4 nœuds EC4+2 mais sur la même machine R720 (pas vraiment HA matériel — ajoute Hetzner storage post-launch)
- HAProxy sticky WS : 2 backend-api sur la même machine R720 (pas N+1 hardware HA)
- Loki HA : 3 réplicas en conteneurs Incus mais sur le même hôte (single-host failure mode, accepté pour launch)
- DMCA workflow : workflow complet + agent enregistré, MAIS audit fingerprinting reporté → take-down purement humain
- Pentest : externe mais light scope (auth + paiement + DMCA + uploads), pas full deep dive
- Game day : 1 game day cycle complet, pas 3 cycles répétés
3. Critical path & dépendances
EX-1 légal ──────────────────────────────▶ EX week 5 signature ──▶
EX-2 pentest ────────────────────▶ EX week 4 exec ──▶ rapport week 6
EX-9 stripe live ────────▶ KYC ──▶ test E2E vrais fonds week 5
EX-10 email custom ──────▶ DKIM/SPF prop ────▶ délivrabilité week 4
Code:
W1 finir v1.0.9 + IaC ──▶ W2 Postgres HA + obs ──▶ W3 stockage + features ──▶
W4 PWA + search + load test ──▶ W5 game day + canary ──▶ W6 go-live
Top-3 risques de glissement :
- Légal : si EX-1 traîne, on tag mais on ne lance pas. Démarre lundi.
- Pentest findings critiques : si le pentest revèle un finding HIGH semaine 6, on rétrograde de tag et on patch — peut décaler 1-2 semaines. Mitigation : faire un pentest interne semaine 4 (ZAP + nuclei + manual audit) pour pré-filtrer.
- Postgres HA failover en game day rouge : si le failover n'atteint pas RTO < 60s on doit ré-architecturer. Mitigation : tester pg_auto_failover en lab Incus dès semaine 1.
4. Sprint breakdown — jour par jour
Convention : chaque jour = ~6-8h productives. Acceptance criteria mesurables. Si un jour déborde, le suivant glisse — pas de "je rattraperai le week-end" autorisé sinon le burn-out garantit le slip de 2 semaines fin de roadmap.
Semaine 1 — Closer v1.0.9 + amorçage IaC
Objectif : tag v1.0.9, scaffolding Ansible posé, Postgres HA en lab.
Jour 1 — Lundi : Démarrer les externes + commit sprint 1
- AM : Envoyer EX-1 (avocat), EX-2 (devis pentest), EX-3 (DMCA agent), EX-9 (Stripe KYC), EX-10 (email transactionnel). Ces 5 emails partent avant midi.
- PM : Commit + push les 4 commits préparés (
/tmp/v1.0.9-commit-messages.md). Vérifier CI Forgejo verte. Si rouge → debug avant de continuer. - Acceptance : 5 emails envoyés, 4 commits pushés, CI verte sur main.
- Files :
/tmp/v1.0.9-commit-messages.md
Jour 2 — Mardi : Item G subscription Phase 2
- Webhook handler
subscription.payment_succeeded/payment_faileddansinternal/services/hyperswitch/webhook_subscription.go - Dispatcher dans
ProcessPaymentWebhookqui détecte le type subscription et route - Reuse
webhook_raw_payloadstable (item E v1.0.7) pour persistence - Tests : webhook arrive → row pending_payment → active. Webhook payment_failed → expired.
- Acceptance : 4 tests unitaires verts (success, failed, replay idempotent, unknown event)
- Files :
internal/services/hyperswitch/webhook_subscription.go(nouveau),internal/services/hyperswitch/webhook_subscription_test.go(nouveau)
Jour 3 — Mercredi : Item G subscription Phase 3 + E2E
- Endpoint recovery
POST /api/v1/subscriptions/complete/:idqui retourne leclient_secretHyperswitch pour reprendre un paiement stalled - Distribution gate :
distribution.checkEligibilitytraitepending_paymentcomme ineligible - E2E
@criticaltests/e2e/subscription-pending-payment.spec.ts: subscribe paid plan → distribution submit retourne 403 → simuler webhook → distribution OK - Remove
TODO(v1.0.7-item-G)annotation danssubscription/service.go - Acceptance : E2E vert + grep TODO Item G = 0 hit
- Files :
internal/handlers/subscription_handler.go,internal/api/routes_subscription.go,internal/core/distribution/service.go,tests/e2e/28-subscription-pending-payment.spec.ts
Jour 4 — Jeudi : Item 1.7 flake stab + tag v1.0.9
- Lire les rapports CI Forgejo des 5 derniers runs E2E sur main
- Identifier flakes : tests qui ont failé puis passé sans changement code
- Stabiliser : remplacer
waitForTimeoutparwaitForciblés, ajouter retry-on-flake helpers, augmenterexpect.timeoutciblé - 3 cycles E2E nightly verts consécutifs avant tag
- Acceptance : 3 nightly E2E verts,
git tag v1.0.9poussé - Files :
tests/e2e/*.spec.ts(stabilisations ciblées),tests/e2e/playwright.config.ts(timeout ciblé si nécessaire)
Jour 5 — Vendredi : IaC scaffolding Ansible
- Créer
infra/ansible/avec layout standard :inventory/,group_vars/,host_vars/,roles/,playbooks/ - Inventaire : 1 host R720 prod + 1 host Hetzner staging + 1 host R720 lab
- Rôle
common: SSH hardening, fail2ban, unattended-upgrades, monitoring agent - Rôle
incus_host: install Incus + configuration networking - Test : déployer le rôle common sur le host lab
- Acceptance :
ansible-playbook -i inventory/lab playbooks/site.yml --checkpasse sans erreur - Files :
infra/ansible/inventory/{lab,staging,prod}.yml,infra/ansible/playbooks/site.yml,infra/ansible/roles/common/tasks/main.yml,infra/ansible/roles/incus_host/tasks/main.yml,infra/ansible/README.md
Verification gate W1 : tag v1.0.9 poussé, Ansible scaffolding fonctionne en lab, externes EX-1 à EX-10 démarrés.
Semaine 2 — Postgres HA + observabilité
Objectif : Postgres failover automatique testé, OpenTelemetry collector wired, SLO alerts définies.
Jour 6 — Lundi : Postgres HA — pg_auto_failover lab
- Rôle Ansible
postgres_ha: 2 conteneurs Incus (primary + replica), pg_auto_failover monitor - Configuration : sync replication, HOT_STANDBY, max_wal_senders, etc.
- Test : kill primary, vérifier failover automatique en < 60s, vérifier replica devient primary
- Acceptance : failover test scripté
infra/ansible/tests/test_pg_failover.shrouge avant fix, vert après. RTO mesuré < 60s. - Files :
infra/ansible/roles/postgres_ha/{tasks,templates,defaults}/,infra/ansible/tests/test_pg_failover.sh
Jour 7 — Mardi : PgBouncer + connection pool
- Rôle Ansible
pgbouncer: conteneur Incus avec PgBouncer en transaction mode - Pool size : 50 server connections × 1000 client connections capacity
- Backend-api config :
DATABASE_URLpointe vers PgBouncer, pas direct Postgres - Load test : 500 connexions concurrentes simulées via
pgbench, vérifier pas d'épuisement - Acceptance : pgbench 500 clients × 30s sans erreur de connexion
- Files :
infra/ansible/roles/pgbouncer/{tasks,templates}/,veza-backend-api/internal/config/config.go(URL pointe PgBouncer en prod)
Jour 8 — Mercredi : pgBackRest + restore drill
- Rôle Ansible
pgbackrest: install + configuration WAL archive vers MinIO bucket dédié - Backup full hebdo + différentiels quotidiens + WAL continu
- Script
scripts/dr-drill.sh: restore le dernier full+WAL dans un conteneur éphémère, lancecmd/tools/seed --cipartial check, assertionSELECT count(*) FROM users > 0 - Cron hebdo qui execute le script + alerte Prometheus si fail
- Acceptance :
bash scripts/dr-drill.shpasse vert localement - Files :
infra/ansible/roles/pgbackrest/{tasks,templates}/,scripts/dr-drill.sh,config/prometheus/alert_rules.yml(ajoutBackupRestoreDrillFailed)
Jour 9 — Jeudi : OpenTelemetry collector + Tempo
- Rôle Ansible
otel_collector: conteneur OpenTelemetry collector (config receivers + processors + exporters) - Conteneur Tempo (Grafana stack) pour stocker les traces
- Backend-api : exporter OTLP vers le collector (déjà câblé en émetteur, juste activer le wire)
- Instrumenter 4 hot paths : auth login, track upload (chunked init/chunk/complete), payment webhook, search
- Dashboard Grafana basique avec service map + p99 par endpoint
- Acceptance : tracer une requête login → voir le span dans Tempo UI
- Files :
infra/ansible/roles/otel_collector/,config/otel/collector-config.yaml,veza-backend-api/internal/tracing/otlp_exporter.go(nouveau ou extension),config/grafana/dashboards/service-map.json
Jour 10 — Vendredi : SLO burn-rate alerts + runbooks
- Définir 3 SLO dans
config/prometheus/slo.yml:SLO_API_AVAILABILITY: 99.5% sur/api/v1/health+ endpoints lectureSLO_API_LATENCY: 99% p95 < 500ms sur endpoints écritureSLO_PAYMENT_SUCCESS: 99.5% des/api/v1/ordersPOST → 201
- Multi-window burn-rate alerts (fast 1h burn 14.4× → page, slow 6h burn 6× → ticket)
- Annotations
runbook_urlsur chaque alert pointantdocs/runbooks/<alert-name>.md - Squelette runbooks pour les 6 alerts les plus probables
- Acceptance :
promtool check rules config/prometheus/slo.ymlvert, dashboard Grafana SLO visible avec error budget tracking - Files :
config/prometheus/slo.yml,config/alertmanager/routes.yml(acheminement page vs ticket),docs/runbooks/{api-availability-slo-burn,payment-success-slo-burn,db-failover,redis-down,disk-full,cert-expiring-soon}.md
Verification gate W2 : pg_auto_failover test rouge → vert, pgbench 500 connections OK, traces visibles dans Tempo, SLO burn-rate alerts définies + runbooks indexés.
Semaine 3 — Stockage HA + features signature
Objectif : MinIO erasure-coded, CDN edge wired, embed widget + DMCA workflow + track sharing.
Jour 11 — Lundi : Redis Sentinel HA + cache metrics
- Rôle Ansible
redis_sentinel: 3 conteneurs Incus (1 master + 2 replicas + 3 sentinels colocated) - Backend-api :
RedisClientswitch versNewFailoverClientavec MasterName + SentinelAddrs - Cache hit rate metrics : ajouter compteurs Prometheus dans rate limiter, chat pubsub, presence
- Test : kill Redis master, vérifier promotion automatique d'un replica en < 30s
- Acceptance : failover Redis test rouge → vert, dashboard Grafana avec hit rate par service
- Files :
infra/ansible/roles/redis_sentinel/,veza-backend-api/internal/config/config.go(Sentinel config),veza-backend-api/internal/metrics/cache_hit_rate.go(nouveau)
Jour 12 — Mardi : MinIO distribué + migration
- Rôle Ansible
minio_distributed: 4 conteneurs Incus en pool EC4+2 (résiste perte 2 conteneurs sans data loss) - Configuration : bucket
veza-prod-tracks, lifecycle policy (versioning 30j, Glacier après 90j) - Migration depuis le single-node actuel via
mc mirror veza-current/ veza-distributed/ - Backend-api : pas de changement code (interface S3 reste identique)
- Smoke test : upload 100MB via
mc cp, kill 2 nœuds, vérifier read OK, restart nœuds, vérifier rebuild - Acceptance : EC4+2 résiste à 2 nœud kills, dashboard MinIO healthcheck vert
- Files :
infra/ansible/roles/minio_distributed/{tasks,templates}/
Jour 13 — Mercredi : CDN edge externe (Cloudflare R2 ou Bunny.net)
- Choisir : Cloudflare R2 (gratuit 10GB, $0.015/GB stockage, $0/GB egress) OU Bunny.net Stream ($0.005/GB egress audio)
- Configurer compte + bucket + cache rules + signed URL
- Câbler
internal/services/cdn_service.go(déjà existant) pour générer URLs CDN au lieu de MinIO direct - Trackservice
GetStorageURL: retourner CDN signed URL pour les routes publiques (stream, download) - Cache headers backend-api : Cache-Control: public, max-age=86400, immutable pour les segments HLS
- Test : upload track, lire via /tracks/:id/stream → assert URL =
cdn.veza.fr/... - Acceptance : 1 track lue via CDN edge, latence first byte < 200ms depuis 3 PoPs
- Files :
veza-backend-api/internal/services/cdn_service.go(extend),veza-backend-api/internal/core/track/service.go(GetStorageURL hook CDN),infra/ansible/roles/backend_api/templates/.env.j2(env vars CDN)
Jour 14 — Jeudi : DMCA notice handler + workflow
- Migration
987_dmca_notices.sql: tabledmca_notices(id, status, claimant_email, claimant_name, claimant_address, work_description, infringing_track_id, sworn_statement_at, status, takedown_at, counter_notice_at, restored_at, audit_log) - Handler
internal/handlers/dmca_handler.go:POST /api/v1/dmca/notice: public, rate-limited (5/IP/h), validate fields, enregistrependingGET /api/v1/admin/dmca/notices: admin-only, queue + paginationPOST /api/v1/admin/dmca/notices/:id/takedown: admin action → setstatus=takedown, settakedown_at=NOW(), mark trackis_public=false+dmca_blocked=truePOST /api/v1/admin/dmca/notices/:id/dismiss: admin action → setstatus=rejected
- Page publique frontend
/legal/dmcaavec formulaire + agent désigné info - Audit log dans
audit_logs(existing v0.803 SEC2) - Acceptance : E2E
tests/e2e/29-dmca-notice.spec.tsvert (notice → admin queue → takedown → track plus accessible) - Files :
veza-backend-api/migrations/987_dmca_notices.sql+ rollback,veza-backend-api/internal/handlers/dmca_handler.go(nouveau),veza-backend-api/internal/api/routes_legal.go(nouveau),apps/web/src/features/legal/pages/DmcaNoticePage.tsx(nouveau),tests/e2e/29-dmca-notice.spec.ts
Jour 15 — Vendredi : Embed widget + oEmbed + track share tokens
- Route backend
GET /embed/track/:id: retourne page HTML standalone (pas le SPA, page minimaliste avec player + waveform SVG) - Route backend
GET /oembed?url=<track_url>&format=json: réponse oEmbed standard (type=rich, html=<iframe src="...">) - Open Graph tags dynamiques sur
/tracks/:id(SSR mini ou meta runtime) - Migration
988_track_share_tokens.sql: tabletrack_share_tokens(id, track_id, token UUID, expires_at NULLABLE, created_by_user_id) - Handler
POST /api/v1/tracks/:id/share-tokens(création) +GET /api/v1/tracks/share/:token(lecture) - Test : créer share token → fetch via token → assert track lisible même si is_public=false
- Acceptance : embed iframe Twitter card preview OK, oEmbed JSON valide, share token E2E vert
- Files :
veza-backend-api/internal/handlers/embed_handler.go(nouveau),veza-backend-api/internal/handlers/oembed_handler.go(nouveau),veza-backend-api/migrations/988_track_share_tokens.sql+ rollback,apps/web/embed/track.html(template standalone),tests/e2e/30-embed-and-share.spec.ts
Verification gate W3 : Redis HA failover OK, MinIO EC4+2 résiste 2 kills, 1 track lue via CDN, DMCA notice E2E vert, embed widget Twitter card preview verte, share token fonctionne.
Semaine 4 — PWA mobile + HLS default + faceted search + load test
Objectif : expérience mobile aboutie, HLS ABR par défaut, recherche faceted, capacité validée 1k VU.
Jour 16 — Lundi : Service worker offline track cache (Workbox)
- Activer service worker en prod (déjà câblé
pwa.ts:48-56) - Workbox stratégies :
- Static assets :
StaleWhileRevalidate - HLS segments :
CacheFirstavec max-age 7j, max 50 entries - API calls :
NetworkFirstavec timeout 5s
- Static assets :
- Cache offline track : intercepter les fetch
/api/v1/tracks/:id/streamredirigés vers CDN, cacher les segments HLS du track en cours + les 50 derniers favoris - Background sync API pour les actions hors-ligne (like, comment) qui rejouent au retour réseau
- Test : devtools network offline mode, jouer un track déjà écouté, doit fonctionner
- Acceptance : test manuel offline OK, Lighthouse PWA score ≥ 90
- Files :
apps/web/src/sw.ts(extend Workbox config),apps/web/src/services/offlineQueue.ts(extend pour background sync)
Jour 17 — Mardi : HLS par défaut + lossless tier preview
- Flip
HLS_STREAMING=truedans.env.production - Smoke test : upload track, vérifier que le transcode HLS s'execute, vérifier ABR sur le frontend (
useHLSPlayerhook) - Pre-listen 30s public sur les produits marketplace : extend
marketplace.go:258-298pour les tracks publics si créateur opt-in - FLAC tier preview : ajouter une checkbox "FLAC available" sur la page upload pour les Premium subscribers (visible mais reporté post-launch pour la consommation effective)
- Acceptance : nouveau track uploadé sert HLS ABR (3 niveaux 128/256/320), Network panel devtools confirme switch ABR sur réseau dégradé
- Files :
infra/ansible/roles/backend_api/templates/.env.j2(HLS_STREAMING=true),apps/web/src/features/upload/components/UploadForm.tsx(FLAC checkbox)
Jour 18 — Mercredi : Faceted search UI
- Composant
apps/web/src/features/search/components/FacetSidebar.tsx: sidebar avec filtres genre / BPM range / musical_key / year / type (track/playlist/user) - Wire avec orval-generated
useGetSearchhook : passerparams.type[], query params custom pour bpm_min/max - Backend : étendre
search_handlers.go:Searchpour accepterbpm_min,bpm_max,musical_key,year_from,year_to(déjà partiellement supportés via SearchService) - Test : recherche "rock" + filtre BPM 120-130 → résultats restreints
- Acceptance : E2E
tests/e2e/31-faceted-search.spec.tsvert - Files :
apps/web/src/features/search/components/FacetSidebar.tsx(nouveau),apps/web/src/features/search/pages/search-page/SearchPage.tsx(intégration),veza-backend-api/internal/handlers/search_handlers.go(extend params),veza-backend-api/internal/services/search_service.go(extend filter SQL),tests/e2e/31-faceted-search.spec.ts
Jour 19 — Jeudi : HAProxy sticky WS + 2 backend-api actif/actif
- Rôle Ansible
haproxy: conteneur HAProxy avec configfrontend tls / backend api_pool+ sticky cookie sur les routes WS - Backend-api : déployer 2 conteneurs Incus actif/actif derrière HAProxy. Stateless OK (sessions Redis-backed déjà câblé).
- Health check
/api/v1/healthtoutes les 5s, drain 30s avant remove - Test : kill backend-api 1, vérifier HAProxy bascule, vérifier sessions WS reconnectent au backend-api 2 sans perte de message
- Stream-server : 2 conteneurs avec affinité sticky par
track_idhash (HLS cache local optimisé) - Acceptance : test scripté
infra/ansible/tests/test_backend_failover.shvert - Files :
infra/ansible/roles/haproxy/{tasks,templates}/,infra/ansible/roles/backend_api/tasks/main.yml(multi-instance),infra/ansible/tests/test_backend_failover.sh
Jour 20 — Vendredi : Load test k6 nightly + capacity validation
- Compléter
scripts/loadtest/k6_load_test.jsavec scénarios mix réalistes :- 100 VU upload concurrent (10MB tracks)
- 500 VU streaming HLS (segments fetch)
- 1000 VU search + browse mixte
- 50 VU checkout marketplace
- Threshold p95 < 500ms global, error rate < 0.5%
- Workflow CI nightly
.github/workflows/loadtest.ymlqui lance k6 sur staging - Profiling pprof endpoints lents identifiés
- Capacité validée : 1k users concurrents tenus sur 1 R720 sans saturation
- Acceptance : nightly load test passe vert pendant 3 nuits consécutives, dashboard Grafana avec p95/p99 par endpoint
- Files :
scripts/loadtest/k6_*.js,.github/workflows/loadtest.yml(nouveau),docs/PERFORMANCE_BASELINE.md(update avec chiffres réels)
Verification gate W4 : Lighthouse PWA ≥ 90, HLS ABR par défaut OK, faceted search E2E vert, HAProxy failover OK, k6 nightly 1k VU green 3 nuits.
Semaine 5 — Pentest + game day + canary + status page
Objectif : pentest exécuté, game day rejoué et runbooks validés, canary release process automatisé, status page publique.
Jour 21 — Lundi : Pré-flight pentest interne (avant le pentest externe)
- Lancer ZAP automated scan sur staging :
docker run -v $(pwd):/zap/wrk owasp/zap2docker-stable zap-baseline.py -t https://staging.veza.fr - Lancer nuclei :
nuclei -u https://staging.veza.fr -t cves/ -t vulnerabilities/ -t exposures/ - Manual audit OWASP Top 10 sur les nouveaux endpoints v1.0.9 :
/api/v1/dmca/notice: XSS via work_description, SSRF via URLs, CSRF/embed/track/:id: XSS via track metadata/api/v1/config/webrtc: disclosure (rappel : volontairement public)/api/v1/tracks/share/:token: enumeration timing
- Fix les findings HIGH internes avant l'externe
- Acceptance : 0 finding HIGH dans le rapport ZAP/nuclei, manual audit issues fixées ou justifiées
- Files :
docs/SECURITY_PRELAUNCH_AUDIT.md(nouveau, avec findings + résolutions)
Jour 22 — Mardi : Game day #1 — failures simulées
- Scénario A : kill primary Postgres pendant 5min, vérifier failover < 60s + apps reconnectent
- Scénario B : kill HAProxy backend-api 1, vérifier autres backends servent + WS clients reconnect
- Scénario C : kill Redis sentinel master, vérifier promotion replica + chat continue
- Scénario D : saturate disque MinIO 95%, vérifier alertes + degraded write mode
- Scénario E : RabbitMQ down 30min, vérifier event bus log error loud + queue mémoire fallback
- Documenter chaque incident dans
docs/runbooks/game-days/2026-W5-game-day-1.md: timestamp, action, observation, runbook utilisé, gap découvert - Acceptance : aucun silent fail, aucune 5xx > 30s post-incident, chaque alert Prometheus déclenchée < 1min
- Files :
docs/runbooks/game-days/2026-W5-game-day-1.md(nouveau),docs/runbooks/<alert-name>.md(mises à jour selon gaps trouvés)
Jour 23 — Mercredi : Canary release process
- Script
scripts/deploy-canary.sh:- Pull nouvelle image backend-api
- Désactiver backend-api 2 dans HAProxy (drain mode)
- Déployer nouvelle image sur backend-api 2
- Health check :
curl https://api.veza.fr/api/v1/healthdoit return 200 - Re-enable backend-api 2 dans HAProxy
- Monitor pendant 1h : SLI doivent rester verts (p95 < 500ms, err rate < 0.5%)
- Si SLI vert : repeat sur backend-api 1 (full deploy)
- Si SLI rouge : rollback automatique (re-déployer ancienne image)
- Pre-deploy hook : check migrations DB sont backward-compatible (nouveau schema doit fonctionner avec ancienne version code)
- Documentation
docs/CANARY_RELEASE.md - Test : faire 3 canary deploys sur staging avec rollback simulé
- Acceptance : 3 canary deploys réussis (2 normaux + 1 avec rollback simulé volontaire)
- Files :
scripts/deploy-canary.sh(nouveau),docs/CANARY_RELEASE.md(nouveau),Makefile(targetdeploy-canary)
Jour 24 — Jeudi : Status page + synthetic monitoring
- Setup status page : Cachet (self-hosted) sur conteneur Incus OU statuspage.io free tier
- Composants à monitor : API, Stream, Chat, Marketplace, Live, CDN
- Endpoint
GET /api/v1/statuspublic qui retourne{status: "operational"|"degraded"|"down", components: {...}}(utilisé par la status page + supervision) - Synthetic monitoring : Blackbox exporter Prometheus rejouant 6 parcours toutes les 5min depuis un Incus externe (hors-réseau prod) :
- Register → verify → login
- Login → search "test" → play first result
- Login → upload tiny audio → poll status
- Login → browse marketplace → add to cart
- WebSocket chat connect + send message
- Live stream metadata fetch
- Alert Prometheus si parcours fail 2 fois consécutives
- Acceptance : status page accessible sur
status.veza.fr, synthetic monitoring vert sur 6 parcours pendant 24h - Files :
infra/ansible/roles/cachet/(si self-hosted),veza-backend-api/internal/handlers/status_handler.go(nouveau),infra/ansible/roles/blackbox_exporter/,config/prometheus/blackbox_targets.yml
Jour 25 — Vendredi : Pentest externe kick-off + buffer
- Briefer le pentester (selon EX-2) : scope auth + paiement + DMCA + uploads + WebRTC + embed widget. Donner accès staging dédié avec données pré-seed.
- Le pentest s'exécute en async semaine 5 et début semaine 6.
- Buffer day pour rattraper les retards des jours 21-24 + tester le canary process une fois encore.
- Si tout est en avance : commencer 2.7 (rate limit tiers per-app) en avance sur la semaine 6.
- Acceptance : pentester actif, staging accessible, runbooks à jour
- Files :
docs/PENTEST_SCOPE_2026.md(nouveau, brief technique pour le pentester)
Verification gate W5 : pentest interne 0 HIGH, game day documenté avec 0 silent fail, 3 canary deploys verts, status page publique, synthetic monitoring vert 24h.
Semaine 6 — GO/NO-GO + go-live
Objectif : tout le checklist GO/NO-GO vert, soft launch beta privée, public launch.
Jour 26 — Lundi : GO/NO-GO checklist final pass
- Réutiliser et faire passer en vert
docs/GO_NO_GO_CHECKLIST_v1.0.0.mdmais cible v2.0.0-public :- Sécurité : pentest 0 critique/haut, JWT RS256 prod ✓, secrets hors git ✓
- Stabilité : uptime ≥ 99.9% staging 30j (vérifier dashboards), taux 5xx < 0.1%, 0 incident P0 ouvert
- Performance : p95 API < 100ms, Lighthouse perf ≥ 85, a11y ≥ 90, PWA ≥ 90
- Qualité : coverage ≥ 70%, 0 lint, CI verte 2 sem
- Éthique : audit anti-dark-pattern (relire
ORIGIN_UI_UX_SYSTEM.md§13), 0 ML reco, feed chronologique, algo découverte documenté - Business : flow paiement E2E réel testé (jour 27), KYC vendeur testé E2E, support accessible
- Documenter chaque ligne avec preuve (lien dashboard, commit, test passing)
- Lister les RED items avec plan de remédiation jour 27-28
- Acceptance : checklist exhaustive, RED items < 3 et tous remédiables d'ici jour 28
- Files :
docs/GO_NO_GO_CHECKLIST_v2.0.0_PUBLIC.md(nouveau, dérivé du v1.0.0)
Jour 27 — Mardi : Test E2E paiement réel + remediation RED items
- AM : Stripe live mode + Hyperswitch live actifs (selon EX-9), faire un vrai achat marketplace (5€ produit test) avec ta propre carte. Vérifier : webhook reçu, license attribuée, vendeur Connect crédité, payout planifié, refund possible.
- PM : remediation RED items du checklist GO/NO-GO. Si > 5 RED items, escalade : décaler le launch d'1 semaine.
- Acceptance : 1 achat E2E réel avec vrais fonds, refund testé, RED items 0
- Files :
docs/PAYMENT_E2E_LIVE_REPORT.md(nouveau, traces de la transaction live)
Jour 28 — Mercredi : Game day #2 + canary deploy prod
- Game day #2 : rejouer les 5 scénarios du jour 22 sur prod (avec maintenance window 1h annoncée). Cible : tout vert sans intervention manuelle, runbooks validés.
- Canary deploy prod : déployer la version v2.0.0-rc1 sur le R720 prod via le script du jour 23. Soak test 4h, vérifier SLI verts.
- Annonce interne (Discord/Slack équipe) que prod est sur v2.0.0-rc1.
- Acceptance : game day prod vert, canary deploy v2.0.0-rc1 vert, soak 4h vert
- Files :
docs/runbooks/game-days/2026-W6-game-day-2.md,docs/RELEASE_NOTES_V2.0.0_RC1.md
Jour 29 — Jeudi : Soft launch beta privée
- Inviter 50-100 testeurs (mailing list pré-launch, contacts perso, communautés musicales sélectives)
- Email d'invitation avec lien d'inscription + code beta + form Typeform de feedback
- Monitor en temps réel : status page, Grafana dashboards, Sentry frontend errors, support inbox
- Documenter chaque issue rapportée → triage HIGH/MED/LOW
- HIGH issues fixées le jour même, MED le lendemain, LOW backlog post-launch
- Acceptance : 50+ testeurs onboardés, < 3 HIGH issues, monitoring vert
- Files :
docs/SOFT_LAUNCH_BETA_2026.md(rapport feedback consolidé)
Jour 30 — Vendredi : Public launch
- AM : final pre-launch checklist :
- Status page tout vert
- Game day #2 runbook signed off
- Pentest report reçu, 0 critique/haut ouvert (ou risques accepted documentés)
- Légal signé : ToS, Privacy, DMCA agent enregistré
- Soft launch beta : 0 HIGH issue ouverte
- PM :
git tag v2.0.0+ push, annonce publique (réseaux sociaux, Hacker News, communautés musicales, Product Hunt prep) - DNS : couper la maintenance page, ouvrir le trafic public
- War room ouvert (Discord/Slack) pour les 48h post-launch
- Acceptance : v2.0.0 tagué, publication publique, monitoring actif 24h
- Files :
CHANGELOG.md(entry v2.0.0),docs/RELEASE_NOTES_V2.0.0.md,docs/POST_LAUNCH_RUNBOOK.md
Verification gate W6 = LAUNCH : tag v2.0.0 poussé, trafic public ouvert, monitoring vert 24h, war room actif.
5. Post-launch immédiat (D+1 à D+7)
| Jour | Action | Owner |
|---|---|---|
| D+1 | War room actif, monitor toutes les 30min, daily standup interne | toi |
| D+2 | Premier patch release v2.0.1 si HIGH issues remontées | toi |
| D+3 | Post-mortem du launch : ce qui a bien marché, ce qui a cassé | toi |
| D+5 | Annonce status : "X users, Y tracks, Z transactions, uptime A%" | toi |
| D+7 | Démarrer v2.1 — backlog post-launch (Capacitor mobile, fingerprinting, distribution DSP) | toi |
6. Risk register
| # | Risque | Proba | Impact | Mitigation | Owner |
|---|---|---|---|---|---|
| R-1 | Légal traîne au-delà de semaine 5 | M | HIGH | Démarrer EX-1 jour 1, relancer chaque semaine, ToS template open-source en backup (commonsclause-style) |
toi |
| R-2 | Pentest finding CRITICAL semaine 6 | M | CRITICAL | Pentest interne semaine 5 jour 21 pour pré-filtrer, budget 3-5j patch reserve semaine 6 | toi |
| R-3 | Postgres failover ne tient pas RTO < 60s | L | HIGH | Tester en lab dès jour 6, fallback : monitor + alert + manual failover documenté (RTO ~5min) | toi |
| R-4 | Item G subscription bug en prod (paiement réel) | L | CRITICAL | E2E @critical + manual smoke jour 27 + reconciliation worker (déjà v1.0.7) catch les drifts |
toi |
| R-5 | Coturn deploy raté (NAT/UDP forwarding) | M | MED | Tester en sandbox semaine 1-2 (lab Incus), fallback : STUN seul pour launch + advisory UI | toi |
| R-6 | CDN coûts explosent au launch (audio bandwidth) | M | MED | Bunny.net (~5 €/TB) plus prévisible que Cloudflare R2 ($/GB), monitoring bandwidth daily | toi |
| R-7 | Soft launch beta trouve un vrai bug bloquant | H | HIGH | Buffer jour 27 PM + jour 28, max 3 HIGH issues acceptables | toi |
| R-8 | DDoS / abuse au launch | M | MED | Rate limits déjà en place v0.803, Cloudflare proxy en option pour absorption L7 | toi |
| R-9 | Dette technique remonte (CI rouge, flake) | H | LOW | Buffer 20% dans chaque journée, git revert prêt si une feature sabote l'ensemble | toi |
| R-10 | Burnout / décrochage perso | M | CRITICAL | Pas de week-end de "rattrapage", buffer ½ journée par sem, scope-creep refusé | toi |
7. Defended scope — ce qu'on REFUSE pendant les 6 semaines
Si pendant les 6 semaines tu te poses la question "devrais-je rajouter X au scope launch", la réponse par défaut est non, post-launch.
Liste explicite des refus pré-approuvés :
- Native mobile (iOS/Android) — REFUS. Capacitor wrap post-launch, ~3 sem.
- Audio fingerprinting — REFUS. DMCA workflow manuel suffit. Chromaprint v1.1.
- Public API SDK — REFUS. API key + webhooks v0.803 restent. SDK v1.1.
- OAuth2 client credentials — REFUS. v1.1.
- Multi-region failover — REFUS. R720 + Hetzner standby suffit. v1.2.
- Tipping / fan support — REFUS. Décision business post-launch.
- Distribution DSP (TuneCore/CD Baby) — REFUS. v1.1.
- Subscription per-creator (Patreon-style) — REFUS. v1.1+.
- CarPlay / Android Auto — REFUS. Nécessite app native.
- Lossless tier FLAC consommation — REFUS UI complète. Codec exposé en upload form, consommation v1.1.
- "Fresh from your follows" tab — REFUS. Filtre chronologique simple suffit.
- Editorial collections admin UI — REFUS. Curation manuelle DB suffit.
- Saved searches — REFUS. Faceted search UI suffit.
- PWA push notifications custom — REFUS extends. Le push existant v0.302 N1 suffit.
- Light/dark theme switcher avancé — REFUS. Le
prefers-color-schemeactuel suffit. - Internationalisation full — REFUS extends. FR + EN au launch (déjà v0.12.7).
- Accessibility AAA — REFUS. AA suffit (déjà v0.12.6).
- GraphQL API — REFUS jamais (pas dans la stack).
- Migration vers cloud managed — REFUS. Self-hosted Incus est le choix.
Si quelqu'un te pousse à ajouter du scope, refer ce document.
8. Definition of "Production Ready" — critères absolus
Au tag v2.0.0 le 8 juin 2026 (jour 30 = vendredi semaine 6), Veza DOIT remplir tous les critères suivants :
Sécurité (5 critères)
- JWT RS256 prod, clés rotées récemment (< 90j)
- Secrets hors git (Vault ou sops/age)
- Pentest externe rapport reçu, 0 finding critique/haut ouvert (HAUT moyens acceptés documentés)
- Rate limits prod actifs (1000/s global, 100/IP/s)
- HTTPS partout, HSTS preload submitted, certs auto-renouvellement
Stabilité (5 critères)
- Postgres HA failover testé (RTO < 60s) sur staging ET prod
- Redis Sentinel failover testé
- Backup restore drill cron passe vert depuis 7j consécutifs
- Game day #1 + #2 documentés, runbooks signés
- Canary release process testé 3 fois, rollback automatique vérifié
Performance (4 critères)
- p95 API < 500ms sur 1k VU mixed load (k6 nightly green)
- Lighthouse Performance ≥ 85, Accessibility ≥ 90, PWA ≥ 90
- CDN edge actif, latence first byte < 200ms depuis 3 PoPs
- HLS ABR fonctionne (3 niveaux 128/256/320)
Observabilité (5 critères)
- Grafana dashboards : API overview, Chat, Commerce, Stream, Live, Ledger health
- OpenTelemetry collector vivant, traces visibles dans Tempo
- Loki HA 3 réplicas, logs application centralisés
- SLO burn-rate alerts définies (3 SLO), runbooks indexés
- Status page publique sur
status.veza.fr, synthetic monitoring vert 7j
Légal (5 critères)
- Terms of Service publié sur
/legal/terms, avocat-validé - Privacy Policy publié sur
/legal/privacy, RGPD + CCPA conformes - DMCA notice formulaire actif sur
/legal/dmca, agent enregistré US Copyright Office - DSA point de contact UE désigné, transparency report template prêt
- CGV vendeur marketplace publiées
Business (4 critères)
- Stripe Connect live mode actif, KYC créateur testé E2E
- Hyperswitch live mode actif, paiement E2E vrai fonds testé (5+€)
- Refund E2E vrai fonds testé
- Email transactionnel sur domaine custom (SPF/DKIM/DMARC vert)
Qualité (4 critères)
- Coverage tests ≥ 70% (Go + Rust + TS)
- 0 lint error (golangci-lint, ESLint, clippy)
- CI verte sur main depuis 2 semaines consécutives
- E2E
@criticalvert sur les 5 derniers nightly
Éthique (5 critères absolus, non-négociables)
- Aucun code AI/ML recommandation (grep
tensorflow|pytorch|sklearn= 0 hit) - Aucune métrique popularité publique (likes/plays cachés des vues publiques)
- Feed chronologique (
ORIGIN_REVISION_SUMMARY.mdrule 7) - Algorithme découverte documenté + auditable (
docs/DISCOVERY_ALGORITHM.md) - Audit UX anti-dark-pattern signed off
Total : 37 critères. Si > 3 sont rouges au matin du jour 30, le launch décale d'1 semaine.
9. Communication & external comms
Pré-launch (semaines 1-5)
- Lundi semaine 1 : annonce interne (Discord équipe / proches) "lancement public début juin 2026"
- Semaine 3 : ouvrir un compte X/Mastodon Veza, premiers posts teaser
- Semaine 4 : open la mailing list pré-launch (formulaire
veza.fr) - Semaine 5 : envoi soft launch invitations 50-100 testeurs
Launch jour 30
- Annonce simultanée : Twitter/X, Mastodon, Hacker News (Show HN), Reddit /r/musicians, /r/Wearethemusicmakers, Product Hunt préparé pour J+1
- Email à la mailing list pré-launch
- Communiqué de presse short (3 paragraphes) à musique-tech press (TechCrunch, ResidentAdvisor, Music Ally)
Post-launch (D+1 à D+30)
- Daily monitoring stats sur le compte X/Mastodon
- Weekly product update blog
- Office hours hebdo Discord (Q&A créateurs)
10. Annexes — références rapides
- État de départ :
docs/PROJECT_STATE.md,docs/FEATURE_STATUS.md,FUNCTIONAL_AUDIT.md - Audit complet :
AUDIT_REPORT.md,docs/audit-2026-04/ - Plan original 24 semaines :
/home/senke/.claude/plans/quelles-sont-les-prochaines-playful-beaver.md - Sprint 1 v1.0.9 commit messages :
/tmp/v1.0.9-commit-messages.md - Configurations infra :
infra/coturn/,infra/ansible/(à créer semaine 1) - Runbooks :
docs/runbooks/+k8s/disaster-recovery/runbooks/ - Règles immuables :
CLAUDE.md§🚫 +veza-docs/ORIGIN/ - GO/NO-GO précédent (v1.0.0-rc1) :
docs/GO_NO_GO_CHECKLIST_v1.0.0.md
11. Cadence quotidienne suggérée
08:00-08:30 Café + revue plan (ce doc) + vérif notif Forgejo nightly
08:30-09:00 Revue inbox externe (légal/pentest/ops emails)
09:00-12:30 Bloc focus matin — la tâche du jour (~3.5h)
12:30-13:30 Pause déjeuner OBLIGATOIRE
13:30-17:00 Bloc focus après-midi — finir tâche du jour + tests + commit (~3.5h)
17:00-17:30 Update du checklist + push final + setup demain
17:30 Stop. Pas de "juste un dernier truc".
Si une journée glisse de > 4h, ne pas rattraper le soir/week-end — re-prioriser la suite. Le burnout au jour 20 = launch raté.
Document vivant. Mettre à jour le statut des tâches en cochant directement dans ce fichier ([ ] → [x]). Commit chaque mise à jour avec docs: roadmap launch — <jour X> done pour traçabilité.