Claude 92a0ebf853 Configurer les rôles et leurs capacités
Critère d'acceptation de WP-01 resté sans écran : « un rôle personnalisé créé
par un client modifie effectivement l'accès, sans changement de code ». Le
catalogue de capacités existait, l'autorisation s'y adossait déjà, mais seul le
semis pouvait attribuer quoi que ce soit.

Deux dangers opposés, et il faut se protéger des deux. L'escalade d'abord : on
ne peut accorder qu'une capacité qu'on détient soi-même, sans quoi la première
personne autorisée à éditer un rôle s'accorde l'accès aux rémunérations en
trois clics et le catalogue ne sert plus à rien. Le retrait, lui, reste permis
même sur une capacité qu'on n'a pas — réduire un droit n'a jamais élargi le
sien, et l'interdire empêcherait de corriger un rôle trop large.

Le verrouillage ensuite : au moins un rôle doit conserver la gestion des
droits. La condition se juge sur l'ensemble des rôles après coup, non sur celui
qu'on édite — ce qui compte n'est pas que ce rôle-ci garde la capacité, mais
qu'un rôle la garde. Sans cela une organisation se fermerait dehors, et le seul
recours serait une intervention en base.

Un rôle naît sans aucune capacité : en hériter de celles de son créateur
distribuerait des droits que personne n'a demandés. Le niveau propriétaire se
délègue à part. Un rôle système ne se supprime pas, un rôle attribué non plus —
ses membres se retrouveraient dehors sans que personne l'ait décidé pour eux.

Le test va jusqu'au bout : créer le rôle, l'attribuer, se connecter, constater
qu'un écran s'ouvre et qu'un autre reste fermé. S'arrêter à « la case est
cochée » n'aurait rien dit du critère.

Trois défauts d'isolation des tests corrigés au passage, tous du même genre —
un état qui s'accumule d'une exécution à l'autre :

- la file des congés désignait « la première ligne de ce salarié » pour son
  nettoyage, et tombait sur une ligne laissée par un passage antérieur ;
- l'écran de conservation tronquait à quarante pièces triées par ancienneté,
  faisant disparaître les pièces échues derrière celles dont il n'y a rien à
  dire — corrigé dans le produit, pas seulement dans le test ;
- le test de verrouillage de période consommait un mois par exécution sans
  jamais le rendre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-09 08:07:36 +00:00
2026-08-07 17:54:11 +00:00
2026-08-07 17:54:11 +00:00
2026-08-07 17:54:11 +00:00
2026-08-07 17:54:11 +00:00
2026-08-07 17:54:11 +00:00
2026-08-07 17:54:11 +00:00

PlanFlow

Gestion du personnel, des plannings et des temps, multi-établissements, auto-hébergée. La paie est exportée vers Silae ; PlanFlow ne produit ni bulletin ni DSN.

La spécification de construction est PLAN.md. Elle est normative : en cas d'écart entre le code et le plan, c'est le plan qui a raison, ou le plan qui doit être corrigé — jamais l'écart qui s'installe.

Document Rôle
PLAN.md Spécification : périmètre, modèle de données, règles, lots de travail
matrice-conformite-rh-france-2026.md Exigences réglementaires françaises, cotées P0/P1/P2
Audit Combo/ Audit fonctionnel du produit de référence

État

Neuf lots livrés. Aucun écran ne lit plus de données de démonstration : le répertoire src/lib/demo a disparu.

Lot Contenu
WP-00 Socle : Next.js, PostgreSQL, CSP restrictive, CI, sauvegardes
WP-01 Tenancy, identité, autorisation : RLS, audit append-only, 70 capacités, cinq rôles
WP-02 Référentiels et registre de paramétrage juridique
WP-03 Dossiers salariés, contrats et avenants, forfait jours
WP-04 Planning : quatre vues, publication par équipe, impression
WP-05 Moteur de règles de convention, effectif-daté
WP-06 Absences, registre de compteurs, calendrier
WP-07 Heures prévu/réalisé/payé, périodes de paie verrouillables
WP-08 Export Silae, format relevé sur un export réel du dossier
WP-09 Tableau de bord RH : indicateurs explicables

Restent WP-10 (documents) et WP-11 (communication, optionnel), ainsi que deux points nommés à l'intérieur des lots livrés : la régularisation automatique sur la période suivante et l'information au retour d'arrêt.

Ce qui attend une décision du client

Ces points sont des signaux d'arrêt au sens de PLAN.md : ils ne se devinent pas.

  • Les codes d'absence Silae (AB-100, AB-200, AB-300, AB-630) : leur existence est connue, leur signification non. L'export refuse de tourner tant que la correspondance n'est pas confirmée.
  • Le régime dominical applicable aux magasins, qui détermine si la majoration de 100 % et le repos compensateur sont les bonnes contreparties.
  • Les règles applicables aux mineurs, sans source primaire au dossier.

Démarrer

Avec Docker

cp .env.example .env
# Renseigner POSTGRES_PASSWORD et ENCRYPTION_KEY (voir ci-dessous)
docker compose up --build

L'application écoute sur http://localhost:3000. Les migrations s'appliquent au démarrage du conteneur.

En local

Nécessite Node 22, pnpm 10 et un PostgreSQL 16 accessible.

pnpm install
cp .env.example .env          # renseigner DATABASE_URL et ENCRYPTION_KEY
pnpm db:generate
pnpm db:deploy
pnpm dev

Clé de chiffrement

