Sur cette page
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.
| Aspect | Trace distribuée | Piste 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 traceparent | Un trace_id par transaction métier, créé à la réception |
| Durée de vie | La durée de la requête ; une rétention qui se compte en jours | Aussi longtemps que les enregistrements qu'elle justifie |
| Échantillonnage | L'échantillonnage est normal et permis | Pas d'échantillonnage : une entrée manquante est un défaut |
| Mutabilité | Les pipelines filtrent, suppriment et réécrivent selon leur configuration | En ajout seul : une correction est une nouvelle entrée |
| Contenu | Spans, 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
- Corrélation par le trace_id d'audit
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
INSERTetSELECTsur la piste, et niUPDATE, niDELETE, niTRUNCATE. - 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 PostgreSQLTRUNCATEne déclenche pas les triggersON 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
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.
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.
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_threadest documenté comme propageant le contexte courant ;run_in_executorne documente rien de tel, et leto_threadde 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.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.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.
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.Une correction qui écrase
Un
UPDATEdé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.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.
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.
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 lesource_hashde 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
UPDATEau 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
- W3C: Trace Context, the traceparent header · spécification · W3C Recommendation, Level 1 · Vérifié le 2026-09-11
- OpenTelemetry: SDK environment variables: OTEL_PROPAGATORS · spécification · Specification 1.60.0 · Vérifié le 2026-09-11
- OpenTelemetry: Sampling · documentation officielle · Vérifié le 2026-09-11
- OpenTelemetry: Transforming telemetry · documentation officielle · Vérifié le 2026-09-11
- PostgreSQL Global Development Group: TRUNCATE · documentation officielle · PostgreSQL 18 · Vérifié le 2026-09-11
- PostgreSQL Global Development Group: INSERT: ON CONFLICT · documentation officielle · PostgreSQL 18 · Vérifié le 2026-09-11
- PostgreSQL Global Development Group: Role attributes · documentation officielle · PostgreSQL 18 · Vérifié le 2026-09-11
- PostgreSQL Global Development Group: ALTER TABLE: DISABLE TRIGGER · documentation officielle · PostgreSQL 18 · Vérifié le 2026-09-11
- Python Software Foundation: asyncio: Task and to_thread · documentation du langage · Python 3.14 · Vérifié le 2026-09-11
- CPython: Lib/asyncio/threads.py · dépôt du projet · CPython 3.14.7 · Vérifié le 2026-09-11
- Spring Framework: Observability support: context propagation · documentation officielle · Spring Framework 7.0 · Vérifié le 2026-09-11
