Aller au contenu

Projet indépendant de R&D · Cologne

Traçabilité de bout en bout

Une seule identité traçable relie les étapes pertinentes, de la réception à la sortie qui en résulte.

Non normatif

Version du Companion
1.0
Se rapporte à COADF Core
2.2
Statut
À jour
Dernière relecture
Principes COADF
P-4

Propriété d'architecture

COADF P-4 est publié en entier. Un identifiant est créé à la réception d'un document et voyage avec chaque étape de traitement, chaque évaluation de confiance, chaque décision humaine et la sortie publiée. La piste est en ajout seul, une seule requête reconstruit la chaîne, et la chaîne s'exporte en JSON pour quelqu'un qui n'a pas construit le système. Le schéma publié de la piste d'audit nomme les champs : trace_id, timestamp, event_type, actor, input, output, decision, source_hash et immutable.

Deux propriétés la portent. La continuité : chaque étape porte la même identité, à travers chaque frontière qu'elle franchit. La persistance : l'enregistrement de chaque étape subsiste, et personne ne peut le modifier pour que la réponse paraisse meilleure plus tard.

Pourquoi elle compte

La question à laquelle P-4 répond arrive tard et de l'extérieur. Des mois après la publication, quelqu'un demande d'où vient une valeur, ce qui l'a lue et qui l'a examinée. La réponse doit pouvoir être reconstruite par une personne qui n'a pas construit le système, à partir d'enregistrements que personne n'a pu réécrire entre-temps.

Une trace distribuée n'est pas une piste d'audit métier

C'est la distinction que la plupart des mises en œuvre brouillent, parce que les outils se ressemblent : tous deux ont un identifiant, tous deux traversent des services, tous deux montrent des étapes dans l'ordre. Ils répondent à des questions différentes et font des promesses différentes.

Trace distribuée et piste d'audit, comparées
AspectTrace distribuéePiste d'audit
FinalitéExpliquer une exécution : latence, erreurs, structure des appelsÉtablir ce qui est arrivé à une transaction, et qui a agi
IdentitéUne trace par requête ou par job, portée dans l'en-tête W3C traceparentUn trace_id par transaction métier, créé à la réception
Durée de vieLa durée de la requête ; une rétention qui se compte en joursAussi longtemps que les enregistrements qu'elle justifie
ÉchantillonnageL'échantillonnage est normal et permisPas d'échantillonnage : une entrée manquante est un défaut
MutabilitéLes pipelines filtrent, suppriment et réécrivent selon leur configurationEn ajout seul : une correction est une nouvelle entrée
ContenuSpans, durées, attributs choisis pour le diagnosticÉvénements métier : entrée, sortie, décision, acteur

La spécification W3C Trace Context définit l'en-tête traceparent, et les SDK OpenTelemetry propagent tracecontext et baggage par défaut. Cette identité vaut pour une exécution. Dans OpenTelemetry, une trace qui n'est pas échantillonnée n'est pas exportée, et le processeur de filtrage du Collector supprime la télémétrie qui satisfait une condition. Ces deux comportements sont corrects pour le diagnostic et disqualifiants pour la preuve.

Les deux se corrèlent : enregistrer le trace_id d'audit comme attribut sur chaque span, et l'identifiant de la trace distribuée dans les journaux. Aucun des deux ne doit devenir l'autre. Une seule transaction métier s'étend couramment sur plusieurs traces distribuées : une requête de réception, un job de traitement asynchrone, une décision prise des jours plus tard, la sortie.

La corrélation copie l'identifiant d'audit dans la télémétrie, et la télémétrie franchit d'autres frontières : des exportateurs, des fournisseurs, des règles d'accès et une rétention généralement plus lâches que celles du stockage d'audit. Il faut qualifier l'identifiant avant qu'il ne sorte : une référence opaque, jamais une donnée personnelle ni un secret. Là où l'identifiant d'audit porte un sens propre, la corrélation passe par une référence distincte et non sensible.

Architecture d'exemple · Non normative

Traces distribuées et une seule piste d'audit

