docs(adr): registre des decisions d'architecture (9 ADR) #43
10 changed files with 259 additions and 0 deletions
23
docs/adr/0001-php-from-scratch-sans-composer.md
Normal file
23
docs/adr/0001-php-from-scratch-sans-composer.md
Normal file
|
|
@ -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.
|
||||
22
docs/adr/0002-back-office-mvc-rendu-serveur.md
Normal file
22
docs/adr/0002-back-office-mvc-rendu-serveur.md
Normal file
|
|
@ -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`.
|
||||
25
docs/adr/0003-stock-pourcentage-dispo-calculee.md
Normal file
25
docs/adr/0003-stock-pourcentage-dispo-calculee.md
Normal file
|
|
@ -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/`.
|
||||
25
docs/adr/0004-pin-action-sensible-audit.md
Normal file
25
docs/adr/0004-pin-action-sensible-audit.md
Normal file
|
|
@ -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.
|
||||
23
docs/adr/0005-throttle-pin-separe-du-login.md
Normal file
23
docs/adr/0005-throttle-pin-separe-du-login.md
Normal file
|
|
@ -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.
|
||||
25
docs/adr/0006-http-409-conflit-422-validation.md
Normal file
25
docs/adr/0006-http-409-conflit-422-validation.md
Normal file
|
|
@ -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.
|
||||
24
docs/adr/0007-rgpd-anonymisation-tombstone.md
Normal file
24
docs/adr/0007-rgpd-anonymisation-tombstone.md
Normal file
|
|
@ -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-<id>@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).
|
||||
26
docs/adr/0008-makefile-vers-compose-migrate.md
Normal file
26
docs/adr/0008-makefile-vers-compose-migrate.md
Normal file
|
|
@ -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.
|
||||
29
docs/adr/0009-compose-standalone-et-prod-gitignore.md
Normal file
29
docs/adr/0009-compose-standalone-et-prod-gitignore.md
Normal file
|
|
@ -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`).
|
||||
37
docs/adr/README.md
Normal file
37
docs/adr/README.md
Normal file
|
|
@ -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.
|
||||
```
|
||||
Loading…
Add table
Reference in a new issue