Intégration Sentry SaaS pour error monitoring + replay sur les 2 apps.
API (apps/api) :
- start/sentry.ts : init au plus tôt dans bin/server.ts (avant Ignitor)
pour capturer les erreurs de bootstrap. No-op si SENTRY_DSN_API absent.
- app/exceptions/handler.ts:report : captureException sur les 5xx avec
tags { url, method, status } et user.id (PII minimisée). 4xx filtrés
par beforeSend dans start/sentry.ts (validation, auth invalide = bruit).
- start/env.ts : SENTRY_DSN_API + APP_VERSION optionnels.
- bin/server.ts : import #start/sentry en 1er.
- @sentry/node + @sentry/profiling-node ajoutés au package.json.
Web (apps/web) :
- src/lib/sentry.ts : init au plus tôt dans main.tsx, BrowserTracing +
Replay (0% session, 100% sur erreur — économie quota free tier).
maskAllText + blockAllMedia pour privacy par défaut.
- src/lib/auth.ts : Sentry.setUser({ id }) au login, setUser(null) au
logout (corrélation cross-stack des erreurs front avec un user).
- src/main.tsx : ErrorBoundary autour de l'app avec FallbackError UX.
- vite.config.ts : @sentry/vite-plugin uploads les sourcemaps + les
SUPPRIME du dist/ final (filesToDeleteAfterUpload) pour ne pas leak
le code source via nginx en prod. Helper resolveAppVersion() pour
injecter le sha git en dev (le shell n'étant pas évaluable dans .env).
- src/lib/env.ts : VITE_SENTRY_DSN_WEB + VITE_APP_VERSION optionnels.
- .env.development : VITE_SENTRY_DSN_WEB (préfixé correctement pour
être exposé par Vite — l'ancienne SENTRY_DSN ne marchait pas).
- @sentry/react + @sentry/vite-plugin ajoutés au package.json.
CI Gitea :
- deploy-api.yml : kubectl set env APP_VERSION=${{ github.sha }}
runtime → release Sentry trackable au commit pour l'API.
- deploy-web.yml : build-args VITE_SENTRY_DSN_WEB, VITE_APP_VERSION,
SENTRY_AUTH_TOKEN, SENTRY_ORG injectés depuis les secrets Gitea.
- Dockerfile.web : ARG correspondants + propagation au stage build.
Privacy / sécurité (cf. ADR-024) :
- captureException tags : ctx.route?.pattern (pas l'URL réelle) →
les codes OAuth (?code=...) et tokens de check-in n'apparaissent
jamais dans les tags Sentry indexés.
- Sentry user context = user.id UUID seulement, pas d'email/nom.
- Sourcemaps en prod : uploadées à Sentry, supprimées du bundle.
- 4xx filtrées en amont (beforeSend) ET en aval (handler.ts:report).
- DSN public (by-design) commit-able, AUTH_TOKEN secret CI uniquement.
Sample rates (free tier 5K events / 50 replays par mois) :
- traces : 10% prod, 100% dev
- profiles : 100% (sampled par traces)
- replay session : 0% (économie quota)
- replay sur erreur : 100% (debug post-mortem)
Pré-requis runtime à configurer hors-repo :
- Secret K3s rubis-app-secrets : SENTRY_DSN_API
- Secrets Gitea Actions : SENTRY_DSN_WEB, SENTRY_AUTH_TOKEN, SENTRY_ORG
ADR-024 logué dans docs/decisions.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
21 KiB
21 KiB
Log de décisions — Rubis Sur l'Ongle
Format ADR-light. Chaque décision : contexte, décision, rationale, alternatives, statut. Append-only — on n'efface jamais une décision passée, on en ajoute une nouvelle qui l'annule si besoin.
ADR-001 · Conversion 1 rubis = 10 minutes libérées
- Date : 2026-05-05
- Statut : ✅ Validée
- Contexte : la gamification "rubis gagnés" a besoin d'une conversion défendable et tangible.
- Décision : 1 rubis = 10 minutes libérées (≈ une relance manuelle complète).
- Rationale :
- Cohérent avec le bench 5h gagnées par semaine (8h → <3h après automatisation).
- 10 min est un chiffre rond mémorisable dans la copy.
- Borne basse défensible — beaucoup d'études parlent de 15-20 min, on est conservateur, donc incontestable.
- Permet ~30 rubis/mois pour une PME type, ~150 pour les power users — bon flywheel de gamification.
- Alternatives écartées :
- 1 rubis = 1 € de trésorerie débloquée → trop financier, rompt la promesse temps
- 1 rubis = 1 facture encaissée → trop discret pour les utilisateurs faible volume
- 1 rubis = 12 ou 15 minutes → moins mémorisable, pas plus défendable
- À reconfirmer : après MVP, via user testing — si les utilisateurs disent "10 min c'est trop" ou "trop peu", on ajustera.
ADR-002 · Direction de logo : A (gem facetté)
- Date : 2026-05-05
- Statut : ✅ Validée
- Contexte : 4 directions explorées (A gem facetté, B rubis brut, C wordmark, D ongle littéral).
- Décision : Direction A (gem facetté géométrique) en logo principal V1.
- Rationale :
- Iconique, scalable de 16 px à billboard
- Naturellement cohérent avec le ◆ utilisé dans les wireframes pour la gamification
- Marche en mono, knockout, filigrane
- Symbole autonome — fonctionne en favicon et app icon sans wordmark
- Alternatives :
- B (rubis brut) : trop joaillerie, risque Cartier
- D (ongle littéral) : polarisante, risque "manucure / cosmétique"
- C (wordmark) : retenue mais à monter en complément plus tard, pas en V1
- Suivi : à monter le wordmark direction C en V2 pour signature email, doc, contextes éditoriaux.
ADR-003 · Couleur primaire #9F1239
- Date : 2026-05-05
- Statut : ✅ Validée
- Contexte : choisir le rouge de la marque parmi plusieurs options.
- Décision :
#9F1239— rubis profond légèrement violacé. - Rationale : sophistiqué, distinctif, anti-Coca-Cola. Évoque le rubis "pigeon blood" (vivid red avec hints purple). Différencie radicalement des rouges tech génériques.
- Alternatives :
#B91C3C(rose 700) : plus punchy, plus tech#881337: plus dark, plus austère#BE123C: Tailwind rose-700, plus standard
- À surveiller : le contraste sur blanc pur peut être limite pour texte <14 px → utiliser
#771328(rubis profond) dans ces cas.
ADR-004 · Pas d'or accent
- Date : 2026-05-05
- Statut : ✅ Validée
- Contexte : la palette initiale incluait un or
#B89F6Bcomme accent rare. - Décision : abandon total de l'or dans la marque.
- Rationale : "fait très cheap" (Arthur). Risque d'évoquer banque ou bling-bling. La marque tient sans or — rubis + neutres chauds suffit.
- Conséquence : tout document antérieur mentionnant l'or est obsolète sur ce point. La règle devient rubis + neutres chauds, point.
ADR-005 · Typographies Bricolage Grotesque + Inter
- Date : 2026-05-05
- Statut : ✅ Validée
- Contexte : choisir une paire display + body, libre et accessible.
- Décision :
- Display : Bricolage Grotesque (500-800)
- Body : Inter (400-700)
- Les deux sur Google Fonts.
- Rationale :
- Bricolage apporte un caractère "français contemporain" qui différencie de la galaxie Stripe/Linear/Notion sans tomber dans le designer-flex
- Inter est le workhorse incontesté pour l'UI moderne, hyper-lisible, neutre
- Les deux sont gratuites, installables en 30 secondes
- Alternatives :
- General Sans : retenue mais plus neutre, moins ownable
- Söhne : payante, hors scope MVP
- Inter Display + Inter : trop monochrome, pas de personnalité
ADR-006 · Iconographie : Lucide
- Date : 2026-05-05
- Statut : ✅ Validée (tranche-claude)
- Contexte : choisir un set d'icônes pour l'UI.
- Décision : Lucide en regular weight (1.5px stroke).
- Rationale :
- 1 400+ icônes, couvre tous les use cases
- Licence MIT, pas de dépendance commerciale
- Cohérent avec le ton "précis pas froid"
- Bien intégré dans l'écosystème JS (React, Vue, Svelte, Vanilla)
- Alternatives :
- Phosphor Icons : très joli mais moins universel
- Heroicons : trop associé Tailwind
- Custom set : hors scope MVP, retour sur investissement faible
- Règle : le ◆ (gem) reste un SVG custom, jamais une icône Lucide.
ADR-007 · Mise en demeure : validation manuelle obligatoire
- Date : 2026-05-05
- Statut : ✅ Validée
- Contexte : l'étape J+20 (ou équivalent) du plan ferme est une mise en demeure. Doit-elle être envoyée automatiquement comme les autres relances ?
- Décision : non. Toute mise en demeure passe par une modale de confirmation où l'utilisateur clique explicitement "Envoyer la mise en demeure".
- Rationale :
- Risque légal : la mise en demeure a des conséquences juridiques
- Risque relationnel : peut casser une relation client durablement
- Le moment où l'utilisateur veut reprendre le contrôle, c'est exactement à cette étape
- UX : la modale rappelle le contexte légal (loi LME), affiche le contenu de l'email, et propose deux boutons distincts ("Envoyer maintenant" / "Reporter à plus tard").
- Anti-pattern à éviter : checkbox d'auto-envoi, même opt-in. Pas négociable.
ADR-008 · Banking V1 : check-in email à l'utilisateur
- Date : 2026-05-05
- Statut : ✅ Validée
- Contexte : sans intégration bancaire en V1, comment éviter de relancer un client qui a déjà payé hors plateforme ?
- Décision : avant chaque envoi de relance, Rubis envoie un email à l'utilisateur (pas au client) demandant : "Avez-vous été payé pour la facture X ?" avec deux boutons (Oui / Non).
- L'utilisateur configure la cadence et le timing (ex. T-2 jours avant la relance suivante)
- "Oui" → facture marquée encaissée, plan stoppé
- "Non" ou pas de réponse → la relance part comme prévu
- Rationale :
- Évite les relances fantômes sur factures déjà payées
- Garde l'utilisateur informé sans le surcharger
- Architecture compatible V2 banking : le check-in V1 devient simplement un fallback quand on ajoutera Bridge / Tink / GoCardless
- Contrainte architecturale : modèle d'événement abstrait
payment_status_checkqui peut être déclenché par email-réponse OU webhook bancaire. L'app ne doit pas savoir d'où vient le signal.
ADR-009 · SMS et multi-utilisateurs : V2, plans payants
- Date : 2026-05-05
- Statut : ✅ Validée
- Contexte : décider du périmètre V1 vs V2 sur deux features demandées.
- Décision :
- SMS : V2 uniquement, plan le plus cher seulement (Business)
- Multi-utilisateurs : V2 uniquement, plans payants seulement (Pro et Business, pas Free)
- Rationale :
- SMS = coût marginal direct (Twilio/OVH/etc.) → doit être monétisé sur le palier le plus rentable
- Multi-utilisateurs ajoute de la complexité (rôles, invitations, audit) → out of MVP, et c'est un argument upgrade naturel pour les plans payants
- Conséquence sur l'archi :
- DB structurée multi-tenant dès V1 (
organization→users→invoices) pour ne pas refactorer - Modèle d'envoi message abstrait (un message peut être email V1, ou SMS/email V2)
- DB structurée multi-tenant dès V1 (
ADR-010 · Pas d'intégration banking en V1
- Date : 2026-05-05
- Statut : ✅ Validée
- Contexte : l'intégration banking (Bridge / Tink / Powens) automatiserait la réconciliation des paiements.
- Décision : pas en V1. Remplacée par check-in email (ADR-008).
- Rationale :
- Coût d'implémentation important (DSP2, agréments, gestion des erreurs banking)
- Impose de toucher aux données financières du client → friction RGPD/sécurité
- Le check-in email résout 80 % du problème pour 10 % du coût
- À prévoir en V1 : architecture événementielle qui rendra l'intégration V2 mécanique (cf. ADR-008).
ADR-011 · "3 clics maximum" est une règle de design, pas un compteur
- Date : 2026-05-05
- Statut : ✅ Validée (clarification)
- Contexte : la promesse "3 clics" est dans le pitch et la landing. Doit-on la respecter strictement à chaque parcours ?
- Décision : c'est une règle de design (l'utilisateur doit faire le minimum d'actions possible) plus qu'un compteur strict. Si le plan par défaut est bien configuré, 2 clics suffisent. Si le contexte demande plus, on peut aller à 4.
- Rationale : "3 clics" est une promesse marketing claire et mémorable. Le vrai principe sous-jacent est "minimum d'actions utilisateur".
- Application :
- Le storyboard hero (drop → valider → récompense) doit absolument tenir en 3 clics ou moins
- Les flows secondaires (changer un plan, archiver) peuvent dépasser sans casser la promesse
- On ne sacrifie pas la clarté pour économiser un clic
ADR-013 · Tagline V1 « Vos factures relancées toutes seules pendant que vous travaillez »
- Date : 2026-05-05
- Statut : ✅ Validée pour la landing V1
- Contexte : 5 taglines candidates explorées dans
/docs/munitions-marketing.md(douces vs polarisantes vs factuelles). - Décision : retenir « Vos factures relancées toutes seules pendant que vous travaillez. »
- Rationale :
- Image concrète et duale : vous travaillez / l'app travaille — facile à se représenter
- Respecte les 4 principes de marque (chaleureuse, action, précise, rubis-centric implicite)
- Promet le bénéfice (libération de temps) plutôt que le problème (impayés) — différencie de tous les concurrents
- Tient en signature email, en hero de landing, en pitch oral
- Alternatives écartées :
- « Ne mendiez plus jamais ce qu'on vous doit » : trop polarisante pour la landing. Garde en réserve pour campagnes ads ciblées.
- « 3 clics. Plus jamais de relance manuelle » : trop factuelle, pas d'émotion.
- « Arrêtez de courir après votre argent » : impératif négatif, casse le ton chaleureux.
- « L'argent, c'est sérieux. La relance, ça ne devrait plus l'être » : trop intellectualisée.
- À reconfirmer : après MVP, A/B testable sur Google Ads contre la version polarisante.
ADR-014 · Stack technique
- Date : 2026-05-05
- Statut : ✅ Validée
- Contexte : choix du stack initial pour démarrer le développement de Rubis. Bloquant pour les ADR suivants (domain model, repo layout, etc.).
- Décision :
- Backend : AdonisJS v7 (TypeScript, MVC, Lucid ORM, auth & jobs intégrés)
- Frontend : React + Vite + TanStack Router + TanStack Query
- Base de données : PostgreSQL
- Hosting : cluster Proxmox personnel + K3s (déjà en place pour la landing)
- Rationale :
- AdonisJS v7 : "batteries included" en TS first — Lucid pour le SQL relationnel, Auth pour les sessions/tokens, Bouncer pour les permissions, Bull pour les jobs, Mailer pour l'email. Évite l'assemblage Express/Fastify + 12 libs.
- React + Vite : DX moderne, build rapide, écosystème massif. Maturité éprouvée pour un SaaS B2B.
- TanStack Router : routing client-side type-safe avec search params réactifs (idéal pour les filtres de la liste de factures). Pas couplé à un framework SSR.
- TanStack Query : cache + invalidation + retry + optimistic updates pour le state serveur. Évite Redux pour gérer du data API.
- PostgreSQL : transactions ACID indispensables pour la facturation, support JSON pour les payloads OCR, full-text search natif si besoin.
- Proxmox + K3s : maîtrise totale du runtime, coût marginal nul (infrastructure existante), pas de vendor lock-in. Le pipeline Gitea CI → registry → K3s rollout est déjà rodé sur la landing.
- Conséquences architecturales :
- API REST séparée du SPA (pas Inertia.js) — TanStack Router signifie que le client gère son propre routing. CORS et type-sharing entre back et front à organiser proprement.
- TypeScript end-to-end rend possible un dossier de types partagés (ex.
packages/shared/) ou la génération auto via OpenAPI/Zod. - Le build front (Vite) produit des assets statiques — soit servis par AdonisJS (
/public/build/), soit par nginx en sidecar du pod, soit déployés en parallèle.
- Alternatives écartées :
- Next.js / Remix : trop SSR-centric pour un SaaS transactionnel. Le backend des frameworks meta-React reste plus faible que ce qu'offre Adonis.
- Express ou Fastify + ORM (Prisma/Drizzle) : assemblage de 12 libs pour atteindre ce qu'Adonis livre out-of-the-box.
- MongoDB / NoSQL : pas adapté aux relations facture-client-relance ni aux transactions financières.
- Vercel / Render / Fly.io : coûts récurrents évitables — Proxmox déjà en place et payé.
- Décisions à formaliser dans la foulée : repo layout (mono vs split), hébergement Postgres, auth flow (cookie vs token), file storage des PDF, domain model.
ADR-015 · Repo layout : monorepo (apps/api + apps/web)
- Date : 2026-05-05
- Statut : ✅ Validée
- Décision : un seul repo Git, deux applications dans
apps/api/(AdonisJS) etapps/web/(React/Vite), un dossierpackages/shared/pour les types TS partagés. Workspaces gérés en pnpm. - Rationale :
- Le type-sharing entre API et SPA est gratuit (un import depuis
@rubis/shared) - Une seule release coordonnée → pas de problèmes de version drift entre API et SPA
- CI/CD unique, scripts npm racine
- Solo dev TS = monorepo natural fit
- Le type-sharing entre API et SPA est gratuit (un import depuis
- Alternatives écartées :
- Deux repos séparés : friction sur le type-sharing, releases à coordonner manuellement
- Adonis monolithe avec front intégré (Inertia) : aurait écarté TanStack Router (couplage Adonis routing). Adopté seulement si on retire TanStack Router de la stack.
ADR-016 · PostgreSQL : LXC Proxmox existant
- Date : 2026-05-05
- Statut : ✅ Validée
- Décision : utiliser le serveur PostgreSQL déjà provisionné dans le LXC Proxmox d'Arthur. Créer une base
rubisdédiée + un userrubis_useravec les permissions nécessaires. - Rationale :
- Infrastructure existante, zéro coût d'infra additionnel
- Backups Proxmox existants (snapshots LXC) couvrent la base
- Performance native (pas la couche K3s), latence faible avec le cluster K3s sur le même réseau
- Hors cluster K3s = isolement des changements applicatifs (rollouts ne touchent jamais la DB)
- À mettre en place :
- Créer une
database rubis+ user dédié + grants minimum (CREATE/SELECT/INSERT/UPDATE/DELETE sur les tables de la base) - Service K3s ou ExternalName pour exposer la connexion PG aux pods API
- Sauvegarde dump PG quotidienne dans MinIO (script cron côté LXC)
- Créer une
ADR-017 · Auth : access tokens (Bearer)
- Date : 2026-05-05
- Statut : ✅ Validée
- Décision : authentification via access tokens stateless (Bearer header) via
@adonisjs/authv7. Pas de session cookie côté API. - Rationale :
- Architecture API propre, agnostique du client (web V1, mobile V2, intégrations partenaires V3+ supportées sans refactoring)
- Tokens stockés en base (
auth_access_tokensAdonis) → révocation possible côté admin - Permet des abilities/scopes par token (utile en V2 pour donner un token "lecture seule" au comptable)
- Implémentation :
- API : middleware
authAdonis sur les routes protégées, sortieBearer tokenà l'inscription/login - SPA : token stocké en mémoire (variable de module/closure) + refresh token en cookie httpOnly pour persistance après reload, pour limiter le risque XSS
- TTL : access token 30 min, refresh token 30 jours (à valider au moment de l'implémentation)
- API : middleware
- Alternatives écartées :
- Session cookies @adonisjs/auth : plus simple en V1 mais oblige à tout refaire au moment d'ajouter une API mobile/tiers
- Tokens en localStorage seul : exposé XSS (lecture par n'importe quel script tiers), pas safe pour des données financières
ADR-018 · File storage : MinIO Proxmox existant
- Date : 2026-05-05
- Statut : ✅ Validée
- Décision : utiliser le MinIO déjà provisionné sur l'infra Proxmox d'Arthur pour stocker les PDF de factures et les pièces jointes.
- Rationale :
- Infrastructure existante, zéro coût additionnel
- API S3-compatible → utilise n'importe quelle lib AWS SDK
- Migration future vers cloud S3 (R2/B2) triviale (mêmes appels)
- Pre-signed URLs natives MinIO pour les téléchargements client-side sécurisés
- À mettre en place :
- Bucket
rubis-invoices(PDF + images de factures) - Bucket
rubis-attachments(pièces jointes utilisateurs : signatures, logos) - Credentials Access Key/Secret dédiés avec permissions limitées à ces 2 buckets
- Politique de retention : pas de purge auto en V1 (les factures sont des documents légaux)
- Bucket
- Alternatives écartées :
- Local pod + PVC : ne scale pas si plusieurs replicas, backup non trivial
- Cloudflare R2 / Backblaze B2 : sortie de l'infra perso pour rien — MinIO existe déjà
ADR-024 · Error monitoring : Sentry SaaS (free tier)
- Date : 2026-05-08
- Statut : ✅ Validée
- Décision : utiliser Sentry SaaS (free tier) pour le monitoring d'erreurs et le replay session, avec 2 projets distincts
rubis-api(Node) etrubis-web(React). - Rationale :
- Free tier suffisant pour V1 (5 K events/mois, 50 replays/mois) — on calibre les sample rates pour rester dedans.
- Stack standard, vaste écosystème d'intégrations (AdonisJS, React, Vite plugin).
- Source maps uploadées au build par
@sentry/vite-pluginpuis SUPPRIMÉES dudist/final (filesToDeleteAfterUpload) → stack traces désobfusquées dans Sentry, mais pas exposées publiquement par nginx. - Releases trackées au sha git :
APP_VERSION=${{ github.sha }}injecté en build-arg (web) et runtime env (api). Une régression ↔ un commit, sans ambiguïté.
- Sample rates :
- traces : 10 % prod, 100 % dev (debug)
- profiles : 100 % (sampled par traces)
- replay session : 0 % (pas de replay sans erreur — économie quota)
- replay sur erreur : 100 % (capture les 30 s précédant le crash, debug post-mortem)
- Privacy / PII :
- User context Sentry :
user.idUUID seulement, pas d'email ni nom (minimisation PII). - Replay :
maskAllText: true+blockAllMedia: true(sécurité par défaut, on relâche après si besoin). - Tags Sentry : on log le pattern de route (
/api/v1/checkin/:token/paid) pas l'URL réelle, sinon les codes OAuth (?code=...) et les tokens de check-in fuiteraient dans des champs indexés. - 4xx attendues filtrées dans
beforeSendcôté API (validation, auth invalide → bruit, pas une erreur).
- User context Sentry :
- Implémentation :
- API :
apps/api/start/sentry.tschargé en 1er dansbin/server.ts,Sentry.captureExceptiondansapp/exceptions/handler.ts:reportpour les 5xx. - Web :
apps/web/src/lib/sentry.tschargé en 1er dansmain.tsx,Sentry.ErrorBoundaryautour de l'app,Sentry.setUserdansauthStore.setSession/clear. - DSN web : variable
VITE_SENTRY_DSN_WEB(publique by-design — bake-able dans le bundle). - DSN api : variable
SENTRY_DSN_API(env runtime K3s). - Auth token CI :
SENTRY_AUTH_TOKENsecret Gitea, lu uniquement au build pour upload des sourcemaps. Jamais en runtime.
- API :
- Alternatives écartées :
- Self-hosted Sentry : opérationnellement coûteux pour V1 solo founder. À reconsidérer si quota free tier explosé.
- Datadog / New Relic / Bugsnag : cher au-delà du free tier, overkill pour la taille V1.
- Pas de monitoring : insoutenable en prod B2B SaaS — un bug silencieux peut perdre un client en 24h.
Décisions à venir (en attente)
| # | Sujet | Pourquoi en attente |
|---|---|---|
| 019 | Domain model / DB schema (entités, relations, index) | À écrire — débloqué par 014-018, prochaine étape technique |
| 020 | Provider OCR (Mindee, Document AI, Textract, Tesseract self-hosted) | À benchmarker (coût + qualité sur factures FR) |
| 021 | Provider email outbound (Resend, Postmark, SendGrid, AWS SES) | À benchmarker (deliverability FR + prix au volume) |
| 022 | Pricing exact (Free 5 factures ? Pro 19 € ? Business 49 €) | À tester avant figer |
| 023 | Endpoint waitlist (Resend / Formspree / Tally / API Adonis perso) | Choix simple au moment du push de la landing |
Comment ajouter une décision
## ADR-XXX · Titre court
- **Date** : YYYY-MM-DD
- **Statut** : ✅ Validée | 🟡 En cours | ❌ Annulée par ADR-YYY
- **Contexte** : la situation qui appelle la décision
- **Décision** : ce qu'on fait
- **Rationale** : pourquoi (avec preuves/data si possible)
- **Alternatives écartées** : ce qu'on n'a pas fait, et pourquoi
- **Conséquences** : impact sur l'archi, le produit, la marque
Quand une décision est remise en cause, on ajoute un ADR qui annule l'ancien (jamais éditer rétroactivement). C'est ça qui rend le log fiable comme mémoire d'équipe.