Exemple d'architecture, non normatif : en haut, quatre traces distribuées distinctes, une par requête ou tâche, et en dessous une seule piste d'audit dont les entrées illustratives partagent un même trace_id.Couloir du haut, les traces distribuées : quatre traces distinctes, une pour une requête de réception, une pour une tâche de traitement, une pour une décision prise plusieurs jours plus tard et une pour une sortie. Elles ne sont pas reliées entre elles. Couloir du bas, la piste d'audit : quatre entrées illustratives, réception, traitement, décision et sortie, qui portent toutes le même trace_id, persistées séparément de la télémétrie et non échantillonnées. Une note dans le couloir indique que les événements sont illustratifs, et non un workflow COADF obligatoire. Une ligne en tirets relie chaque trace à l'entrée qui lui correspond, étiquetée comme attribut de span portant le trace_id d'audit.Traces distribuées : contexte d'exécution, peuvent être échantillonnées, durée de vie courtePiste d'audit : un seul trace_id, en ajout seul, non échantillonnéeTrace Arequête de réceptionTrace Btâche de traitementTrace Cdécision, jours plus tardTrace DsortieréceptiontraitementdécisionsortieÉvénements illustratifs, et non un workflow COADF obligatoire ; le même trace_id sur chaque entréeattribut de span
  • Corrélation par le trace_id d'audit
Une transaction métier, quatre traces distribuées. La télémétrie peut être échantillonnée et a une durée de vie courte ; les entrées d'audit sont persistées séparément et ne sont pas échantillonnées. Elles sont corrélées par le trace_id d'audit, enregistré sur chaque trace comme attribut de span. Les événements sont illustratifs, et non un workflow COADF obligatoire.

Description textuelle. Couloir du haut, les traces distribuées : quatre traces distinctes, une pour une requête de réception, une pour une tâche de traitement, une pour une décision prise plusieurs jours plus tard et une pour une sortie. Elles ne sont pas reliées entre elles. Couloir du bas, la piste d'audit : quatre entrées illustratives, réception, traitement, décision et sortie, qui portent toutes le même trace_id, persistées séparément de la télémétrie et non échantillonnées. Une note dans le couloir indique que les événements sont illustratifs, et non un workflow COADF obligatoire. Une ligne en tirets relie chaque trace à l'entrée qui lui correspond, étiquetée comme attribut de span portant le trace_id d'audit.

Stratégies de mise en œuvre valables

Créer une seule fois, à la réception

L'identifiant est créé là où la transaction entre, et nulle part ailleurs. Chaque composant ultérieur l'exige et aucun n'en génère. Un identifiant manquant est une erreur, jamais une raison d'en créer un nouveau, et une valeur par défaut dans un modèle de message est la façon la plus courante d'enfreindre cette règle sans que personne ne le remarque.

Le porter explicitement à travers chaque frontière

  • Dans le processus : un argument de fonction ou le contexte de la requête.
  • Entre services : un champ du contrat de requête ou de message, là où un schéma peut l'exiger.
  • Dans le travail planifié : un argument du job.
  • Au repos : une colonne sur chaque ligne stockée.

Les bibliothèques de propagation portent d'elles-mêmes le contexte d'exécution. L'identité d'audit fait partie du contrat de l'application et a sa place dans la charge utile.

Persister, en ajout seul

Plusieurs réalisations sont valables, et elles se combinent :

  • Une persistance en insertion seule. Le dépôt offre l'ajout et la lecture, et rien d'autre.
  • Les privilèges de base de données. Le rôle de l'application a INSERT et SELECT sur la piste, et ni UPDATE, ni DELETE, ni TRUNCATE.
  • Des triggers qui refusent les réécritures, en défense en profondeur pour les sessions qui détiennent des droits plus larges, avec un trigger de niveau instruction pour TRUNCATE, parce que dans PostgreSQL TRUNCATE ne déclenche pas les triggers ON DELETE.
  • Un modèle d'événements immuable, dans lequel une correction est un nouvel événement qui en remplace un antérieur.
  • Une convention de remplacement. La valeur courante d'un attribut est sa dernière entrée ; l'historique, c'est l'ensemble de ses entrées.

