Aller au contenu

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.

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.

  • 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_interets et parametre_introuvable ne 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)

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).