ENCRYPTION_KEY chiffre au repos les colonnes sensibles exigées par le plan (§3.6) : NIR, IBAN, BIC.

openssl rand -base64 32

Elle vit hors de la base : une sauvegarde volée ne doit pas suffire à lire ces colonnes. La perdre rend ces données irrécupérables — la sauvegarder séparément et documenter sa rotation. Elle chiffre également les secrets de second facteur et le mot de passe du serveur d'envoi.

Second facteur — accès de secours

Les rôles qui lisent les rémunérations ou distribuent les droits doivent porter un second facteur (matrice n° 15) : tant qu'il n'est pas activé, l'application ne leur ouvre aucun écran. Chaque activation délivre dix codes de secours, affichés une seule fois.

PlanFlow étant auto-hébergé, il n'y a pas d'éditeur à appeler si un administrateur perd à la fois son téléphone et ses codes. Le retrait se fait alors depuis le serveur :

pnpm mfa:reset adresse@example.fr

Le retrait révoque les sessions ouvertes et s'inscrit au journal d'audit. Il n'est délibérément pas exposé dans l'application : l'exécuter demande déjà un accès au serveur, c'est-à-dire davantage que ce que le second facteur protège.

Durées de conservation

Aucune durée n'est appliquée par défaut : la matrice interdit d'aligner tout sur cinq ans, et un objet sans politique déclarée se conserve. Les durées se déclarent dans Réglages → Durées de conservation, chacune avec sa justification — elle devra être défendue lors d'un contrôle.

La purge doit tourner périodiquement, par exemple en cron :

pnpm retention:purge --dry   # inventaire, n'efface rien
pnpm retention:purge         # efface les pièces échues, compte par compte

Une conservation à titre probatoire (legal hold) suspend la purge des objets qu'elle vise, quelle que soit leur échéance. Les journaux d'audit y échappent par construction : ils doivent survivre aux données qu'ils décrivent, sans quoi il deviendrait impossible de démontrer que la purge a eu lieu.

Vérifier

pnpm verify      # typecheck + lint + tests unitaires
pnpm test:e2e    # build, serveur standalone, tests de bout en bout

pnpm verify est ce que la CI exécute sur chaque pull request, suivi du build et des tests end-to-end.

Configuration de la base — à ne pas rater

L'application ne doit pas se connecter en superutilisateur PostgreSQL.

Un superutilisateur contourne la row-level security, y compris déclarée en FORCE. Connecter PlanFlow avec un tel compte désactive silencieusement la seconde couche d'isolation multi-tenant : les requêtes fonctionnent, les tests applicatifs passent, et rien n'indique que la protection a disparu — jusqu'au jour où quelqu'un lit les données d'un autre établissement.

CREATE ROLE planflow_app LOGIN PASSWORD '…' NOSUPERUSER NOBYPASSRLS;
GRANT USAGE ON SCHEMA public TO planflow_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO planflow_app;

Les migrations, elles, s'appliquent avec un compte propriétaire distinct.

L'application vérifie ce point au démarrage : elle refuse de démarrer en production sur une base mal configurée, et se contente d'un avertissement en développement. GET /api/sante expose l'état sous tenantIsolation.

Choix structurants

Aucun traceur tiers. L'audit du produit de référence a intercepté 2102 requêtes de traçage — Segment, LinkedIn Ads, Google Ads, DoubleClick, Clarity, Hotjar — et aucune requête métier. Une application RH ne doit pas envoyer un contexte de navigation portant sur des salariés identifiables à des régies publicitaires. Deux garde-fous rendent la règle vérifiable plutôt que déclarative :

  • une Content-Security-Policy qui ne nomme aucune origine externe, posée par requête avec un nonce (src/proxy.ts) ;
  • un test qui échoue si une dépendance de traçage apparaît dans package.json.

Pour de la télémétrie technique, passer par une interface abstraite auto-hébergée.

Le serveur testé est celui qui est déployé. Les tests end-to-end lancent le serveur standalone, celui que l'image Docker exécute — pas next dev, dont la politique de sécurité est volontairement plus permissive.

Structure

src/
├── app/                 écrans (App Router)
├── lib/
│   ├── env.ts           contrat d'environnement, validé à l'import
│   └── security/csp.ts  politique de sécurité, fonction pure et testable
├── server/
│   ├── db.ts            client Prisma — le scoping multi-tenant s'y greffe au WP-01
│   └── health.ts
└── proxy.ts             en-têtes de sécurité par requête
prisma/                  schéma et migrations
tests/
├── unit/                Vitest
└── e2e/                 Playwright

Écarts assumés par rapport au plan

Trois choix diffèrent de ce qu'annonçait PLAN.md §2, et le plan a été mis à jour en conséquence.

Sujet Plan initial Retenu Raison
Next.js 15 16.3 Version stable courante ; démarrer un greenfield une majeure en retard n'a pas de contrepartie.
Authentification Auth.js v5 Sessions maison en base Auth.js v5 est encore en beta. Le besoin se limite à identifiants et invitation, sans OAuth, et la matrice de conformité (n° 23) impose la révocation de session — immédiate avec des sessions en base, malaisée avec des jetons JWT.
Convention Next middleware.ts proxy.ts Next 16 a renommé la convention ; middleware est déprécié.
S
Description
No description provided
Readme
15 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 97%
CSS 1.2%
Shell 0.7%
JavaScript 0.5%
PLpgSQL 0.4%
Other 0.2%