From 035129d7befc1befc475ec2852282b8530286ab4 Mon Sep 17 00:00:00 2001 From: Imugiii Date: Wed, 17 Jun 2026 13:50:27 +0000 Subject: [PATCH] docs(adr): registre des decisions d'architecture (9 ADR) docs/adr/ : une fiche courte (contexte / decision / consequences) par decision structurante, + un index et un modele de fiche. 0001 PHP from scratch sans Composer ; 0002 back-office MVC rendu serveur ; 0003 stock en % + dispo calculee RG-T21 ; 0004 PIN d'action sensible + audit en transaction ; 0005 throttle PIN separe du login (RG-T22) ; 0006 HTTP 409 vs 422 ; 0007 effacement RGPD par anonymisation (tombstone) ; 0008 Makefile -> docker compose (wakdo-migrate) ; 0009 compose standalone + prod gitignore. Deuxieme PR du jeu de documentation. Resout le renvoi docs/adr/ d'ARCHITECTURE.md. --- .../0001-php-from-scratch-sans-composer.md | 23 ++++++++++++ .../adr/0002-back-office-mvc-rendu-serveur.md | 22 +++++++++++ .../0003-stock-pourcentage-dispo-calculee.md | 25 +++++++++++++ docs/adr/0004-pin-action-sensible-audit.md | 25 +++++++++++++ docs/adr/0005-throttle-pin-separe-du-login.md | 23 ++++++++++++ .../0006-http-409-conflit-422-validation.md | 25 +++++++++++++ docs/adr/0007-rgpd-anonymisation-tombstone.md | 24 ++++++++++++ .../adr/0008-makefile-vers-compose-migrate.md | 26 +++++++++++++ ...09-compose-standalone-et-prod-gitignore.md | 29 +++++++++++++++ docs/adr/README.md | 37 +++++++++++++++++++ 10 files changed, 259 insertions(+) create mode 100644 docs/adr/0001-php-from-scratch-sans-composer.md create mode 100644 docs/adr/0002-back-office-mvc-rendu-serveur.md create mode 100644 docs/adr/0003-stock-pourcentage-dispo-calculee.md create mode 100644 docs/adr/0004-pin-action-sensible-audit.md create mode 100644 docs/adr/0005-throttle-pin-separe-du-login.md create mode 100644 docs/adr/0006-http-409-conflit-422-validation.md create mode 100644 docs/adr/0007-rgpd-anonymisation-tombstone.md create mode 100644 docs/adr/0008-makefile-vers-compose-migrate.md create mode 100644 docs/adr/0009-compose-standalone-et-prod-gitignore.md create mode 100644 docs/adr/README.md diff --git a/docs/adr/0001-php-from-scratch-sans-composer.md b/docs/adr/0001-php-from-scratch-sans-composer.md new file mode 100644 index 0000000..d4edb2d --- /dev/null +++ b/docs/adr/0001-php-from-scratch-sans-composer.md @@ -0,0 +1,23 @@ +# ADR-0001 — PHP from scratch, sans framework ni Composer + +- Statut : Accepte +- Date : 2026-04-23 + +## Contexte +Certification RNCP (Titre Developpeur Web, option DevOps). L'objectif pedagogique est +de demontrer la maitrise des fondamentaux (routage, PDO, sessions, securite) plutot que +la configuration d'un framework. Options : Symfony/Laravel ; micro-framework (Slim) ; +from scratch. + +## Decision +Application PHP 8.3 ecrite **from scratch** : routeur, autoloader PSR-4 manuel +(`spl_autoload_register`), couche `Database` sur PDO, le tout **sans Composer**. Les +outils de dev (PHPUnit, PHPStan) sont utilises via leurs **`.phar` autonomes**. + +## Consequences +- (+) Chaque mecanisme (routage, auth, RBAC, requetes preparees) est explicite et + defendable a l'oral ; pas de magie de framework. +- (+) Surface de dependances minimale (moins de supply-chain a auditer). +- (-) Du code d'infrastructure a ecrire et tester soi-meme (Core, Auth). +- CI sans Composer : les `.phar` (phpunit, phpstan) sont epingles/telecharges. + Voir `docs/PROJECT_CONTEXT.md` section 6. diff --git a/docs/adr/0002-back-office-mvc-rendu-serveur.md b/docs/adr/0002-back-office-mvc-rendu-serveur.md new file mode 100644 index 0000000..83f1bc9 --- /dev/null +++ b/docs/adr/0002-back-office-mvc-rendu-serveur.md @@ -0,0 +1,22 @@ +# ADR-0002 — Back-office en MVC rendu serveur (pas de SPA) + +- Statut : Accepte +- Date : 2026-06-15 + +## Contexte +Le back-office (login, CRUD catalogue, stock, users, RBAC, stats) doit etre construit. +Options : SPA JS consommant une API JSON ; pages rendues serveur (MVC PHP) ; hybride. +La borne client, elle, est deja un front statique distinct (Bloc 1). + +## Decision +Le back-office est en **MVC rendu serveur** : formulaires POST + redirections, vues PHP +injectees dans un layout commun. L'API REST (`/api/*`) reste interne, consommee par la +borne. Login = vue PHP, pas un endpoint JSON. + +## Consequences +- (+) CSRF, sessions, garde de permission et echappement de sortie se branchent + naturellement sur chaque page ; demontre le MVC sans build front. +- (+) Pas de duplication d'etat client/serveur pour l'admin. +- (-) Interactions riches (matrice RBAC, editeur recette) gerees en JS vanilla cible, + CSP-safe (champs caches / cases scalaires), sans framework front. +- Controleurs non-`final` (seam de test) ; vues sous `src/app/Views/admin`. diff --git a/docs/adr/0003-stock-pourcentage-dispo-calculee.md b/docs/adr/0003-stock-pourcentage-dispo-calculee.md new file mode 100644 index 0000000..4605c22 --- /dev/null +++ b/docs/adr/0003-stock-pourcentage-dispo-calculee.md @@ -0,0 +1,25 @@ +# ADR-0003 — Stock en pourcentage + disponibilite produit calculee (RG-T21) + +- Statut : Accepte +- Date : 2026-06-12 + +## Contexte +Modeliser le stock des ingredients et la commandabilite des produits. Un stock en +quantites absolues seules rend les seuils d'alerte arbitraires d'un ingredient a +l'autre ; et marquer la disponibilite produit "en dur" exige une cascade a maintenir +a chaque mouvement de stock. + +## Decision +Stock ancre sur une **`stock_capacity`** (reference 100%, `CHECK > 0`) ; `stock_pct` et +les 3 bandes (normal / alerte / critique) sont **calcules**, pas stockes. La +**disponibilite produit (RG-T21)** est derivee : commandable si `is_available = 1` ET +chaque ingredient non retirable est au-dessus de la bande critique. Aucune colonne +stockee, aucune cascade. + +## Consequences +- (+) Seuils homogenes (en %) ; un reappro au-dessus du critique rend le produit + commandable de lui-meme, sans ecriture. +- (+) `stock_quantity` signe (survente assumee, remontee manager) : le systeme ne bloque + pas une commande sur une lecture de stock. +- (-) Le calcul de dispo se fait a la lecture (jointure composition) ; borne par requete. +- Source unique de la derivation : `IngredientRepository::stockBand`. Voir `docs/merise/`. diff --git a/docs/adr/0004-pin-action-sensible-audit.md b/docs/adr/0004-pin-action-sensible-audit.md new file mode 100644 index 0000000..80b3060 --- /dev/null +++ b/docs/adr/0004-pin-action-sensible-audit.md @@ -0,0 +1,25 @@ +# ADR-0004 — PIN d'action sensible (equipier) + audit dans la meme transaction + +- Statut : Accepte +- Date : 2026-06-15 + +## Contexte +Les postes back-office sont partages (session ouverte au comptoir). Pour les operations +sensibles (annulation, changement prix/TVA, suppressions, inventaire, gestion +utilisateur, RBAC, effacement PII), il faut imputer l'acte a une personne, pas a la +session partagee. + +## Decision +Modele **identifiant equipier + PIN** : l'operation sensible exige email + PIN, verifies +contre `user.pin_hash` (argon2id). Le `user_id` ainsi resolu est l'**acteur** ecrit dans +`audit_log` (RG-T14), dans la **meme transaction** que l'effet (RG-T08). Le set sensible +est defini par RG-T13. Les operations de stock tracent via `stock_movement.user_id` +(pas de double-journal). + +## Consequences +- (+) Imputabilite reelle sur poste partage ; trace immuable et atomique (pas d'effet + sans audit, ni l'inverse). +- (+) Le PIN n'identifie pas la session : un manager peut autoriser sur le poste d'un + autre sans relog. +- (-) Surface d'attaque PIN (4 chiffres) -> necessite un throttle dedie (voir ADR-0005). +- Brique : `App\Auth\PinVerifier`. Regle : `docs/merise/mlt.md` RG-T13/RG-T14. diff --git a/docs/adr/0005-throttle-pin-separe-du-login.md b/docs/adr/0005-throttle-pin-separe-du-login.md new file mode 100644 index 0000000..7de7f22 --- /dev/null +++ b/docs/adr/0005-throttle-pin-separe-du-login.md @@ -0,0 +1,23 @@ +# ADR-0005 — Throttle du PIN separe des compteurs de connexion (RG-T22) + +- Statut : Accepte +- Date : 2026-06-15 + +## Contexte +Le PIN d'action sensible (ADR-0004) est court (4 chiffres) : il faut limiter le +brute-force. Question : reutiliser les compteurs de login (`user.lockout_until` / +`login_throttle`) ou un compteur dedie ? Et sur quelle dimension compter ? + +## Decision +Table **`pin_throttle`** dediee, **separee** des compteurs de connexion. La dimension +est l'**utilisateur agissant** (la session authentifiee qui soumet le PIN), pas l'email +cible (contournable par rotation) ni l'IP (collateral sur poste partage). Backoff +degressif, bornes propres plus permissives que le login. Verrou evalue AVANT la +verification ; sous verrou actif, pas de nouvelle ligne `pin.failed` (anti-amplification). + +## Consequences +- (+) Spammer le PIN d'une victime ne verrouille pas sa CONNEXION (pas d'escalade DoS + sur une surface plus sensible). +- (+) Detection : un pic de `pin.failed` reste alertable. +- (-) Un compteur de plus a purger (cron, comme `login_throttle`). +- Brique : `App\Auth\PinThrottle`. Regle : RG-T22. Cf. ADR-0004. diff --git a/docs/adr/0006-http-409-conflit-422-validation.md b/docs/adr/0006-http-409-conflit-422-validation.md new file mode 100644 index 0000000..aac79e5 --- /dev/null +++ b/docs/adr/0006-http-409-conflit-422-validation.md @@ -0,0 +1,25 @@ +# ADR-0006 — HTTP 409 (conflit) vs 422 (validation) + +- Statut : Accepte +- Date : 2026-06-17 + +## Contexte +Les controleurs renvoyaient 422 a la fois pour une validation qui echoue ET pour un +conflit d'etat (unicite, suppression bloquee par FK RESTRICT). Le contrat documente +(`byan-api.md`) attendait 409 pour les conflits. Derive a corriger. + +## Decision +Convention harmonisee sur tous les controleurs : +- **422** : requete bien formee mais **semantiquement invalide** (validation serveur, + RG-T18) ; +- **409** : **conflit d'etat** (violation d'unicite SQLSTATE 23000, hard-delete bloque + par FK RESTRICT) ; +- **403** : CSRF invalide ou permission manquante ; **404** : ressource introuvable. + +## Consequences +- (+) Statuts semantiquement justes (RFC 9110), testables, coherents entre Category / + Product / Menu / Ingredient / User / Role. +- (+) Aligne le code sur le contrat d'API documente. +- (-) Pages rendues serveur : un 200-avec-erreurs "marcherait" visuellement, mais le + statut correct est verrouille par les tests (un oubli = test rouge). +- Remediation : PR #33 (Category/Product/Menu) ; les controleurs suivants naissent en 409. diff --git a/docs/adr/0007-rgpd-anonymisation-tombstone.md b/docs/adr/0007-rgpd-anonymisation-tombstone.md new file mode 100644 index 0000000..7716606 --- /dev/null +++ b/docs/adr/0007-rgpd-anonymisation-tombstone.md @@ -0,0 +1,24 @@ +# ADR-0007 — Effacement RGPD par anonymisation (tombstone), pas DELETE + +- Statut : Accepte +- Date : 2026-06-17 + +## Contexte +Le droit a l'effacement (RGPD, Cr 3.d) s'applique aux comptes back-office. Un `DELETE` +dur casserait l'integrite referentielle (FK entrantes depuis `stock_movement.user_id`, +`customer_order.acting_user_id`, `audit_log.actor_user_id`) et effacerait la trace +d'imputabilite des actes passes. + +## Decision +**Anonymisation** (mlt 10.5), pas suppression : en une transaction, vider la PII de la +ligne `user` (email -> `anon-@wakdo.invalid` RFC 2606, prenom/nom vides, hash vide, +PIN/reset NULL), poser `anonymized_at`, `is_active = 0`. La ligne **persiste** comme +tombstone. Idempotent (clause `anonymized_at IS NULL`). Trace : `audit_log` +`user.erase_pii`. + +## Consequences +- (+) FK preservees ; les actes passes restent imputables a un principal anonymise + (qui-en-tant-qu-id), sans PII. +- (+) Email unique conserve, non identifiant. +- (-) La ligne reste en base (tombstone) : a documenter dans le registre de traitement. +- Garde-fou : interdit d'anonymiser le dernier admin actif / soi-meme (anti-lockout). diff --git a/docs/adr/0008-makefile-vers-compose-migrate.md b/docs/adr/0008-makefile-vers-compose-migrate.md new file mode 100644 index 0000000..15eabe7 --- /dev/null +++ b/docs/adr/0008-makefile-vers-compose-migrate.md @@ -0,0 +1,26 @@ +# ADR-0008 — Du Makefile a `docker compose up` (service wakdo-migrate) + +- Statut : Accepte +- Date : 2026-06-17 + +## Contexte +Le critere Cr 7.c.4 demande de lancer la stack complete en une commande. C'etait +`make init`. Mais le Makefile portait surtout des cibles mortes/trompeuses +(`test`/`lint` annoncaient "pas implemente" alors que les tests tournent) ; sa seule +cible porteuse, `init`, existait parce que `docker compose up` seul n'applique pas les +migrations. Le critere parle d'un **resultat**, pas de `make`. + +## Decision +Migration + seed deplaces **dans la stack** : un service one-shot **`wakdo-migrate`** +(image mariadb, `db/migrate-container.sh` par le reseau) applique +`db/migrations/*.sql` (suivi `schema_migrations`) puis `db/seeds/*.sql` (suivi +`seeds_applied`), idempotents. `wakdo-app`/`wakdo-web` `depends_on: +service_completed_successfully`. **Makefile supprime.** `docker compose up` devient +l'unique commande. + +## Consequences +- (+) Commande universelle, sans dependance a l'outil `make` sur l'hote. +- (+) Comportement = doc (l'ancien `make init` ne seedait meme pas). +- (-) Migrations/seed evalues a chaque `up` (cout negligeable, suivi -> re-run sans effet). +- (-) Base **deja seedee** avant le suivi : back-filler `seeds_applied` avant le 1er up. +- `db/migrate.sh` (hote) conserve pour l'usage manuel. Detail : journal 2026-06-17. diff --git a/docs/adr/0009-compose-standalone-et-prod-gitignore.md b/docs/adr/0009-compose-standalone-et-prod-gitignore.md new file mode 100644 index 0000000..5c3afe5 --- /dev/null +++ b/docs/adr/0009-compose-standalone-et-prod-gitignore.md @@ -0,0 +1,29 @@ +# ADR-0009 — docker-compose.yml standalone + docker-compose.prod.yml gitignore + +- Statut : Accepte +- Date : 2026-06-17 + +## Contexte +Le `docker-compose.yml` versionne supposait un reverse proxy Traefik (reseau externe, +labels, aucun port hote) : `docker compose up` echouait pour quiconque sans Traefik +(jury, contributeur, tests E2E). Options envisagees : overlay `-f` (base + prod fusionnes +via `!reset`) ; un seul fichier parametre ; deux fichiers complets independants. + +## Decision +Deux fichiers **complets et independants** (pas d'overlay) : +- **`docker-compose.yml`** (versionne) : standalone, `wakdo-web` publie + `${HTTP_PORT:-8080}:80`, reseau interne seul, sans Traefik. `docker compose up` tourne + partout, facon app open-source self-hostable. +- **`docker-compose.prod.yml`** : **gitignore**, propre a chaque hote derriere un proxy + (meme stack + reseau externe + labels Traefik, sans port). `docker compose -f + docker-compose.prod.yml up -d`. + +Renommage `TRAEFIK_DOMAIN_*` -> `APP_HOST_*` (ce sont des `ServerName` de vhosts, pas du +Traefik). `.env.example` local-first. + +## Consequences +- (+) `docker compose up` marche en local sans configuration ; le repo ne porte aucune + hypothese d'infra. +- (+) Le critere Cr 7.c.4 tient avec un fichier que tout le monde peut lancer. +- (-) Duplication entre les deux fichiers (assumee : clarte > DRY pour l'infra). +- (-) Le serveur maintient son propre fichier prod (comme `.env`). diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..cab4e3d --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,37 @@ +# Registre des decisions d'architecture (ADR) + +Une fiche courte par decision structurante : **contexte**, **decision**, **consequences**. +Format inspire des Architecture Decision Records (M. Nygard). Les ADR sont immuables : +une decision revisee donne une nouvelle fiche qui *supersede* l'ancienne (statut mis a jour). + +**Auteur : BYAN** (formalisation ; arbitrage et validation par l'auteur). + +| # | Decision | Statut | +|---|---|---| +| [0001](0001-php-from-scratch-sans-composer.md) | PHP from scratch, sans framework ni Composer | Accepte | +| [0002](0002-back-office-mvc-rendu-serveur.md) | Back-office en MVC rendu serveur (pas de SPA) | Accepte | +| [0003](0003-stock-pourcentage-dispo-calculee.md) | Stock en pourcentage + disponibilite produit calculee (RG-T21) | Accepte | +| [0004](0004-pin-action-sensible-audit.md) | PIN d'action sensible (equipier) + audit dans la meme transaction | Accepte | +| [0005](0005-throttle-pin-separe-du-login.md) | Throttle du PIN separe des compteurs de connexion (RG-T22) | Accepte | +| [0006](0006-http-409-conflit-422-validation.md) | HTTP 409 (conflit) vs 422 (validation) | Accepte | +| [0007](0007-rgpd-anonymisation-tombstone.md) | Effacement RGPD par anonymisation (tombstone), pas DELETE | Accepte | +| [0008](0008-makefile-vers-compose-migrate.md) | Du Makefile a `docker compose up` (service wakdo-migrate) | Accepte | +| [0009](0009-compose-standalone-et-prod-gitignore.md) | docker-compose.yml standalone + docker-compose.prod.yml gitignore | Accepte | + +## Modele de fiche + +``` +# ADR-NNNN — Titre + +- Statut : Propose | Accepte | Supersede par ADR-XXXX +- Date : AAAA-MM-JJ + +## Contexte +Le probleme, les contraintes, les options envisagees. + +## Decision +Le choix retenu, en une ou deux phrases nettes. + +## Consequences +Ce que ca implique (positif et negatif), et les regles/fichiers concernes. +```