Protocole de synchronisation
Version 1, 29 septembre 2026. Livrable n° 1 des travaux de conception du Lot 0. S’appuie sur le découpage (décisions « Synchronisation », « Session hors-ligne », « Commandes hors-ligne », « Durée maximale hors-ligne », « Révocation ») et sur EC-009, EC-012, EC-013, EC-022, EC-029, EC-031, EC-032, EC-033 et GPS-01 à GPS-03.
- Le téléphone n’écrit jamais dans les tables métier. Il enregistre des commandes (« enregistrer une collecte ») dans une file locale.
- Le serveur traite les commandes dans l’ordre de chaque appareil, une par une. Il revalide tout avec les mêmes règles (
packages/domain), attribue les numéros officiels et écrit les données métier. - Chaque commande reçoit une issue : acceptée, acceptée en contrôle ou rejetée. Un problème métier ne produit jamais de rejet : il produit une entrée dans la file de contrôle. Le rejet est réservé aux anomalies de sécurité.
- Les données descendent vers le téléphone par PowerSync, filtrées par organisation et par périmètre de l’agent.
- Tant qu’une commande n’est pas traitée, le téléphone affiche ce qu’il a saisi comme provisoire.
1. Principes
Section intitulée « 1. Principes »| Principe | Conséquence |
|---|---|
| Le serveur est la seule autorité | Numéros officiels, capacités, statuts de qualité et validations ne sont décidés que par le serveur. |
| Des commandes, pas des modifications de lignes | Le téléphone exprime une intention métier complète et datée. Le serveur la traduit en écritures. |
| Idempotence | Une commande rejouée n’est enregistrée qu’une fois (UUID de commande, EC-009). |
| Ordre par appareil | Les commandes d’un appareil sont traitées dans l’ordre de leur numéro de séquence local, sans trou. |
| Un fait physique n’est jamais rejeté sans trace | Tout problème métier mène à « acceptée en contrôle ». Seul un humain peut écarter une commande, avec un motif. |
| Même calcul des deux côtés | Le téléphone et le serveur exécutent les mêmes fonctions de packages/domain. En cas d’écart, le serveur fait foi et l’écart est tracé. |
| Droits à la date de l’opération | Le serveur vérifie les droits de l’agent et l’état de l’appareil à la date où l’opération a eu lieu, pas à la date de réception. |
2. Architecture
Section intitulée « 2. Architecture »TÉLÉPHONE (apps/terrain, Capacitor) SERVEUR (Kubernetes, région UE)
Écran ──▶ packages/domain (contrôles locaux) │ ▼ commande_locale + commande (file d'envoi) │ │ │ │ POST /v1/sync/commandes │ └───────────────────────────▶ API NestJS │ ├─ validation (Ajv, packages/domain) │ ├─ écritures métier, numéros, mouvements │ ├─ journal d'audit, file de contrôle │ └─ tâches pg-boss (SMS, documents) │ │ │ ▼ │ PostgreSQL + PostGIS │ │ réplication logique ▼ ▼ Tables synchronisées ◀──────── flux de lecture ──────── Service PowerSync (lecture seule) (règles de synchronisation)Deux canaux distincts :
- Écriture : commandes envoyées à l’API.
- Lecture : sous-ensemble de la base, poussé par PowerSync.
Les photos, pièces d’identité, signatures et traces GPS brutes passent par un troisième canal, le stockage objet (section 10). Elles ne transitent pas par PowerSync (EC-029).
3. Enveloppe de commande
Section intitulée « 3. Enveloppe de commande »Chaque commande est un document JSON à enveloppe fixe.
| Champ | Type | Rôle |
|---|---|---|
id |
UUID v7 | Généré sur le téléphone. Clé d’idempotence. |
type |
texte | Type de commande, ex. collecte.enregistrer (section 4). |
version_schema |
entier | Version du JSON Schema du payload. |
organisation_id |
UUID | Organisation de l’agent. |
appareil_id |
UUID | Appareil enrôlé. |
utilisateur_id |
UUID | Agent auteur. |
sequence |
entier | Séquence locale de l’appareil. Commence à 1, ne revient jamais à zéro. |
cree_le_appareil |
horodatage | Heure du téléphone au moment de la saisie. |
horloge |
objet ou nul | ref_serveur (dernière heure serveur connue) et ms_depuis_ref (temps écoulé mesuré par l’horloge monotone). Nul si le téléphone a redémarré depuis. |
reference_provisoire |
texte ou nul | BR-{code appareil}-{AAAAMMJJ}-{NNNN} pour les commandes qui produisent un document de terrain (EC-009). |
versions |
objet | Versions utilisées par le téléphone : pack, paramètres, application. |
pieces_jointes |
liste | fichier_id, sha256, role (signature, photo, piece_identite, trace_gps…). |
payload |
objet | Contenu propre au type, validé par son JSON Schema. |
empreinte |
texte | SHA-256 du JSON canonique (RFC 8785) de tous les champs sauf empreinte. Reprise dans le QR code du document de terrain. |
Exemple :
{ "id": "0192f3a4-7c1e-7b2a-9d41-6f0c2e8a1b37", "type": "collecte.enregistrer", "version_schema": 1, "organisation_id": "0192a0d1-…", "appareil_id": "0192b8f0-…", "utilisateur_id": "0192b8e2-…", "sequence": 42, "cree_le_appareil": "2026-10-14T09:12:31.412Z", "horloge": { "ref_serveur": "2026-10-13T17:40:02.000Z", "ms_depuis_ref": 55949412 }, "reference_provisoire": "BR-K7Q2-20261014-0042", "pieces_jointes": [ { "fichier_id": "0192f3a4-8a02-…", "sha256": "9f2c…", "role": "signature" } ], "payload": { "collecte_id": "0192f3a4-7c1e-7000-…", "campagne_id": "…", "producteur_id": "…", "parcelle_id": "…", "produit_id": "…", "contrat_id": "…", "pesees": [{ "brut_g": 105000, "tare_g": 5000 }], "humidite_cp": 1400, "impuretes_cp": 150, "calcul_telephone": { "net_g": 100000, "refaction_g": 2000, "net_marchand_g": 98000 }, "qualite_declaree": { "UE": "bio" } }, "empreinte": "3b8e…"}Deux identifiants distincts. L’id identifie la commande. Les entités créées (ici collecte_id) ont leur propre UUID v7, lui aussi généré sur le téléphone. Le serveur l’utilise comme clé primaire. C’est ce qui permet à une commande suivante de référencer une entité créée hors-ligne, et au téléphone de faire le lien entre sa saisie provisoire et la donnée synchronisée.
4. Catalogue des commandes v1
Section intitulée « 4. Catalogue des commandes v1 »4.1 Commandes autorisées hors-ligne
Section intitulée « 4.1 Commandes autorisées hors-ligne »| Type | Lot | Contenu principal | Contrôles serveur propres | Motifs de contrôle possibles |
|---|---|---|---|---|
producteur.enregistrer |
3 | Identité, groupement, pièce d’identité, photo | Doublon de pièce d’identité | doublon_identite |
producteur.modifier |
3 | Champs modifiés uniquement, version_base |
Modification concurrente | modification_concurrente |
parcelle.enregistrer |
3 | Parcelle, surface déclarée, premier relevé | Validité, chevauchement | polygone_invalide, chevauchement_parcelle |
parcelle.relever |
3 | Nouvelle version de polygone, métadonnées GPS-03, trace brute | Validité, chevauchement, recalcul depuis la trace | polygone_invalide, chevauchement_parcelle, ecart_calcul |
collecte.enregistrer |
3 | Pesées, humidité, impuretés, qualité, signature, calcul du téléphone | Capacité, contrat, certificat, recalcul | capacite_depassee, reference_inactive, ecart_calcul |
collecte.signaler_erreur |
3 | Collecte visée, motif, correction proposée | – | erreur_signalee_agent (toujours) |
recu.reimprimer |
3 | Document visé, nombre d’exemplaires | – | – (trace du duplicata) |
livraison.etablir_bon |
3 | Collectes regroupées, véhicule, destination | Collectes existantes et non expédiées | reference_inconnue |
intrant.remettre |
4 | Producteur, intrant, quantité, valeur, avance | Intrant autorisé en bio | intrant_non_autorise |
inspection.enregistrer |
4 | Fiche, version du formulaire, réponses, photos | Conflit d’intérêts (AV1-07) | conflit_interets |
recolte.estimer |
4 | Parcelle, date prévue, quantité estimée | Parcelle active | reference_inactive |
non_conformite.constater |
4 | Membre ou parcelle, constat, gravité, photos | – | – |
Chaque type a son JSON Schema dans packages/schemas/commandes/{type}.v{n}.json. Le serveur accepte la version courante et la précédente (N et N-1).
Toute commande peut aussi recevoir les motifs communs de la section 6.3.
4.2 Opérations interdites hors-ligne
Section intitulée « 4.2 Opérations interdites hors-ligne »Elles n’existent que dans le back-office, en ligne : valider un lot, transformer, vendre ou expédier, valider une capacité, payer, dévalider, annuler, lever un contrôle, modifier un paramètre ou un pack, enrôler ou révoquer un appareil.
L’application terrain n’expose aucun écran pour ces opérations.
5. Côté téléphone
Section intitulée « 5. Côté téléphone »5.1 Tables locales
Section intitulée « 5.1 Tables locales »| Table | Nature PowerSync | Contenu |
|---|---|---|
Tables synchronisées (producteur, parcelle_version, solde_capacite…) |
Synchronisées | Données descendues du serveur. Lecture seule : aucune écriture applicative. |
commande |
Insertion seule | File d’envoi. Chaque ligne insérée part dans la file de téléversement PowerSync. |
commande_locale |
Locale uniquement | Copie complète de chaque commande, avec son état local (en_attente, traitee). Sert à l’affichage provisoire, à la réimpression et au diagnostic. |
commande_resultat |
Synchronisée | Issue de chaque commande de cet appareil, descendue par PowerSync. |
Les fichiers joints sont stockés dans le système de fichiers de l’application (Capacitor Filesystem), avec leur empreinte, jusqu’à leur envoi.
5.2 Enregistrer une commande
Section intitulée « 5.2 Enregistrer une commande »Dans une seule transaction locale :
- incrémenter le compteur de séquence de l’appareil ;
- construire l’enveloppe et calculer son empreinte (
packages/domain) ; - insérer dans
commande_localeet danscommande.
Le document de terrain (reçu, bon, fiche) est généré après cette transaction, à partir de l’enveloppe enregistrée. Un reçu ne peut donc jamais exister sans sa commande.
5.3 Envoi
Section intitulée « 5.3 Envoi »- Le connecteur PowerSync (
uploadData) lit la file d’envoi dans l’ordre et envoie les commandes par lots de 50 au plus àPOST /v1/sync/commandes. - Réponse 200 : la transaction PowerSync est marquée terminée, même si certaines commandes sont en contrôle.
- Erreur réseau, 5xx ou 429 : nouvel essai avec délai croissant. La file est conservée telle quelle.
- 401 : renouvellement du jeton avec le jeton hors-ligne, puis nouvel essai.
- Arrêt pour trou de séquence (section 6.5) : alerte technique remontée au support. Ce cas signale un défaut de l’application.
- Les fichiers joints sont envoyés en parallèle, indépendamment des commandes (section 10).
5.4 Affichage provisoire
Section intitulée « 5.4 Affichage provisoire »Pour chaque entité saisie sur le terrain, une vue locale combine :
- les lignes synchronisées ;
- les commandes de
commande_localesans résultat, projetées sous la même forme.
La fusion se fait sur l’identifiant de l’entité (collecte_id, producteur_id…). Quand la donnée synchronisée arrive, elle remplace la ligne provisoire.
Rendu : une ligne provisoire porte sa référence grise précédée de « prov. » (design system, composant « Référence »). Une ligne dont la commande est en contrôle affiche « En vérification », sans le détail du motif.
5.5 Contrôles locaux
Section intitulée « 5.5 Contrôles locaux »Le téléphone exécute les mêmes règles que le serveur, avec les données dont il dispose. Leur résultat est indicatif.
| Situation | Comportement du téléphone |
|---|---|
| Capacité restante insuffisante | Message « Capacité dépassée de 120 kg. La collecte sera vérifiée par le RSCI. » L’agent peut poursuivre. |
| Polygone invalide | Enregistrement refusé, avec proposition de reprendre le relevé. L’agent peut forcer l’enregistrement « pour contrôle ». |
| Producteur possiblement en doublon | Avertissement avec la fiche existante. L’agent choisit : utiliser la fiche existante ou créer quand même. |
| Durée hors-ligne supérieure à 7 jours | Plus aucune nouvelle saisie. La file existante est conservée. |
| Données non synchronisées depuis plus de 24 h | Pastille d’état de synchronisation en terre. |
5.6 Rétention et purge
Section intitulée « 5.6 Rétention et purge »Une ligne de commande_locale est purgée quand les trois conditions sont réunies :
- son résultat est descendu ;
- l’entité correspondante est synchronisée, ou la commande est en contrôle ;
- 7 jours se sont écoulés depuis la réception du résultat (paramètre).
Les fichiers joints sont purgés dès que le serveur confirme leur réception avec la bonne empreinte.
6. Traitement serveur
Section intitulée « 6. Traitement serveur »6.1 Points d’entrée
Section intitulée « 6.1 Points d’entrée »| Point d’entrée | Usage |
|---|---|
POST /v1/sync/commandes |
Envoi d’un lot de commandes |
GET /v1/sync/etat-appareil |
Dernière séquence reçue, statut de l’appareil, versions minimales, heure serveur |
POST /v1/sync/jeton |
Émission du jeton PowerSync (section 12) |
POST /v1/fichiers/url-envoi |
Adresse d’envoi signée pour un fichier joint |
POST /v1/appareils/enroler |
Enrôlement, en ligne uniquement |
6.2 Étapes de traitement
Section intitulée « 6.2 Étapes de traitement »Le lot est traité séquentiellement, une transaction par commande. Une commande validée reste acquise même si la suivante échoue.
- Authentification de l’appel : jeton Keycloak valide, audience
biotrace-api. - Verrou de l’appareil :
SELECT … FOR UPDATEsur la ligneappareil. Deux envois simultanés du même appareil sont ainsi sérialisés. - Identité : organisation, utilisateur et appareil de l’enveloppe doivent correspondre au jeton et à l’enrôlement.
- Idempotence : si l’
idest connu avec la même empreinte, le résultat existant est renvoyé, sans nouveau traitement. - Ordre :
sequencedoit valoirderniere_sequence + 1. - Empreinte recalculée et comparée.
- Forme : validation Ajv du
payloadselontypeetversion_schema. - Date d’opération (section 6.4).
- Droits à la date d’opération : utilisateur actif, permission correspondant au type, appareil non révoqué, délai hors-ligne respecté.
- Références : chaque entité référencée existe, est active et n’est pas elle-même en contrôle.
- Rejeu des calculs avec
packages/domainet la version de pack figée par la campagne. Comparaison aveccalcul_telephone. - Règles métier propres au type (section 4.1).
- Écriture, dans la transaction ouverte :
SET LOCAL app.organisation_id,app.utilisateur_id,app.appareil_id,app.commande_id, pour le journal d’audit ;- enregistrement de la commande brute ;
- écritures métier, numéro officiel, mouvements de matière ;
- entrées de la file de contrôle ;
- résultat (
commande_resultat) ; - tâches pg-boss (SMS, génération de documents) ;
appareil.derniere_sequenceetappareil.derniere_synchro.
Les contrôles de capacité verrouillent la ligne de solde concernée (FOR UPDATE). Deux agents qui collectent le même producteur au même moment sont donc traités l’un après l’autre.
6.3 Issues et motifs
Section intitulée « 6.3 Issues et motifs »Issues
| Issue | Écritures métier | Cas |
|---|---|---|
acceptee |
Oui | Tous les contrôles passent |
acceptee_controle |
Oui, entité marquée « en anomalie » | Au moins un motif de contrôle |
acceptee_controle sans écriture |
Non : seuls la commande et le contrôle sont enregistrés | Données illisibles, version obsolète, empreinte incohérente |
rejetee |
Non : seule la commande est enregistrée, avec une alerte de sécurité | Anomalie de sécurité (tableau ci-dessous) |
Motifs communs de contrôle
| Motif | Déclencheur |
|---|---|
donnees_illisibles |
payload non conforme à son schéma |
version_client_obsolete |
version_schema plus ancienne que N-1 |
empreinte_incoherente |
Empreinte recalculée différente de celle reçue |
apres_revocation |
Date d’opération postérieure à la révocation de l’appareil |
hors_ligne_depasse |
Date d’opération au-delà de 7 jours après la dernière synchronisation |
droit_manquant |
Permission absente à la date d’opération |
reference_inconnue |
Entité référencée introuvable |
reference_inactive |
Entité référencée retirée, expirée ou close (contrat, campagne, certificat) |
dependance_en_controle |
Entité référencée elle-même en contrôle |
ecart_calcul |
Calcul du serveur différent de celui du téléphone |
parametre_introuvable |
Aucune règle ne donne de valeur pour le cas traité (PC-07). L’opération est enregistrée, sans blocage |
horloge_invraisemblable |
Date d’opération impossible à établir (section 6.4) |
piece_manquante |
Fichier joint non reçu dans le délai |
module_inactif |
Module de la commande inactif à la date d’opération (EC-096). Commande conservée, sans écriture métier |
Rejets (sécurité uniquement)
| Code | Déclencheur |
|---|---|
identite_incoherente |
Organisation, utilisateur ou appareil différents du jeton ou de l’enrôlement |
identifiant_reutilise |
id connu avec une autre empreinte |
sequence_reutilisee |
Séquence déjà utilisée par une autre commande |
Alertes. Le résultat peut aussi porter des alertes non bloquantes, sans contrôle : horloge_corrigee, rendement_non_calibre, version_pack_differente.
6.4 Date d’opération et horloge
Section intitulée « 6.4 Date d’opération et horloge »- Si
horlogeest renseignée :estimation = ref_serveur + ms_depuis_ref. - Si l’écart entre
cree_le_appareiletestimationest inférieur ou égal à 10 minutes (paramètre), la date d’opération estcree_le_appareil. - Sinon, la date d’opération est
estimation, avec l’alertehorloge_corrigee. - Si
horlogeest nulle (redémarrage),cree_le_appareilest retenue si elle est comprise entre la dernière synchronisation de l’appareil et l’heure de réception. Sinon : motifhorloge_invraisemblable, et la date d’opération provisoire est l’heure de réception. - La date d’opération n’est jamais postérieure à l’heure de réception.
Le serveur conserve toujours les trois valeurs : heure du téléphone, date d’opération retenue, heure de réception.
6.5 Réponse
Section intitulée « 6.5 Réponse »{ "resultats": [ { "commande_id": "0192f3a4-7c1e-7b2a-9d41-6f0c2e8a1b37", "issue": "acceptee_controle", "motifs": ["capacite_depassee"], "alertes": [], "numero_officiel": "26A/COL/ANA/0137", "entites": { "collecte": "0192f3a4-7c1e-7000-…" }, "date_operation": "2026-10-14T09:12:31.412Z" } ], "derniere_sequence": 42, "heure_serveur": "2026-10-16T08:03:11.020Z", "arret": null}Si une commande a une séquence trop grande, le traitement s’arrête avant elle : "arret": { "raison": "sequence_attendue", "attendue": 40 }. Les commandes précédentes du lot restent traitées.
Le résultat est aussi écrit dans commande_resultat, synchronisé vers l’appareil. Si la réponse HTTP est perdue, le téléphone apprend quand même l’issue.
7. Règles de synchronisation (lecture)
Section intitulée « 7. Règles de synchronisation (lecture) »Mise en œuvre (Lot 1, A9, 01/10/2026). Les règles définitives sont dans
infra/powersync/sync-config.yaml(formatbucket_definitions, EC-076), avec cinq compartiments :globales,organisation,contrats_actifs(un par contrat d’achat actif),perimetre_agentetappareil. L’exemple ci-dessous reste indicatif. Voir Lot 1, séquence A9 pour les colonnes descendues, les colonnes exclues, les colonnes générées que PostgreSQL 16 ne réplique pas (EC-077) et le volume mesuré.
7.1 Paramètres du jeton
Section intitulée « 7.1 Paramètres du jeton »Le jeton PowerSync est émis par l’API (section 12). Il porte organisation_id, utilisateur_id et appareil_id. Toutes les règles filtrent sur ces valeurs.
7.2 Compartiments
Section intitulée « 7.2 Compartiments »| Compartiment | Paramètre | Contenu |
|---|---|---|
packs |
– | Versions de pack publiées utilisées par au moins une campagne ouverte |
organisation |
organisation_id |
Paramètres, campagnes ouvertes, produits, unités, contrats d’achat actifs, formulaires en vigueur, taux de change |
perimetre_agent |
groupements affectés à l’agent | Producteurs, parcelles et version de polygone en vigueur, statuts de conformité, certificats (résumé), capacités validées et soldes |
collectes_agent |
utilisateur_id |
Collectes et bons de livraison de l’agent, tant qu’ils sont marqués synchronisables |
inspection (Lot 4) |
utilisateur_id |
Plan d’inspection de l’inspecteur, historique des non-conformités ouvertes des membres concernés |
appareil |
appareil_id |
commande_resultat de l’appareil, statut de l’appareil |
Exemple, en syntaxe indicative (à caler sur la version de PowerSync retenue) :
bucket_definitions: organisation: parameters: SELECT request.jwt() ->> 'organisation_id' AS organisation_id data: - SELECT * FROM parametre WHERE organisation_id = bucket.organisation_id - SELECT * FROM campagne WHERE organisation_id = bucket.organisation_id AND ouverte = true
perimetre_agent: parameters: > SELECT groupement_id FROM affectation_agent WHERE utilisateur_id = request.jwt() ->> 'utilisateur_id' AND active = true data: - SELECT * FROM producteur WHERE groupement_id = bucket.groupement_id - SELECT * FROM parcelle_version_en_vigueur WHERE groupement_id = bucket.groupement_id - SELECT * FROM solde_capacite WHERE groupement_id = bucket.groupement_id
appareil: parameters: SELECT request.jwt() ->> 'appareil_id' AS appareil_id data: - SELECT * FROM commande_resultat WHERE appareil_id = bucket.appareil_id7.3 Contraintes de modélisation induites
Section intitulée « 7.3 Contraintes de modélisation induites »- Pas de jointure dans les règles de données. Chaque table descendue par périmètre porte directement
groupement_id, en plus d’organisation_id(EC-032). Cette colonne est tenue à jour par la base. - Pas de filtre sur une date glissante. Une colonne booléenne
synchronisablesur les collectes est remise àfalsepar une tâche nocturne pg-boss après 30 jours (paramètre). - Pas de PostGIS sur le téléphone. Les polygones descendent dans une colonne texte GeoJSON, tenue par un déclencheur à partir de la colonne
geometry. - Soldes précalculés.
solde_capacite(capacité validée, quantité consommée, reste) est mis à jour dans la même transaction que chaque collecte, pour que le téléphone ait un solde à jour sans calcul. - Budget. Cible : moins de 20 Mo de données synchronisées par téléphone. À mesurer au prototype sur un périmètre réaliste.
8. Conflits
Section intitulée « 8. Conflits »| Conflit | Détection | Traitement |
|---|---|---|
| Deux agents, même producteur, capacité dépassée | Au traitement de la collecte qui franchit le plafond (ordre de traitement serveur) | Les deux collectes sont acceptées. Celle qui franchit le plafond passe en contrôle capacite_depassee (EC-013). Le RSCI voit les deux. |
| Doublon de pièce d’identité | Au traitement de producteur.enregistrer |
Fiche créée, contrôle doublon_identite. Ni capacité ni paiement tant que non résolu (EC-031). |
| Collecte sur un producteur en contrôle | Étape 10 | Collecte acceptée, contrôle dependance_en_controle. |
| Modification concurrente d’un producteur | version_base antérieure à une modification d’un autre auteur sur les mêmes champs |
Les champs non concernés sont appliqués. Les champs en conflit gardent la valeur du serveur, la proposition de l’agent va en contrôle modification_concurrente. |
| Deux relevés du même polygone | Deux parcelle.relever sur la même parcelle |
Chacun crée une version. La version en vigueur est celle dont la date d’opération est la plus récente. Si une capacité validée existe sur l’ancienne surface, elle repasse en validation (EC-012). |
| Chevauchement de parcelles | ST_Intersects sur les versions en vigueur, au-delà d’une surface minimale (paramètre) |
Contrôle chevauchement_parcelle. Capacité non validable tant que non résolu. |
| Référentiel périmé sur le téléphone | Version de pack ou de paramètres différente de celle de la campagne | Recalcul serveur. Alerte version_pack_differente. Contrôle ecart_calcul si le résultat diffère. |
| Contrat ou campagne clos entre-temps | Étape 10 | Contrôle reference_inactive. |
| Appareil révoqué | Étape 9 | Commandes antérieures à la révocation traitées normalement. Commandes postérieures en contrôle apres_revocation. |
9. File de contrôle
Section intitulée « 9. File de contrôle »Un mécanisme unique pour tous les motifs.
| Motif | Traité par | Décisions possibles | Effet tant que le contrôle est ouvert |
|---|---|---|---|
capacite_depassee |
RSCI | Régularisation, déclassement, non-conformité | Lot en anomalie : ni transformation, ni vente sous le référentiel bio |
doublon_identite |
RSCI ou administrateur | Fusion, confirmation (personnes distinctes) | Ni capacité ni paiement pour la nouvelle fiche |
polygone_invalide, chevauchement_parcelle |
RSCI | Correction (nouveau relevé), confirmation | Capacité non validable (AV1-01) |
modification_concurrente |
Administrateur | Valeur retenue, champ par champ | Valeur du serveur conservée |
ecart_calcul |
RSCI | Confirmation de la valeur serveur, correction | Paiement de la collecte bloqué |
apres_revocation, hors_ligne_depasse, droit_manquant, horloge_invraisemblable |
RSCI et administrateur | Confirmation, écart manuel motivé | Entité en anomalie |
reference_inconnue, reference_inactive, dependance_en_controle |
RSCI | Correction, confirmation | Entité en anomalie |
donnees_illisibles, version_client_obsolete, empreinte_incoherente, module_inactif |
Support et RSCI | Reprise manuelle dans le back-office, liée à la commande | Aucune entité créée |
piece_manquante |
RSCI | Pièce fournie, confirmation sans pièce | Entité en anomalie |
erreur_signalee_agent |
RSCI | Correction (dévalidation, Lot 2), confirmation | Paiement de la collecte bloqué |
Règles communes :
- l’auteur de la commande ne peut pas lever son propre contrôle (vérifié par la base) ;
- toute décision exige un motif écrit ;
- un contrôle levé ne se modifie plus ;
- l’écart manuel d’une commande (équivalent d’un rejet) est une décision tracée comme les autres, jamais une suppression ;
- le bandeau « À traiter » du back-office affiche les contrôles ouverts, triés par ancienneté et par effet bloquant.
10. Pièces jointes
Section intitulée « 10. Pièces jointes »- Sur le téléphone, le fichier est enregistré localement, son SHA-256 calculé, et un
fichier_id(UUID v7) attribué. - La commande référence le fichier par
fichier_idetsha256. - Quand le réseau le permet, l’application demande une adresse d’envoi signée (
POST /v1/fichiers/url-envoi) et envoie le fichier au stockage objet. - L’API vérifie l’empreinte à réception. Si elle diffère, le fichier est marqué
empreinte_invalideet renvoyé. - La commande et le fichier peuvent arriver dans n’importe quel ordre. Une commande arrivée avant son fichier est traitée normalement. Le fichier est « attendu ».
- Un fichier toujours attendu après 14 jours (paramètre) ouvre un contrôle
piece_manquante.
La trace GPS brute (GPS-03) suit le même chemin, au format GeoJSON.
11. Numéros, documents et SMS
Section intitulée « 11. Numéros, documents et SMS »- Numéro officiel. Attribué dans la transaction de traitement, y compris pour une commande acceptée en contrôle : la marchandise existe. Il est lié à la référence provisoire, qui reste consultable.
- QR code du document de terrain. Il porte la référence provisoire, l’
idde commande et les 16 premiers caractères de l’empreinte. La page de vérification (Lot 5) les compare aux données du serveur. - SMS au producteur. Une tâche pg-boss est inscrite dans la transaction d’une collecte acceptée ou acceptée en contrôle. Le SMS confirme la référence, la date et le poids net marchand. Il ne mentionne jamais un contrôle.
12. Session, enrôlement et révocation
Section intitulée « 12. Session, enrôlement et révocation »Le détail de la configuration est dans la page « Configuration Keycloak ». Ce qui concerne le protocole :
Enrôlement (en ligne, au bureau)
- Un administrateur génère dans le back-office un code d’enrôlement pour un agent, valable 24 heures.
- L’agent se connecte à l’application terrain (Keycloak, avec le jeton hors-ligne) et saisit le code.
POST /v1/appareils/enrolervérifie le code, crée l’appareil, lui attribue un code court unique (EC-009) et renvoie la séquence de départ (1) et l’heure serveur.- L’agent définit son code PIN local.
Jeton PowerSync
L’API émet le jeton PowerSync (durée 5 minutes) après avoir vérifié le jeton Keycloak et le statut de l’appareil. Le service PowerSync valide ce jeton avec la clé publique de l’API. Un appareil révoqué ne reçoit donc plus aucune donnée, dès la première tentative.
Révocation
- L’administrateur révoque l’appareil dans le back-office. L’API révoque la session hors-ligne dans Keycloak et note la date de révocation.
- À la reconnexion, l’application apprend la révocation par
GET /v1/sync/etat-appareil. - Elle envoie d’abord sa file de commandes, puis efface ses données locales. Les commandes postérieures à la révocation passent en contrôle
apres_revocation, sans rejet (découpage).
13. Paramètres du protocole
Section intitulée « 13. Paramètres du protocole »| Paramètre | Valeur par défaut |
|---|---|
| Taille maximale d’un lot d’envoi | 50 commandes |
| Écart d’horloge toléré | 10 minutes |
| Durée maximale de saisie hors-ligne | 7 jours |
| Alerte de données non synchronisées | 24 heures |
| Rétention des commandes locales après résultat | 7 jours |
Délai avant piece_manquante |
14 jours |
| Durée de synchronisation des collectes de l’agent | 30 jours |
| Versions de schéma acceptées | N et N-1 |
| Validité d’un code d’enrôlement | 24 heures |
| Surface minimale de chevauchement signalée | 10 m² |
Tous sont versionnés par organisation, sauf la taille de lot et les versions de schéma, qui sont techniques.
14. Scénarios de recette du protocole
Section intitulée « 14. Scénarios de recette du protocole »| id | Scénario | Résultat attendu |
|---|---|---|
| SP-01 | La même commande est envoyée deux fois | Un seul enregistrement ; même résultat renvoyé |
| SP-02 | La réponse HTTP est perdue après traitement | Le téléphone reçoit le résultat par commande_resultat ; rien n’est dupliqué |
| SP-03 | Dernière séquence reçue : 40. La 41 manque, la 42 arrive | Arrêt avant 42, attendue: 41 ; aucune commande perdue |
| SP-04 | Même id, contenu modifié |
Rejet identifiant_reutilise, alerte de sécurité |
| SP-05 | Horloge du téléphone décalée de 3 jours, horloge renseignée |
Date d’opération corrigée, alerte horloge_corrigee |
| SP-06 | Téléphone redémarré, horloge incohérente | Contrôle horloge_invraisemblable |
| SP-07 | Deux agents, même producteur, capacité dépassée | Deux collectes acceptées, une en contrôle capacite_depassee |
| SP-08 | Producteur créé hors-ligne, en doublon, puis collecté | Fiche et collecte acceptées, deux contrôles, aucun paiement possible |
| SP-09 | Modification concurrente d’un même champ | Valeur serveur conservée, proposition en contrôle |
| SP-10 | Fichier joint arrivé après la commande, puis jamais arrivé | Traitement normal, puis piece_manquante au délai |
| SP-11 | Appareil révoqué avec 12 commandes en file, dont 5 postérieures | 7 acceptées, 5 en contrôle apres_revocation, données locales effacées après envoi |
| SP-12 | Commande en version de schéma N-2 | Contrôle version_client_obsolete, commande conservée |
| SP-13 | Payload altéré en transit | Contrôle empreinte_incoherente |
| SP-14 | Trois jours hors-ligne : 300 collectes, 40 relevés, 60 photos | Synchronisation complète sans perte ni doublon ; mesure de la durée et du volume |
| SP-15 | L’auteur tente de lever son propre contrôle | Refus par la base |
État d’exécution (30/09/2026)
Section intitulée « État d’exécution (30/09/2026) »Tests dans apps/api/test/ (pnpm --filter @biotrace/api test:integration), contre PostgreSQL et l’application Nest.
| id | État |
|---|---|
| SP-01 à SP-06 | Exécutés |
| SP-07 à SP-10 | Écrits, sautés : capacité (lots 1 et 2), types de commande du lot 3, stockage objet |
| SP-11 | Exécuté côté API (7 acceptées, 5 en contrôle) ; l’effacement des données locales relève du téléphone |
| SP-12, SP-13 | Exécutés |
| SP-14 | Version réduite côté API : 300 commandes en 6 lots ; volume complet au lot 3 |
| SP-15 | Couvert en base : db/tests/sb-12.sql |
Précisions issues de la mise en œuvre
- Les rejets de sécurité s’écrivent dans
sync.rejet(EC-043), pas danssync.commande. - Un type de commande inconnu, ou une version de schéma plus récente que la courante :
acceptee_controlesans écriture, motifdonnees_illisibles(EC-044). appareil.derniere_synchros’inscrit à la fin du lot, pas à chaque commande : pendant un lot, « hors_ligne_depasse » et la date d’opération se jugent par rapport à la synchronisation précédente.- Une enveloppe illisible (forme de l’enveloppe, §3) fait refuser le lot entier en 400 : rien n’est traité.
- Le jeton Keycloak 26.0.5 porte le nom de l’organisation ; l’API la retrouve par son code (EC-045).
- La permission requise se lit dans la table type → permission de
packages/domain(permissionRequise, EC-038 : identifiants anglais,collecte.enregistrer→collection.record). L’utilisateur doit être actif ; la permission se juge à la date d’opération. - Les paramètres du §13 sont des valeurs par défaut dans le code ; leur lecture par organisation viendra avec le module
parametres(PC-01 à PC-08). - Non faits :
POST /v1/fichiers/url-envoietPOST /v1/appareils/enroler. - Limite : une
reference_provisoiredéjà utilisée par une autre commande provoque une erreur 500 (contrainte d’unicité), non traitée.
SP-07 et SP-14 couvrent les critères de fin du Lot 3. SP-01 à SP-04 couvrent le critère du Lot 0 (« rejouée, validée ou rejetée sans perte ni doublon »).
15. Points à vérifier par prototype en S1
Section intitulée « 15. Points à vérifier par prototype en S1 »Préparé, vérifié dans un navigateur, non exécuté sur téléphone. Test sur téléphone reporté à un lot ultérieur, hors critères de fin du Lot 0 (EC-041). Voir Prototype PowerSync sous Capacitor : mode d’emploi, résultats déjà obtenus et grille à remplir. Le point 2 exige un téléphone.
Ces points ne changent pas le protocole, mais peuvent changer sa mise en œuvre.
- Tables en insertion seule de PowerSync : confirmer leur comportement (persistance locale, ordre de la file) et la combinaison avec la table locale
commande_locale. - PowerSync sous Capacitor : SDK dédié avec SQLite natif, ou SDK web dans la WebView. Mesurer la tenue sur trois jours de données et après un redémarrage.
- Réplication des colonnes GeoJSON tenues par déclencheur.
- Jeton émis par l’API : validation par PowerSync via la clé publique (JWKS) de l’API.
- Budget de volume sur un téléphone d’entrée de gamme, avec un périmètre de 2 000 producteurs.
16. Mises à jour induites
Section intitulée « 16. Mises à jour induites »- Découpage. La décision « Stockage PWA » devient caduque avec EC-033. Elle est remplacée par la base locale de l’application Capacitor et le stockage sécurisé Android.
- ADR à rédiger dans
apps/docs/src/content/docs/technique/adr/:- modèle d’écriture par commandes et file de contrôle unique ;
- jeton PowerSync émis par l’API ;
- application terrain sous Capacitor (EC-033).
- Hors périmètre v1, à réexaminer au Lot 7 : signature de chaque commande par une clé propre à l’appareil (stockée dans le Keystore Android), pour renforcer la preuve d’origine.