Lot 1 — Routes de lecture du back-office
1er octobre 2026. Application d’EC-066, décidé le 01/10/2026 : la piste A écrit ces quatre routes avant que B2 ne démarre. Écart ouvert : EC-071.
| Route | Droit | Rend |
|---|---|---|
GET /v1/moi |
membre actif | Utilisateur, organisation active, permissions effectives |
GET /v1/campagnes |
membre actif | Campagnes de l’organisation, filtre statut |
GET /v1/controles |
review.resolve |
File de contrôle, filtres statut et limite |
POST /v1/controles/{id}/lever |
review.resolve |
Le contrôle levé |
- « Membre actif » : un jeton valide, un compte de l’organisation, un statut
actif. Aucune permission particulière : le catalogue (EC-038) n’a pas de permission de lecture des référentiels et on n’en invente pas. La route le déclare par@SansPermission(); une route protégée sans aucune déclaration reste une erreur de programmation. - Les permissions se lisent en base à chaque appel, sans cache : une permission retirée disparaît à la requête suivante, y compris dans
moi.
File de contrôle
Section intitulée « File de contrôle »GET /v1/controles rend par défaut les contrôles ni levés (ouvert, en_cours), du plus ancien au plus récent. Chaque contrôle porte ses decisions_possibles, calculées par controle.decisionsPermises (packages/domain), qui reprend la table du protocole §9. L’écran n’a pas à connaître cette table.
POST /v1/controles/{id}/lever prend { decision, motif_decision }. Le décideur et la date viennent du contexte et de l’horloge de l’API, jamais du corps.
| Refus | Code | Statut |
|---|---|---|
| Contrôle inconnu dans l’organisation | controle_inconnu |
404 |
| Contrôle déjà levé | controle_deja_leve |
409 |
| Décision absente du protocole pour ce motif | decision_non_permise |
400 |
| Décision qui passe par son action | decision_par_action |
400 |
| Levée par l’auteur de la commande (SB-12, tenu par la base) | levee_par_auteur |
403 |
| Motif absent ou vide, identifiant invalide | corps_invalide, identifiant_invalide |
400 |
La fusion ne se lève pas ici. Lever un doublon par fusion sans fusionner les fiches laisserait une incohérence. decisions_possibles d’un doublon ne contient donc que confirmation (personnes distinctes). L’action fusionnerProducteurs (A4) lève elle-même le contrôle avec la décision fusion, et sa route arrivera avec les écrans B3.
Les autres décisions (confirmation, correction, déclassement…) ne sont ici qu’un enregistrement motivé. Leurs effets métier arrivent avec leurs lots.
Rapprochements avec le protocole (EC-071)
Section intitulée « Rapprochements avec le protocole (EC-071) »- Une seule permission. Le §9 distingue « RSCI », « administrateur » et « support » par motif ; EC-038 n’a retenu que
review.resolve. Aucune granularité n’est ajoutée. - Libellés rapprochés des valeurs de la base. « Valeur retenue » (modification concurrente) et « pièce fournie » (pièce manquante) deviennent la décision
correction, la base n’ayant pas de valeur dédiée. - Motifs sans décision.
intrant_non_autorise,conflit_interetsetparametre_introuvablene figurent pas au §9 : ils n’ont aucune décision, donc aucun contrôle de ces motifs ne peut être levé tant que l’écart n’est pas tranché.
| Test | Ce qu’il vérifie |
|---|---|
VT-CTRL-001 à 013 |
Décisions par motif, motif sans décision, motif inconnu |
apps/api/test/back-office.test.ts |
moi (permissions effectives, retrait pris en compte, suspendu refusé), campagnes (ordre, filtre, isolation entre organisations), controles (droit, doublon d’A4 visible, tri, limites), lever (décideur tracé, refus, SB-12) |
openapi.test.ts |
Concordance du registre et des routes montées ; le client est régénéré (pnpm --filter @biotrace/api-client generer) |
Hors séance
Section intitulée « Hors séance »Priorité des contrôles par effet bloquant : le champ bloquant est rendu depuis le contrat 1.1.0 (B2, voir EC-079), l’API trie toujours par ancienneté et c’est le back-office qui met les bloquants en tête ; pagination au-delà de 200 ; route de fusion des fiches (B3).