diff --git a/docs/conception.md b/docs/conception.md index 251e214..bafcdca 100644 --- a/docs/conception.md +++ b/docs/conception.md @@ -66,8 +66,7 @@ Compte système unique, hors rôles Atelier. - arbitre correction et annulation ; - priorise et ajoute une consigne ; - modifie les informations descriptives d'un incident actif ; -- annule un incident non pris ou reprend le contrôle d'un incident en - attente ; +- annule un incident actif non pris ou en attente ; - invalide une clôture avec motif ; - suit des incidents ; - accède seul au journal transverse (le Pilotage, comme le Dashboard et la @@ -101,8 +100,10 @@ Atelier. ### Board -Le code local est comparé à un hash bcrypt. La session est limitée dans le -temps, révocable par version et ne donne accès qu'à la projection Board. Un +Le code local est comparé à un hash bcrypt. La durée de session est réglée +par l'administrateur (0 = sans expiration automatique) ; la session reste +révocable à tout moment par version et ne donne accès qu'à la projection +Board. Un utilisateur Atelier déjà connecté peut aussi lire cette projection sans obtenir de nouveau droit. @@ -253,6 +254,11 @@ uniquement si l'incident n'est pas pris et n'a pas d'arbitrage. Cas `PENDING` : uniquement `RESPONSABLE`, comme décision de supervision. Effet : statut `CANCELED`, conservation intégrale dans l'historique. +**Archivage forcé d'une ligne.** Acteur : administrateur, après affichage de +l'impact. Effet : tous les incidents actifs de la ligne, y compris pris ou en +attente, passent `CANCELED` avec le motif `line_archived`, et leurs +arbitrages ouverts deviennent `SUPERSEDED`, dans la même transaction. + **`INVALIDATE_CLOSED`.** Acteur : `RESPONSABLE`. Condition : incident `CLOSED`. Donnée requise : motif. Effet : statut `INVALIDATED`, sans réouverture de l'incident. @@ -273,6 +279,10 @@ la croix/Escape : aucun changement métier. Seul `ACTIVE` compte dans la pastille rouge « À arbitrer ». Une consultation du dossier déclenchée en dehors du bouton d'arbitrage ne marque jamais le cas lu. +**Retrait.** `WITHDRAW_CANCEL` : l'opérateur déclarant retire sa demande +tant qu'elle attend l'arbitrage. Le cas passe `WITHDRAWN`, l'incident reste +actif et le marqueur de demande est effacé. + **Décision.** `APPROVE_CANCEL` : cas `APPROVED`, incident `CANCELED`. `REJECT_CANCEL` : cas `REJECTED`, incident reste actif, demande effacée. Archivage/annulation globale rendant la demande obsolète : cas @@ -342,7 +352,7 @@ Les états décrivent l'anomalie, indépendamment du statut de traitement. | `CONSULTED` | consultation explicitement demandée, décision encore attendue | | `APPROVED` | demande acceptée | | `REJECTED` | demande refusée | -| `WITHDRAWN` | correction retirée par son demandeur | +| `WITHDRAWN` | demande (correction ou annulation) retirée par son demandeur | | `SUPERSEDED` | demande rendue caduque par une autre opération | Comportement de navigation : décider directement est le chemin principal ; diff --git a/docs/design.md b/docs/design.md index c9d3370..461cda2 100644 --- a/docs/design.md +++ b/docs/design.md @@ -317,9 +317,17 @@ au lieu de redéfinir localement des couleurs d'état. ### 5.2 Mouvement (P1, P2) Le mouvement est l'attribut pré-attentif le plus puissant : il est réservé à -de rares cas et n'est jamais mis en boucle. Les transitions servent la -continuité — comprendre ce qui change — non la décoration. Le réglage -`prefers-reduced-motion` est respecté. +de rares cas et n'est jamais mis en boucle pour décorer. Les transitions +servent la continuité — comprendre ce qui change — non la décoration. Le +réglage `prefers-reduced-motion` est respecté : il ramène toute animation à +une seule itération quasi instantanée. + +Exceptions assumées, parce qu'elles signalent une attente ou un état +dégradé : les indicateurs de chargement (spinner d'action ponctuelle, +squelette), l'indicateur « écrit… » de l'assistance, et le bandeau +« Hors ligne » du Board, qui pulse doucement tant que l'écran n'est plus +synchronisé — le seul cas où le Board risquerait d'afficher une situation +dépassée. ### 5.3 Densité (P2, P3) @@ -344,11 +352,17 @@ esthétique. L'échelle de tokens (`--text-*`) en est le vocabulaire. ### 5.6 Temps (P7) Le temps s'exprime en durée vécue (« depuis 3 h », « depuis 8 j »), la date -précise restant accessible au second plan. Le vieillissement module le -niveau d'attention (§5.1) de manière continue ; le seuil « 7 jours » est un +précise restant accessible au second plan. Le vieillissement doit moduler le +niveau d'attention (§5.1) de manière progressive ; le seuil « 7 jours » est un repère, pas une alarme. Les durées sont alignées et lisibles d'un coup d'œil pour permettre la comparaison sans calcul. +État actuel : la durée vécue est appliquée partout via une fonction unique +(`formatDuration`). La fonction de montée par ancienneté (`ageAttentionLevel`, +paliers à 1, 3 et 7 jours) existe mais n'est pas encore branchée sur les +cartes, dont le niveau dépend aujourd'hui du statut, de la priorité et de la +prise en charge (`incidentAttentionLevel`). C'est une évolution prévue. + --- ## 6. Usage de la doctrine @@ -391,8 +405,8 @@ Le chantier a été mené en cinq phases, des fondations transversales vers les | Phase | Lot | Objet | État | |-------|-----|-------|------| | 1 — Fondations | F1 | Tokens de niveaux d'attention (`--attention-calm/-watch/-act/-critical`) | Fait | -| 1 — Fondations | F2 | Composant unique appliquant la grammaire d'attention | Fait | -| 1 — Fondations | F3 | Fonction unique « durée vécue + niveau d'attention dérivé de l'âge » | Fait | +| 1 — Fondations | F2 | Fonction unique `incidentAttentionLevel()` et classes CSS appliquant la grammaire d'attention (le composant `AttentionBadge`, jamais branché, a été retiré en juillet 2026) | Fait | +| 1 — Fondations | F3 | Fonction unique « durée vécue + niveau d'attention dérivé de l'âge » | Fait (niveau par l'âge non encore branché, voir §5.6) | | 1 — Fondations | F4 | Skeleton unifié, spinner réservé aux actions ponctuelles | Fait | | 2 — Urgence | U1 | Refonte des cartes urgentes du Dashboard selon F1 | Fait | | 2 — Urgence | U2 | Alignement des cartes Board sur F1, lisibilité à distance vérifiée | Fait | @@ -417,7 +431,8 @@ service backend, ramenant l'Historique à une seule question au sens de P3. - un lot correspond à un commit testé et déployable ; - chaque changement est justifié par un principe (référence explicite en commit) ; -- le backend n'a pas été touché : la doctrine agit comme une couche - d'expérience au-dessus d'un domaine jugé sain ; +- le backend n'a été modifié que pour S2 (contrôle d'accès du Journal côté + service) : la doctrine agit comme une couche d'expérience au-dessus d'un + domaine jugé sain ; - l'ordre des phases n'était pas négociable — les fondations avant les écrans, pour ne pas refaire le travail deux fois. diff --git a/docs/production.md b/docs/production.md index 37f4f63..45a94af 100644 --- a/docs/production.md +++ b/docs/production.md @@ -419,8 +419,11 @@ ne pas interrompre le script pendant la bascule. ``` Le script refuse de démarrer si une sauvegarde est en cours (verrou partagé -avec `backup.sh`), et refuse tout dump sans `.sha256` associé sauf ajout -explicite de `--allow-unverified` (journalise un avertissement audité). Il +avec `backup.sh` dans le même répertoire : si les dumps sont écrits avec +`--dir`, lancer la restauration avec la même valeur, par exemple +`BACKUP_DIR=/srv/backups/sentinel ./scripts/restore.sh …`), et refuse +tout dump sans `.sha256` associé sauf ajout explicite de +`--allow-unverified` (journalise un avertissement audité). Il importe dans une base temporaire, contrôle le schéma, exige que le ledger corresponde exactement aux migrations canoniques du checkout (noms, ordre et checksums), puis arrête le backend et bascule les bases. En cas d'échec @@ -546,7 +549,7 @@ vérificateur dans la trace d'intervention. | `BOARD_ACCESS_CODE_HASH` | oui | hash bcrypt du code Board initial (entre quotes simples) | | `ADMIN_USERNAME` | base vide | bootstrap Admin, non vide, max. 80 caractères, non numérique | | `ADMIN_PASSWORD` | base vide | mot de passe temporaire du premier admin | -| `CADDY_DOMAIN` | topo A | domaine servi par Caddy (annexe uniquement) | +| `CADDY_DOMAIN` | oui | domaine servi par Caddy ; exigé à la lecture du Compose même en topologie B, où Caddy ne démarre pas | Ne jamais mettre un secret dans une commande versionnée, un ticket public ou une capture d'écran. @@ -585,8 +588,13 @@ locale (`up -d --no-build --force-recreate backend`). La rotation de `JWT_SECRET` invalide toutes les sessions : planifier l'opération et prévenir les utilisateurs. -**Code Board.** Voie normale : Administration > Paramètres. Procédure de -secours : +**Code Board.** Voie normale et voie de secours : Administration > +Paramètres. Le nouveau hash est enregistré en base et la version de session +Board est incrémentée : toutes les sessions Board existantes sont révoquées +immédiatement. Dès qu'un code a été défini depuis l'interface, le hash en +base est prioritaire et `BOARD_ACCESS_CODE_HASH` (`.env`) est ignoré ; cette +variable ne sert qu'au code initial, tant qu'aucun code n'est enregistré en +base : ```bash cd backend @@ -594,10 +602,8 @@ BOARD_ACCESS_CODE='nouveau-code-temporaire' npm run hash:board cd .. ``` -Mettre le hash bcrypt dans `.env` entre quotes simples, recréer le backend, -vérifier une nouvelle connexion Board. Les sessions Board antérieures -peuvent rester valides jusqu'à expiration ; une rotation du `JWT_SECRET` les -invalide toutes. +Si l'accès Admin est lui-même perdu, le rétablir d'abord (paragraphe +suivant), puis définir le nouveau code Board depuis Paramètres. **Mot de passe admin.** Voie normale : Administration > Sécurité. Si l'accès est perdu : @@ -999,10 +1005,15 @@ signataires. ## 16. État vérifié historique de l'instance publique -Dernière preuve de production consignée dans ce document : **31 juillet -2026**, sur le SHA `deecf6d57d3f0304e18fe9fd56847f5d9cd0d1a7` (tag -`v1.0.0-rc.8`). Cette section est une photographie historique RC8 : elle ne -prouve pas que le candidat RC9 en préparation est déjà déployé. +La release `v1.0.0-rc.9` (SHA `ed26a25e3c005cabb0da30a4553dfbbee03afe81`) +est publiée ; le 17 septembre 2026, `/api/health` de l'instance publique +renvoyait ce SHA. Cette observation datée, avec la CI, la publication et les +digests d'images, est consignée dans +[rc9-verification-2026-09-17.md](rc9-verification-2026-09-17.md). + +La suite de cette section est la photographie historique précédente, sur RC8 : +**31 juillet 2026**, SHA `deecf6d57d3f0304e18fe9fd56847f5d9cd0d1a7` (tag +`v1.0.0-rc.8`). - `/api/health` répondait `{"status":"ok","db":"ok","version":"deecf6d57d3f0304e18fe9fd56847f5d9cd0d1a7"}` ; - le DNS A de `sentinel.akiksystems.fr` pointait vers l'adresse du VPS, les @@ -1034,12 +1045,11 @@ sécurité RC9. La revue RC9 du 16 septembre 2026 est décrite dans [technique.md](technique.md) §16 : politique sans exception active et quatre audits npm à zéro vulnérabilité au moment de la revue. -Sans accès SSH nominatif au VPS, cette section ne prouve pas les fichiers -Compose actifs, les binds loopback ni les images/digests internes. Une -nouvelle preuve de production doit être créée après publication et -déploiement du candidat RC9 : SHA de `/api/health`, digests de release et -d'images, puis recette courte. Aucune affirmation d'alignement RC9 ne doit -être déduite de la preuve RC8 ci-dessus. +Une observation publique ne prouve ni les fichiers Compose actifs, ni les +binds loopback, ni les digests effectivement exécutés : ces points se +contrôlent sur le serveur (§5.2 et §9). Aucune affirmation RC9 ne doit être +déduite de la photographie RC8 ci-dessus : la vérification RC9 est le +document daté cité en tête de section. ### Historique des audits @@ -1050,7 +1060,7 @@ rapports historiques — y compris un verdict du 17 juillet 2026 explicitement invalidé après coup parce que le VPS n'était alors pas encore aligné sur le candidat audité — restent conservés dans l'historique Git comme preuve d'un processus itératif réel plutôt qu'effacés ou réécrits. Ils ne remplacent pas -la preuve à produire pour RC9. +la vérification RC9 citée en tête de section. ## 17. Publication GitHub — spécificités release diff --git a/docs/technique.md b/docs/technique.md index 7ecbcca..5c76261 100644 --- a/docs/technique.md +++ b/docs/technique.md @@ -304,6 +304,10 @@ jamais sur le texte du message. ### 6.5 Défenses HTTP - CORS limité à `CLIENT_ORIGIN` ; +- garde anti-CSRF sur toute requête d'écriture : `Origin` (ou `Referer` à + défaut) doit valoir exactement `CLIENT_ORIGIN`, et un `Sec-Fetch-Site` + inter-sites est refusé (`middlewares/csrfProtection.ts`), en plus des + cookies `SameSite=Strict` ; - headers de sécurité applicatifs ; - corps JSON limité à 50 Ko ; - limite globale par IP et limite renforcée sur les connexions ; @@ -342,8 +346,11 @@ CLOSED -> INVALIDATED Une prise en charge est une revendication ou un transfert explicite. Les champs `is_taken`, `taken_by_user_id` et `taken_at` sont cohérents par contrainte SQL. Une seule anomalie active peut exister pour un emplacement -machine donné. Voir [conception.md](conception.md) pour le détail complet des -transitions et invariants métier. +machine donné. Un incident `OPEN` pris ne passe à `CANCELED` que par +l'archivage forcé de sa ligne ; un incident `PENDING` peut aussi être annulé +par le responsable. +Voir [conception.md](conception.md) pour le détail complet des transitions et +invariants métier. ### 7.3 Arbitrage @@ -766,9 +773,10 @@ job Containers inspecte les images applicatives réellement construites. `scripts/backup.sh` produit un dump gzip atomique avec checksum et rétention. `scripts/restore.sh` valide le dump dans une base temporaire avant une bascule de noms, avec arrêt court du backend et tentative de rollback en -cas d'échec. Les deux scripts partagent un même verrou de fichier : sauvegarde -et restauration ne peuvent jamais s'exécuter en même temps. La restauration -refuse par défaut tout dump sans checksum SHA-256 associé, importe dans une +cas d'échec. Les deux scripts partagent un même verrou de fichier +(`$BACKUP_DIR/.sentinel-backup.lock`) : sauvegarde et restauration ne peuvent +pas s'exécuter en même temps sur un même répertoire de sauvegarde. La +restauration refuse par défaut tout dump sans checksum SHA-256 associé, importe dans une base temporaire, valide la présence des tables du schéma et l'égalité exacte du ledger `schema_migrations` avec les fichiers canoniques du checkout (noms, ordre et checksums) avant d'échanger les noms de base. Un trap nettoie la @@ -799,9 +807,11 @@ est dans [production.md](production.md). et le verrou de migration tolèrent plusieurs workers, mais un déploiement horizontal demanderait une stratégie explicite de sessions, santé et orchestration ; -- le rate limiting (`backend/src/utils/inMemoryRateLimit.ts`) est un - compteur en mémoire de processus : il protège correctement une réplique - unique mais ne partage aucun état entre instances. Tout passage à +- le rate limiting (`backend/src/middlewares/loginRateLimit.ts`, utilisé + pour les limites globale, connexion, réinitialisation et support ; + `backend/src/utils/inMemoryRateLimit.ts` pour la réauthentification Admin) + repose sur des compteurs en mémoire de processus : il protège correctement + une réplique unique mais ne partage aucun état entre instances. Tout passage à plusieurs répliques exige un stockage partagé (Redis ou équivalent) avant déploiement ; - les migrations sont forward-only ; le rollback de schéma passe par une