L'event sourcing est une façon d'obtenir un historique en ajout seul, et pas la seule ; voir la note technique.

Écrire l'entrée avec le changement

Enregistrer l'entrée d'audit dans la même transaction de base de données que le changement d'état qu'elle décrit, ou via un outbox transactionnel, afin que l'un ne puisse pas exister sans l'autre.

Rendre les relances idempotentes

Horodater l'étape au moment où elle s'exécute, et renvoyer la même entrée lors d'une relance. Donner aux entrées une clé unique naturelle, pour qu'une écriture répétée soit absorbée plutôt que dupliquée. Dans PostgreSQL, ON CONFLICT DO NOTHING ignore une ligne en conflit avec une contrainte ou un index unique au lieu de lever une erreur, et c'est précisément pourquoi cela ne peut pas être toute la réponse : un conflit n'est pas toujours une relance. La même clé avec un contenu différent, c'est un second processus qui écrit, ou un bug. Comparer l'entrée stockée avant de qualifier l'écriture de relance, et échouer bruyamment quand elle diffère.

Reconstruire en une requête, exporter en JSON

Si la reconstruction a besoin de quelqu'un qui connaît le système, P-4 ne tient pas. Ordonner par horodatage, et dire ce que cet ordre ne tranche pas : les entrées de même horodatage ne sont ordonnées par aucun des champs publiés.

Ce qu'un trigger n'apporte pas

Les privilèges et les triggers sont des contrôles situés à l'intérieur de la frontière de confiance de la base de données elle-même. Dans PostgreSQL, un superutilisateur contourne toutes les vérifications de permissions, et le propriétaire de la table peut désactiver ses triggers. Une table en ajout seul est donc un contrôle fort face à l'application et un contrôle faible face à ses administrateurs. La détectabilité des altérations face à des initiés privilégiés exige un contrôle extérieur à la base de données, et c'est une propriété distincte de la persistance en ajout seul. Aucune des deux n'est, en soi, une affirmation juridique sur la preuve.

Modes de défaillance

  1. L'identifiant régénéré en cours de route

    Un consommateur ou un worker crée un nouvel identifiant quand le champ est absent, souvent via une valeur par défaut dans un modèle de message. Le résultat : deux moitiés d'une même transaction, et aucune requête qui les relie.

  2. La trace n'existe que dans les journaux

    Les journaux tournent, sont échantillonnés, ne sont pas structurés, et sont écrits par du code qui les traite comme du diagnostic. Une chaîne qu'on ne peut reconstruire qu'à partir des journaux ne peut pas être reconstruite de façon fiable.

  3. Une frontière asynchrone perd le contexte

    Les pools de threads, les executors et les brokers ne portent rien si quelque chose ne le copie pas. En Python, asyncio.to_thread est documenté comme propageant le contexte courant ; run_in_executor ne documente rien de tel, et le to_thread de CPython lui-même copie explicitement le contexte avant de l'appeler. Dans Spring, un executor a besoin d'un décorateur de tâches qui propage le contexte.

  4. Les lignes stockées ne peuvent pas être reliées à leurs éléments de preuve

    Pas de source_hash, ou le hash d'autre chose que les octets réellement lus, si bien que la même donnée d'entrée ne peut jamais être reconnue à nouveau.

  5. Des relances qui produisent des entrées ambiguës

    Un horodatage pris au moment de l'insertion fait de chaque relance une entrée nouvelle et différente, et la piste dit alors qu'une étape a eu lieu deux fois.

  6. Un conflit écarté comme une relance

    Une clé unique et ON CONFLICT DO NOTHING, et rien d'autre : une seconde entrée avec la même clé et une décision différente disparaît sans erreur, et la piste garde celle qui est arrivée en premier.

  7. Une correction qui écrase

    Un UPDATE détruit ce qui était tenu pour vrai auparavant, et la piste ne peut plus expliquer pourquoi une sortie antérieure disait ce qu'elle disait.

  8. Une frontière de service qui démarre une nouvelle trace

    Une passerelle retire un en-tête qu'elle ne connaît pas, une bibliothèque cliente ne propage pas, un job par lots part de rien. Chacun de ces cas reste invisible jusqu'à ce que quelqu'un essaie de reconstruire.

  9. Le backend de traçage utilisé comme piste d'audit

    Son échantillonnage, sa rétention et son pipeline modifiable conviennent au diagnostic et ne conviennent pas à la preuve.

  10. Des horloges murales pour ordonner les étapes entre hôtes

    Des horodatages venant de machines différentes n'ordonnent les entrées qu'aussi bien que leurs horloges concordent. Au sein d'une transaction, un seul processus qui écrit, ou un ordre attribué par un seul processus, est plus fiable que l'heure murale de plusieurs hôtes.

