Repository navigation
feat(communication): #128 — chef d'unité par UnitLead en messagerie, modération et pédagogie - #184
Merged
Merged
Conversation
…modération et pédagogie - dm-policy : « chef de l'unité du jeune » = CHEF qui encadre son unité (ledUnits) ; la règle entre mineurs de 15 à 17 ans passe avant. - Signalements : notification et file des CHEF par unités encadrées, fail-closed sans encadrement. - 2e validation pédagogique : notifie les chefs qui encadrent l'unité. - setUserRoles ne recrée plus d'UnitLead sur un compte DELETED. - D-045 amendée. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
This was referenced Oct 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.





Origine
Refs #128 — étape 3 sur 4 (« chef de cette unité » passe par les unités encadrées). L'issue reste ouverte pour l'étape 4.
Ce qui change
Pourquoi
Après #183, le périmètre d'un chef repose sur
UnitLead, mais la messagerie, la modération et la notification pédagogique comparaient encoreUser.unit. Un compagnon chef des Louveteaux recevait donc les signalements des Compagnons et pas ceux des Louveteaux.Preuves
pnpm lint: ✅ 0 erreur (3 warnings déjà présents surdevelop,TicketsSection.tsxetplace-actions.ts)pnpm typecheck: ✅pnpm test: ✅ 602 tests (584 surdevelopaprès feat(admin): #128 — unités encadrées (UnitLead), périmètre d'un chef #183). Tests ajoutés, rouges avant le code (6 échecs constatés, pour la bonne raison) :src/modules/communication/dm-policy.test.ts, blocchef d'unité par UnitLead (#128):ledUnits(vide ou absent) : refusé ;ledUnitssans rôle CHEF : refusé.src/modules/communication/moderation-policy.test.ts, blocroutage par unités encadrées (#128): Lucas notifié pour les Louveteaux et pas pour les Compagnons, avec le traitement symétrique ; chef de deux unités ; CHEF sans encadrement ; encadrement sans CHEF ; l'auteur du contenu reste exclu (fix(moderation): le chef visé par un signalement le reçoit, voit qui l'a signalé et peut le rejeter #91).src/lib/permissions.test.ts: propagation déjà en place.pedago.manage,pedago.referential,member.family.manage,announcement.publish,event.manageetbudget.managesuiventledUnitspour Lucas, et un chef repris (UnitLead=unit) garde les mêmes droits. Ces tests sont verts dès l'écriture : c'est la vérification demandée, sans changement de code.expect) existante n'a changé. Les fixtures CHEF dedm-policy.test.ts(chief()) et du blocselectReportRecipientsreçoiventledUnits= leurunit, comme le fait la migration.user.unitpour un CHEF » :grep -rnE "\b(user|me|actor|viewer|adult|u)\??\.unit\b" src --include=*.ts --include=*.tsx(hors tests) donne 36 lignes, classées une par une :admin/inscriptions,admin/utilisateurs(×2),planning/[id]/page.tsx:388-390,AttendanceList.tsx,registrations/export,membres/[id]/page.tsx:168-169,FamilySection.tsx,compte,dashboard/page.tsx:81,UserMenu.tsx,pedagogie/attribuer,family/queries.ts,planning/queries.ts:160.membres/[id]/page.tsx:90-100(canActOnUnitsur l'unité du jeune) etplanning/stats.ts:99(stats du jeune).dm-queries.ts:27(unité du jeune),audience.ts:43etannouncement-queries.ts:58(audience des annonces),communication/access.ts:46,54(salons d'unité),planning/actions.ts:337(branche de la personne qui s'inscrit),planning/[id]/page.tsx:116(un CHEF passe parisStaff),api/calendar/[token]/route.ts:110(iCal),dashboard/queries.ts:181(prochain événement, non borné pour qui aevent.manage).planning/presences/page.tsx:31(unité présélectionnée parmiscopedUnits).permissions.ts:292(prêt d'un jeune Pionnier ou Compagnon) etpermissions.ts:335(rôles non CHEF,TODO(ROLES-06)).dm-policy,selectReportRecipients,listReportsetprogression-actions: elles sont migrées. Plus aucunroles contains CHEF+unitdanssrc/.src/modules/camp/places.ts:228: affichage de l'unité de l'événement d'un avis de lieu. Ni accès ni appartenance, rien à faire.src/modules/planning/event-hooks.ts:46(resolveUnitAudience) : appartenance. Les notifications d'événement vont aux membres de l'unité. Conséquence produit : Lucas n'est pas notifié des événements des Louveteaux. À trancher à l'étape 4.src/modules/planning/reminders.ts:37: appartenance (relances d'inscription aux membres et à leurs parents). Correct tel quel.pnpm dev:worktree(port 3101), base du worktree re-seedée, état vérifié en base. Deux exécutions :verifier(scriptall.js) :/messages→ « Nouveau » : seuls « Admin Piloti » et « Éléonore Lefebvre » (cheffe des Compagnons, son unité d'appartenance) sont proposés, aucun jeune./messages/<id d'une Compagnon de 17 ans>: 404.UnitLead=unit),/moderation: il voit le signalement Pionniers, pas celui des Louveteaux.s3.js), pour les points que le verifier n'a pas pu prouver, sur une base re-seedée :#compagnonsun message du chef des Louveteaux (→concernedUnitLOUVETEAUX) et un message de la cheffe des Compagnons (→ COMPAGNONS).REPORT_CREATEDdu signalement LOUVETEAUX : admin, RG, Lucas.REPORT_CREATEDdu signalement COMPAGNONS : admin et RG, pas Lucas. Avant ce changement, il l'aurait reçu (membre des Compagnons)./moderation: les deux signalements Louveteaux (celui du seed et le nouveau), pas celui des Compagnons.chef.farfadetspar l'UI →DELETED,unitFARFADETS conservée, 0UnitLead. J'envoie ensuitesetUserRoles(rôle CHEF) sur ce compte, en réécrivant l'identifiant de la requête envoyée par le dialogue « Rôles » d'un autre chef, comme feat(admin): #128 — unités encadrées (UnitLead), périmètre d'un chef #183 l'a fait pourcreateEvent→ toujours 0UnitLead, auditUSER_ROLE_CHANGEDavecleadUnits: []. Le chef dont la requête a été détournée garde[SCOUTS]. Je n'ai pas rejoué ce scénario sur le code d'avant : selonleadUnitsForRoles, il aurait recréé[FARFADETS].after()). Couverte seulement par le typecheck et la relecture.security-auditor: 1 passe, sur le commit19c7226(diffHEAD~1..HEAD,developn'existant pas en local dans son contexte : même contenu). Aucun point bloquant ni élevé. 3 points relevés, 0 corrigé, 3 laissés (ci-dessous, « Risques »). Le premier est moyen : salons, annonces, iCal et événements encore surUser.unit. Les deux autres sont faibles :ledUnitsoptionnel dansDmParticipantetModeratorCandidate, etroles contains "CHEF"dansprogression-actions, code d'avant le diff.reviewer: 1 passe. Il a atteint sa limite de tours et je l'ai relancé une fois pour qu'il conclue : c'est la même relecture. Il a lancé lint, typecheck et tests (602 ✅). Aucun point bloquant, élevé ni moyen. 3 points faibles relevés, 0 corrigé, 3 laissés :unit: nullnotifiait les chefs sans unité ;DELETEDdesetUserRolespeut-être inutile. Vérifié : aucune garde de statut en amont, et le parcours l'a atteinte, donc je l'ai gardée ;rolesélectionné mais jamais lu dansnotifyModerators, code d'avant le diff.verifier: 1 passe. Il a atteint sa limite de tours et je l'ai relancé une fois. Messagerie de Lucas ✅, modération de Lucas et de Thomas ✅. Notification de Lucas et compteDELETEDnon prouvés : faits ensuite dans le fil principal (ci-dessus). Il avait créé un signalement par l'app et en avait inséré un autre en base ; la base a été re-seedée ensuite.architect: conception tranchée par Mathis (option E, règles de la tâche) ;test-engineer: tests écrits dans le fil principal, rouges avant le code ;debugger: pas de bug à diagnostiquer ;feature: code écrit dans le fil principal ;refactorer: pas de refactor ;doc-writer: amendement de D-045 écrit dans le fil principal ;git-manager: commit et PR dans le fil principal ;explorer/Explore: recherches faites dans le fil principal ;project-manager: sans objet.Invariants
withAudit(): la seule mutation touchée estsetUserRoles, déjà souswithAudit. Son audit porteleadUnits: []pour un compte supprimé.can(user, "…"): gardes inchangées. Seuls changent le périmètre d'unité (ledUnits) et le calcul des destinataires.setUserRoles.Décisions prises
ledUnitscontient l'unité d'appartenance du jeune. L'unité d'appartenance du chef ne compte plus, et le rôle CHEF reste exigé : unUnitLeadsans CHEF n'ouvre rien.concernedUnit IN ledUnits, et une liste vide renvoie[]avant toute requête (fail-closed). Elle est suivie du filtrecanModerateReportexistant.unit: nullnotifiait les chefs sans unité.sendDirectMessagepasse directement l'utilisateur connecté (qui porteledUnits) àevaluateDmPolicy.DECISIONS.md.Risques et points à vérifier
À trancher à l'étape 4 (relevé par le
security-auditor, MOYEN) : ces surfaces restent surUser.unitcomme appartenance :communication/access.ts) ;Conséquences pour Lucas : il lit le salon des Compagnons, pas celui des Louveteaux, sauf si le salon est ouvert au rôle Chef, et il n'est pas notifié des événements des Louveteaux. Réciproquement, retirer l'encadrement d'un chef ne lui retire pas l'accès aux salons de son unité d'appartenance. Rien n'a changé ici par rapport à
develop.FAIBLE (auditor) :
ledUnitsest optionnel dansDmParticipantetModeratorCandidate. Un futur appelant qui l'oublierait compilerait, et les chefs perdraient l'accès sans erreur (fail-closed). Tous les appelants actuels le fournissent :sendDirectMessage,getThread,listContacts,notifyModerators. Le rendre obligatoire toucherait toutes les fixtures non CHEF des tests.FAIBLE (auditor), code d'avant le diff :
roles: { contains: "CHEF" }dansprogression-actions.tsest une recherche de sous-chaîne. Aucun autre rôle ne contient « CHEF » aujourd'hui.Choix A (rappel) : un RG ou une secrétaire qui s'attribue CHEF encadre son unité, et peut alors écrire en privé à ses 15-17 ans. Décision validée, tracée en audit.
À relire en priorité :
dm-policy.ts(isUnitChiefOf) etmoderation-queries.ts(listReports).🤖 Generated with Claude Code