release: dev -> main v0.2.0 #93

Merged
Corentin merged 96 commits from dev into main 2026-06-23 10:09:58 +02:00
10 changed files with 259 additions and 0 deletions
Showing only changes of commit 33e6901e7c - Show all commits

View 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.

View 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`.

View 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/`.

View 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.

View 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.

View 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.

View 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).

View 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.

View 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
View 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.
```