Blueprint des apps de la famille
Le tronc commun que chaque app du produit doit porter, l’état d’Orbit contre les dépôts de référence et la liste P0/P1/P2.
Statut (2026-10-02) : première version. Elle fixe ce que chaque app du produit doit porter, où Orbit en est, et ce qu'il reste à faire, avec une priorité et un effort. Elle se lit avec les skills
.claude/skills/(procédure pour les agents) :new-app-blueprint,launch-attribution,billing-stripe,transactional-email,seo-aeo-geo,platform-ops,admin-structure.
Pourquoi ce document existe
Le fondateur exploite plusieurs apps. Deux dépôts de référence, en lecture seule, portent ce qui a déjà été appris :
| Repère | Dépôt | Ce qu'on y lit |
|---|---|---|
| A | BoostEcom/boostecom.app | le système le plus optimisé : mesure, attribution, Stripe, e-mail, SEO/AEO, exploitation, gardes |
| B | BoostEcom/easyconnector.app | la structure d'admin et d'app (onglets par groupe de routes) |
Orbit n'a aucune plateforme parente (AGENTS.md, règle 1) : on copie des patterns avec leur garde, jamais des fichiers, jamais des valeurs, jamais des secrets. Ces dépôts ne sont jamais modifiés depuis ici.
La culture à reprendre (le vrai blueprint)
Toute règle écrite est tenue par un test, un script, un hook ou un workflow CI. Une règle qui ne vit
que dans la prose se périme. Quand on ajoute une règle à AGENTS.md ou à un skill, on ajoute sa garde dans la
même PR, ou on dit pourquoi aucune garde ne peut la tenir (par exemple : « seule la relecture peut
refuser une valeur copiée », règle 2).
Gardes de ce blueprint : apps/web/src/test/blueprint-claims.test.ts (chaque chemin cité par les skills existe, les deux shas de référence sont enregistrés) et audit-orbit-claims.mjs (dossier scripts de ce dépôt de documentation : chaque chemin cité par cette page existe dans le dépôt applicatif), apps/web/src/test/workflow-pins.test.ts (chaque action des workflows est épinglée
par SHA), .github/workflows/secret-scan.yml, .github/workflows/dependency-watch.yml,
.github/workflows/blueprint-watch.yml.
Les forces d'Orbit à ne pas régresser
| Force | Où | Les références font moins bien |
|---|---|---|
Migrations Prisma versionnées, fichier PHASE, pnpm db:drift, garde « base étrangère » | packages/db, skill database | A synchronise le schéma avec prisma db push et un « schema guard » ; la base unique sert de prod |
Proxy fail-closed + CSP à nonce par requête (strict-dynamic) | apps/web/src/proxy.ts, apps/web/src/lib/security/headers.ts | B garde 'unsafe-inline' |
Stripe par lookup_key, script de catalogue avec dry-run, table StripeEvent idempotente | apps/web/src/modules/billing, apps/web/scripts/stripe-catalog.mjs | B : webhooks sans table d'idempotence |
| Back-office séparé (projet Vercel à part, Vercel Authentication, 503 par défaut) | apps/admin | A et B mêlent l'admin à l'app |
| Environnement qui refuse les secrets d'un autre produit | apps/web/src/env/schema.ts, apps/admin/src/env/schema.ts | aucun équivalent |
| Thème sombre par jetons, aucune classe hex | apps/web/src/test/dark-only.test.ts | B : couleurs hex |
Un seul ci.yml épinglé par SHA, concurrency: cancel-in-progress | .github/workflows/ci.yml | B : aucune CI |
| Nommage d'infra = slug du projet Vercel | AGENTS.md « Infrastructure naming » | variable selon les apps |
À ne pas copier
prisma db push, le schema guard généré, ignoreBuildErrors, la base unique qui sert de prod, l'absence de
CI, 'unsafe-inline' dans la CSP, les webhooks sans table d'idempotence, les couleurs hex, le mélange
db:migrate / db:push, un utm_source égal au nom du produit, et tout secret ou identifiant d'un autre
produit (variables Orbit : préfixe ORBIT_*).
Tableau d'écarts : Orbit contre les références
Priorité : P0 = avant le lancement (il manque une mesure ou une protection) ; P1 = dans le mois qui suit ; P2 = quand le besoin se confirme. Effort : S (< 1 jour), M (1 à 3 jours), L (> 3 jours).
| # | Domaine | État d'Orbit | Référence | Prio | Effort | Statut |
|---|---|---|---|---|---|---|
| a | Mesure | apps/web/src/lib/analytics/datafast.ts servait le script sans cookie et affirmait « no Stripe checkout » : faux depuis la facturation. Pas de barrière de consentement (état avant ce lot) | A : variante cookie derrière ThirdPartyConsentGate, datafast_visitor_id en métadonnées du checkout, gardes dans les deux sens | P0 | M | code fait (launch-p0) : variante cookie première partie, bannière + ThirdPartyConsentGate, datafast_visitor_id en métadonnées du checkout ; reste l'action propriétaire (site DataFast, objectifs, Stripe connecté : la checklist de lancement) |
| b | Attribution | aucune capture de premier contact | A : acquisition.ts (cookie premier contact après consentement, jamais écrasé, ref + UTM) | P0 | M | code fait : cookie premier contact après consentement, jamais écrasé, persisté à l'inscription (Workspace.acquisition, migration 20261002160000), lu par l'app admin, objectifs serveur signup / checkout_started / subscription_started, constructeur utmFor |
| c | Facturation | cycle de vie des abonnements et invoice.payment_failed seulement ; pas de Stripe Tax ; ni remboursement, ni litige, ni invoice.paid, ni invoice.payment_action_required | A : services/webhooks.ts (liste complète, crédits depuis la métadonnée nette), cron de réconciliation | P0 | L | code fait (rebase sur le flux lot1 à surveiller : mêmes fichiers) : Stripe Tax derrière ORBIT_STRIPE_TAX, invoice.paid, invoice.payment_action_required, charge.refunded, litiges, politique de retenue testée, cron reconcile-stripe ; reste l'action propriétaire (endpoint, Tax) |
| d | Resend via l'intégration Vercel, sans domaine d'envoi authentifié propre à Orbit pour les codes OTP | A : apex authentifié (SPF, DKIM, DMARC), sous-domaine séparé pour le marketing | P0 | S (action propriétaire : DNS + Resend) puis S (code) | code fait : quatre e-mails de facturation, une mise en page, jetons, deux langues ; reste le domaine d'envoi authentifié (propriétaire, runbook section 4) | |
| e | Secrets | aucune analyse de secrets en CI | A : secret-scan.yml (gitleaks épinglé, historique complet) | P0 | S | fait dans cette branche (.github/workflows/secret-scan.yml) |
| f | Dépendances | rien entre deux PR | A : dependency-watch hebdomadaire | P1 | S | fait dans cette branche (.github/workflows/dependency-watch.yml) |
| g | Blueprint | aucune détection de dérive des références | (nouveau) | P1 | S | fait dans cette branche (.github/workflows/blueprint-watch.yml, scripts/blueprint-drift.mjs) |
| h | Funnel exact | pas de table de jalons ; le funnel dépendrait de DataFast | A : UserMilestone écrit dans la même transaction que le fait métier | P1 | M | à planifier |
| i | Liens sortants | pas de constructeur d'UTM unique | A : un constructeur unique, utm_source = la plateforme | P1 | S | fait avec (b) (apps/web/src/lib/analytics/utm.ts) |
| j | Réconciliation | pas de cron de réconciliation Stripe | A : api/cron/reconcile-stripe | P1 | M | fait avec (c) (/api/cron/reconcile-stripe) |
| k | pas de gabarits React Email partagés, pas de webhook Resend | A : modules/email (un client, mise en page, jetons, désabonnement, Svix) | P1 | L | après (d) | |
| l | SEO / AEO / GEO | robots.ts à une règle ; sitemap = la home ; ni llms.txt, ni JSON-LD, ni images OG dynamiques | A : robots-policy, llms*.txt, JSON-LD, OG | P1 | M | les pages marketing relèvent du flux « landing » ; la politique de crawl demande la décision du propriétaire |
| m | Exploitation | pas de moniteur externe sur /api/health, pas de checklist du tableau de bord Vercel, pas de runbook de rotation des secrets | A : A:docs/ops/vercel-dashboard-checklist.md, A:docs/ops/secret-rotation.md | P1 | S | skill platform-ops ; cases à cocher par le propriétaire |
| n | Admin | apps/admin couvre vue d'ensemble, espaces, admins ; pas de changelog, roadmap, FAQ, retours, coupons | B : onglets par groupe de routes | P1 | L | par module, voir skill admin-structure |
| o | Données personnelles | ni suppression de compte, ni export des données | A : gdpr-erasure | P1 | L | à planifier |
| p | Budget d'AGENTS.md | aucun plafond de taille | A : A:src/test/claude-md-budget.test.ts | P2 | S | non fait : le fichier est court ; à ajouter si les lots le font grossir |
| q | Citations IA | aucune mesure | A : cron aeo-citation | P2 | M | à planifier |
| r | Lancement | pas de phases de lancement (liste d'attente, bêta) ni de coupons | A : launch-tick, coupons | P2 | M | à planifier |
| s | Code mort | pas de détection | A : deadcode.yml, knip.json | P2 | S | à planifier |
La liste P0
Statut (2026-10-02) : le code de (a), (b), (c) et la partie code de (d) est livré sur la branche
launch-p0; ce qu'il reste est côté propriétaire et tient dans la checklist de lancement (site DataFast et objectifs, endpoint webhook Stripe avec les nouveaux événements, Stripe Tax, domaine d'envoi Resend,CRON_SECRET).
- (a) Variante cookie de DataFast, barrière de consentement,
datafast_visitor_id(etdatafast_session_id) dans les métadonnées du checkout, sur la session et sursubscription_data, gardes dans les deux sens ; corriger le commentaire delib/analytics/datafast.ts. - (b) Capture du premier contact (cookie après consentement, jamais écrasé,
refet UTM) et persistance sur leWorkspace(migration versionnée). - (c) Stripe Tax ; événements remboursement, litige,
invoice.paid,invoice.payment_action_required; crédits accordés depuis la métadonnée nette. - (d) Domaine d'envoi Resend authentifié (SPF, DKIM, DMARC) pour les codes OTP.
- (e) Analyse de secrets : fait dans cette branche.
Qui fait quoi pour éviter les doublons. Le flux « facturation / Brain / admin / i18n de l'app » travaille
dans modules/billing (plans et quotas) : (c) et la partie checkout de (a) attendent sa fusion. Le flux
« landing » travaille dans (marketing) et i18n : (l) et la bannière de consentement de (a) passent par lui.
Cette branche ne touche ni l'un ni l'autre.
Dernière synchronisation
La sha de HEAD lue pour chaque dépôt de référence, et la date de la lecture, sont enregistrées dans le fichier de données .github/blueprint-shas.json du dépôt applicatif : c'est l'unique enregistrement, lu par scripts/blueprint-drift.mjs et par le workflow blueprint-watch. Cette page ne les recopie pas.
Les dépôts de référence sont privés : blueprint-watch a besoin d'un jeton en lecture seule (voir .github/workflows/blueprint-watch.yml). Ne modifier le fichier de shas qu'après avoir relu les changements entre la sha enregistrée et la nouvelle (skill new-app-blueprint, section 5).
Mettre à jour le blueprint
- Une PR qui ajoute une garde à une app met aussi ce fichier à jour (une ligne du tableau d'écarts, ou la liste des forces).
- Quand
blueprint-watchouvre l'issue « Blueprint drift » : lire (lecture seule) les changements des références, porter ce qui vaut la peine avec sa garde, mettre à jour.github/blueprint-shas.json(dépôt applicatif) et cette page dans leurs PR respectives. - Les références restent en lecture seule : aucun commit, aucune branche, aucune configuration.