2 Commits
Author SHA1 Message Date
Claude ef2868b980 Rendre le déploiement Docker correct, et la RLS réellement active
Trois problèmes, dont un grave, trouvés en corrigeant un échec de déploiement.

**Le compose connectait l'application en superutilisateur PostgreSQL.** Un
superutilisateur contourne toute politique de sécurité au niveau ligne, y
compris déclarée en FORCE : la seconde couche d'isolation était présente en
base et absente des faits. Le README l'interdisait déjà noir sur blanc ; le
chemin de déploiement que nous livrons faisait exactement l'inverse. Un script
d'initialisation crée désormais un rôle `planflow_app` NOSUPERUSER NOBYPASSRLS,
propriétaire de la base — il lui faut ce droit pour migrer, et les politiques
sont en FORCE précisément pour s'appliquer aussi au propriétaire. Mesuré : en
superutilisateur, deux lignes visibles sans compte courant ; avec le rôle
dédié, zéro.

Basculer la base de développement sur ce même rôle a révélé le défaut que le
superutilisateur masquait : `resolveSession` lisait `Membership`, table filtrée
par compte, sans périmètre. Avec la RLS active, plus personne ne pouvait se
connecter. La résolution passe maintenant par une porte étroite — une politique
qui n'ouvre que les lignes dont l'utilisateur est titulaire, sous `app.user_id`
— le temps de trouver le compte, puis repasse par le périmètre ordinaire. La
suite de tests traverse enfin la RLS au lieu de la contourner.

**Les pièces du dossier salarié n'avaient aucun volume.** Elles étaient écrites
dans la couche du conteneur et disparaissaient au premier redéploiement. Une
pièce d'identité perdue ne se reconstitue pas.

Le reste répond à la demande : Postgres préconfiguré — la base n'étant ni
publiée ni attachée au réseau du proxy, ce mot de passe protège d'un conteneur
voisin, pas d'Internet — réseau `nginx_default` déclaré externe avec
l'application seule dessus, et port publié peu courant.

ENCRYPTION_KEY reste la seule variable sans valeur par défaut, et n'en aura
pas : elle chiffre le NIR, l'IBAN et les arrêts de travail. Une clé livrée avec
l'image serait connue de quiconque lit ce dépôt.

Au démarrage, l'application contrôle ses propres privilèges : elle refuse de se
lancer si la base porte plus d'un compte, et se contente d'un avertissement
s'il n'y en a qu'un — bloquer une installation mono-compte fermerait l'accès de
l'entreprise à ses données pour une fuite entre clients qui ne peut pas se
produire.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-09 08:39:54 +00:00
Claude 023f236881 WP-04: planning sur données réelles — grille semaine, vue jour, publication par équipe
Le planning quitte le module de démonstration : la grille lit `WeeklySchedule`
et `Shift`, et les quatre derniers fichiers de `src/lib/demo/` qui portaient des
salariés fictifs disparaissent.

Modèle
- `TeamMember` : rattachement d'un salarié à une équipe, distinct de
  `MembershipScope` (qui dit ce qu'un manager a le droit de voir). Sans lui, un
  salarié sans créneau n'apparaîtrait pas dans la grille — c'est-à-dire l'état
  de départ de toute semaine en construction.
- RLS sur `WeeklySchedule`, `Shift`, `Rest`, `DailyNote` et `TeamMember`.

Temps
- `src/domain/planning/week.ts` : repérage par couple année ISO + semaine ISO,
  jamais par date de début. Le lundi 29 décembre 2025 appartient à la semaine 1
  de 2026 ; une clé fondée sur la date ferait apparaître deux semaines 1.
- `zonedInstant` / `zonedMidnight` corrigent le décalage mesuré **à l'instant
  visé**. Le 29 mars 2026, minuit est en UTC+1 et 09 h en UTC+2 : ajouter neuf
  heures à minuit donnerait 10 h locales.
- La semaine du retour à l'heure d'hiver dure 169 h, celle du passage à l'heure
  d'été 167 — vérifié par test.

Écritures
- Création, modification, suppression de créneau ; publication et dépublication
  **par équipe**, avec verrou optimiste sur `version` : deux managers sur la
  même grille est le cas normal, pas l'exception.
- Chevauchement refusé en transaction, pas seulement dans le formulaire.
- Modifier une semaine publiée exige `planning.edit_published`, capacité que le
  rôle manager n'a pas : un salarié a organisé sa semaine sur ce qu'il a lu.
- Toute mutation laisse une entrée d'audit ; la suppression écrit sa trace
  **avant** l'effacement, sinon l'état supprimé serait perdu.

Lecture
- `planning.view_unpublished` filtre en base : sans cette capacité, les
  brouillons ne sont pas chargés du tout. Un test vérifie que les horaires
  n'apparaissent pas dans le HTML servi — un masquage CSS les y laisserait.
- Vue jour reconstruite sur les mêmes données, amplitude déduite de la journée
  réelle plutôt que figée à 06 h–21 h.

Vérification
- 26 tests unitaires sur le repérage des semaines et la mise en grille.
- Parcours e2e : poser un créneau, refus de chevauchement, publier, dépublier ;
  et ce que voient un salarié et un manager sur la même semaine.
- `scripts/dev-db.sh` : la base de développement est éphémère dans cet
  environnement, la remonter ne doit pas être une redécouverte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-07 23:43:48 +00:00