Vérification

  • Test d'intégration

    Réussit quand : D'une sortie publiée jusqu'à ses sources : une requête renvoie chaque entrée, toutes avec le même trace_id, avec le source_hash de chaque document lu.

    Preuve de dents : Retirer l'identifiant d'un saut asynchrone dans une branche jetable. Le test de reconstruction doit échouer, et non renvoyer une chaîne plus courte qui paraît complète.

  • Test d'intégration

    Réussit quand : Une correction s'ajoute : après elle, les deux entrées existent, et la plus récente remplace l'antérieure.

    Preuve de dents : Remplacer l'ajout par une mise à jour : le test échoue.

  • Test d'intégration

    Réussit quand : Une relance exacte est absorbée, et la même clé avec un contenu différent est refusée, pas absorbée.

    Preuve de dents : Prendre chaque conflit pour une relance sans comparer : le test qui écrit un contenu différent sous la même clé échoue.

  • Test d'intégration

    Réussit quand : Face à la vraie base de données, le rôle de l'application ne peut ni mettre à jour, ni supprimer, ni tronquer la piste.

    Preuve de dents : Accorder UPDATE au rôle dans une base de données jetable : le test échoue.

  • Test de contrat

    Réussit quand : Chaque schéma de message qui franchit une frontière asynchrone exige l'identifiant d'audit.

    Preuve de dents : Rendre le champ facultatif : le test de contrat échoue.

  • Test de bout en bout

    Réussit quand : Une requête qui se ramifie en un job et un message garde une seule identité d'audit, contrôlée dans la piste persistée plutôt que dans les journaux.

  • Preuve manuelle

    Réussit quand : Exporter une chaîne en JSON et faire reconstruire l'historique de la sortie, à partir de cet export seul, par une personne qui n'a pas construit le système.

Réalisations alternatives

  • Event sourcing. Le stockage d'événements est l'historique, et l'état en est dérivé. De fortes propriétés d'audit, et un engagement architectural important.
  • Change data capture. Les changements de lignes lus dans le journal de la base de données. Utile pour la réplication ; cela enregistre ce qui a changé, pas qui a décidé ni pourquoi, et ne remplace donc pas les entrées d'audit métier.
  • Un stockage objet à écriture unique pour les chaînes exportées, là où la rétention doit survivre à la base de données.
  • Des registres ou des stockages à détection d'altération, là où l'altération par des initiés fait partie du modèle de menace.

Limites

  • Le pattern ne décide pas quelles étapes sont pertinentes pour une réglementation particulière ou un produit particulier.
  • La persistance en ajout seul n'est pas une détectabilité des altérations, et aucune des deux n'est une conclusion juridique sur la preuve.
  • Il ne définit pas de durées de rétention.
  • Il repose sur le respect du contrat par chaque composant. Un composant qui ne le respecte pas est découvert par le test de reconstruction, pas empêché par lui.

Sources

COADF Engineering Companion 1.0 · non normatif · se rapporte à COADF Core 2.2

Droits de publication réservés. Aucune licence publique n'est accordée à ce jour pour le COADF Engineering Companion 1.0 ni pour ses exemples de référence.

Statut de propriété intellectuelle et de publication