Skip to content

Jalons d'intégrité documentaire

Le problème que ça résout

Depuis la mission signature électronique, l'API calcule et stocke un hash du contenu du document à chaque signature (signatures.document_hash). Un hash prouve qu'un contenu présenté correspond à ce qui a été signé — mais il ne permet jamais de reconstituer ce que le document contenait réellement à un instant passé. Concrètement : si un litige survient entre la collecte et la livraison ("la marchandise déclarée à la livraison n'est pas celle collectée"), rien ne permettait de comparer les deux états, seulement de vérifier qu'un état donné correspondait à un hash donné.

C'est l'équivalent numérique d'un problème bien connu sur le CMR papier : la lettre de voiture existe en plusieurs exemplaires carbone (expéditeur, transporteur, destinataire), et détecter une falsification consiste à superposer les calques pour repérer une divergence — jamais à empêcher quelqu'un d'écrire autre chose sur son exemplaire après séparation. Cette mission construit l'équivalent numérique exact : un instantané complet du contenu légal à chaque jalon du cycle de vie, et un endpoint qui superpose deux jalons pour vous montrer précisément ce qui a changé.

Ce que ce mécanisme ne fait pas : il ne verrouille aucun champ en écriture. goods_lines, les dates planifiées, etc. restent modifiables après ISSUED, exactement comme avant cette mission — c'est un mécanisme de détection, pas un contrôle d'accès. Si vous avez besoin qu'un champ précis devienne immuable après un certain point du cycle de vie, c'est une question distincte, à traiter séparément.

Les jalons capturés

Jalon Déclenché par
DRAFT_CREATED POST /v1/freightdocuments
ISSUED POST /v1/freightdocuments/{id}/issue
COLLECTION_SIGNED Signature de type COLLECTION complétée
COLLECTION_ACCEPTANCE_SIGNED Signature de type COLLECTION_ACCEPTANCE complétée (le chauffeur/transporteur, au moment de la collecte — voir Signature électronique)
HANDOVER_SIGNED Signature de type HANDOVER complétée
ACCEPTANCE_SIGNED Signature de type ACCEPTANCE complétée
DELIVERY_SIGNED Signature de type DELIVERY complétée — la transition qui en résulte est directement CLOSED (voir Cycle de vie)
CLOSED Valeur historique du jalon CLOSED pour les documents clôturés avant la refonte du modèle de statuts (transition DELIVEREDCLOSED, alors déclenchée par une pièce jointe POD) ; aucun nouveau document ne produit ce jalon séparément — DELIVERY_SIGNED couvre déjà l'instant de la signature qui clôture directement le document (voir Cycle de vie)

DRAFT_CREATED est informatif — un DRAFT reste librement modifiable, donc ce jalon ne fait pas encore partie de la chaîne de détection de falsification à proprement parler. Celle-ci commence réellement à ISSUED, une fois le document juridiquement engageant.

Lister les jalons disponibles

GET /v1/freightdocuments/{id}/snapshots

Requiert la permission VIEW — cette donnée n'est jamais plus sensible que ce qu'un GET normal sur le document montre déjà.

{
  "snapshots": [
    { "id": "...", "milestone": "DRAFT_CREATED", "content_hash": "a1b2...", "created_at": "..." },
    { "id": "...", "milestone": "ISSUED", "content_hash": "c3d4...", "created_at": "..." }
  ]
}

Réponse volontairement légère (pas de contenu complet) — c'est l'enumération de ce qui est disponible à comparer, pas un export de contenu.

Comparer deux jalons — la fonctionnalité centrale

GET /v1/freightdocuments/{id}/snapshots/compare?from=ISSUED&to=DELIVERY_SIGNED

Retourne un diff structuré : une liste plate de changements, chacun avec un chemin explicite (notation point/crochet, valable pour n'importe quelle partie du document, y compris un champ imbriqué dans un rôle ou une ligne de marchandise) et l'ancienne/nouvelle valeur.

{
  "from_milestone": "ISSUED",
  "to_milestone": "DELIVERY_SIGNED",
  "from_snapshot_id": "...",
  "to_snapshot_id": "...",
  "changes": [
    {
      "field": "goods_lines[0].nature_of_goods",
      "from": "Grain de café",
      "to": "Autre chose"
    },
    {
      "field": "status",
      "from": "ISSUED",
      "to": "DELIVERED"
    }
  ],
  "comparison_method": "Items inside a list (goods_lines, roles) are matched by content, not raw position: an item that is byte-identical on both sides is never reported as changed, even if it moved — only a genuinely different item at its resolved position is. [...]"
}

comparison_method est toujours présent, à lire avant de tirer une conclusion de changes sur quelle ligne précise a changé : les éléments d'une liste (goods_lines, roles) sont appariés par contenu, pas par position brute — une ligne insérée au milieu d'une liste n'est jamais faussement rapportée comme "la ligne suivante a été modifiée", elle apparaît comme une insertion propre à sa position réelle.

Dans cet exemple, la marchandise déclarée à l'émission n'est plus celle déclarée à la livraison — exactement le type de divergence que ce mécanisme existe pour révéler. Le changement de status lui-même apparaît aussi dans le diff : c'est une évolution légitime attendue entre ces deux jalons, pas une anomalie — c'est à vous d'interpréter le diff selon le contexte, l'endpoint ne juge pas ce qui est normal.

Une ligne ajoutée ou supprimée dans une liste (goods_lines, roles) apparaît comme une seule entrée avec la valeur complète de la ligne, pas un champ par champ :

{ "field": "goods_lines[1]", "from": null, "to": { "nature_of_goods": "...", "package_count": 5, "...": "..." } }

Second usage : reconstituer le PDF à un jalon passé

Les jalons ne servent plus seulement à comparer deux états entre eux : GET .../pdf?milestone=<jalon> (voir Pièces jointes & PDF) s'appuie sur le même mécanisme pour régénérer le PDF exactement comme il existait à ce jalon, plutôt que dans son état courant — le contenu du snapshot fournit le statut/rôles/marchandises/références/dates à cet instant, les signatures et pièces jointes étant filtrées par leur propre horodatage pour ne montrer que celles qui existaient déjà. Demander un jalon non encore atteint renvoie la même erreur freight_document.snapshot_not_found que l'endpoint de comparaison ci-dessus — pas un code distinct.

Codes d'erreur spécifiques

Code HTTP Sens
freight_document.snapshot_not_found 404 from/to nomment un jalon réel, mais ce document n'a pas encore de instantané pour l'un des deux (ex : comparer DELIVERY_SIGNED avant toute signature de livraison).
freight_document.invalid_snapshot_comparison 400 from/to ne correspondent à aucun jalon valide, ou from est égal à to.

Voir Codes d'erreur pour le format complet.