Le journal d'accès
Le journal d'accès est le registre à l'épreuve de la falsification de votre entreprise. Chaque décision de vérification et chaque événement de cycle de vie du consentement y est consigné, chaîné par hachage, replié périodiquement en une époque dont la racine est ancrée sur la chaîne. C'est la pièce qui répond à la question « pouvez-vous démontrer ce que vous avez fait, et prouver que le registre n'a pas été retouché après coup ». Il outille l'obligation de reddition de comptes (P-39.1 art. 3.1; pour un organisme public, A-2.1 art. 52.2).
Un registre vérifiable, pas une preuve de conformité
Le journal démontre que la suite d'événements enregistrée est intègre et complète jusqu'à la dernière époque ancrée. Il ne certifie pas que vos pratiques sont conformes, et il ne dit rien de ce que vos autres systèmes ont fait des renseignements. Agreely documente et démontre; il ne certifie pas.
Ce qui est consigné
Une entrée est écrite pour chacun de ces événements, sur vos propres sujets :
| Événement | Ce qu'il consigne |
|---|---|
check |
Une décision de vérification, autorisation ou refus, avec son statut. |
grant |
Un consentement octroyé. |
revoke |
Un consentement retiré. |
erase |
Un effacement (destruction de clé). |
refuse |
Une offre refusée par la personne concernée. |
view |
Une lecture d'un renseignement personnel stocké (par exemple le déchiffrement du courriel d'un client pour l'une des surfaces permises). Elle porte la référence, jamais la valeur. |
Le sujet est identifié par votre propre référence client, jamais par un DID citoyen : le journal ne crée aucun lien corrélable avec la couche citoyenne. Voir non-corrélation.
L'écriture est hors du chemin critique. La vérification synchrone dépose l'événement dans une boîte de réception et répond; un travailleur à écrivain unique par entreprise (verrou consultatif Postgres) draine ensuite la boîte dans la chaîne. Une panne de journalisation ne ralentit ni ne casse jamais une vérification.
La chaîne de hachage
Chaque ligne engage la précédente :
row_hash = keccak256( prev_hash || canonicalJson(payload) )
prev_hash du premier enregistrement = 32 octets nuls
payload = {
"chain_seq": 1024,
"event_type": "check",
"decision": "deny",
"status": "revoked",
"occurred_at": "2026-06-26T15:04:05Z",
"consentRef": "0x…" | null,
"commitment": "0x…"
}
Le canonicalJson est la sérialisation canonique de la RFC 8785 : clés triées,
aucun espace, / non échappé, UTF-8 non échappé. Notez que chain_seq voyage ici
comme un nombre JSON; la règle « aucun nombre » décrite dans
canonicalisation et hachage
s'applique à la déclaration de consentement, pas à cette charge utile.
Le chain_seq est contigu, sans trou : une suppression au milieu de la chaîne
laisse un trou, et une suppression en fin de chaîne casse la couture de l'époque
ancrée qui la couvrait.
La chaîne hache l'engagement, jamais le texte lisible
Le commitment est le même keccak256( JCS(déclaration) || sel ) que le reste du protocole. La catégorie, la fin et la référence client sont liées à l'histoire uniquement à travers lui. C'est ce qui permet à une destruction de clé de vider les colonnes lisibles et le sel tout en laissant le row_hash, la racine d'époque et l'ancrage sur la chaîne pleinement vérifiables. Un effacement ne troue jamais le registre.
Les époques et l'ancrage
Périodiquement (au premier des deux seuils, un nombre d'entrées ou un délai), les lignes non encore ancrées sont repliées en une époque :
- un arbre de Merkle est construit sur les
row_hashde la plage, en ordre dechain_seq; - la racine et le
head_hash(lerow_hashàseq_to) sont enregistrés; - la racine, un seul
bytes32opaque, est mise en file pour l'ancrage sur la chaîne (anchorAuditRoot, événementAuditRootAnchored).
La charge utile sur la chaîne ne porte aucun identifiant d'entreprise en
clair, aucun consentRef, aucun renseignement personnel, aucun DID. Voir le
registre sur la chaîne.
Chaque racine d'époque est de plus signée au scellement avec la clé DID enregistrée de votre entreprise, et cette clé peut être autorisée par votre propre clé d'accès racine engagée dans votre DNS. C'est une couche additive, et elle ne conditionne jamais l'intégrité : voir signature d'époque et délégation.
La convention de Merkle fait partie de la recette
L'arbre est le SimpleMerkleTree d'OpenZeppelin, et sa forme exacte est publiée
dans l'export lui-même parce qu'un auditeur externe a déjà implémenté la
lecture évidente (apparier les voisins, keccak(a||b), dupliquer l'impair) et
obtenu une racine différente :
- Feuille : le
row_hashtel quel, jamais re-haché. - Appariement :
keccak256(a || b)oùaetbsont les deux enfants de 32 octets, ordonnés croissant comme chaînes d'octets. - Disposition : le tableau OpenZeppelin de
2N-1nœuds, lesNfeuilles écrites à la fin du tableau en ordre inverse (feuilleià l'indice2N-2-i), nœudi= appariement des nœuds2i+1et2i+2, racine à l'indice0. Un nœud impair est promu, jamais dupliqué. N = 1n'est pas un cas particulier : la racine est l'unique feuille.
Cette disposition n'est pas équivalente à un appariement niveau par niveau avec promotion : au-delà de quatre feuilles, les deux produisent des racines différentes.
La vérification, et sa frontière honnête
L'écran du journal affiche un état de vérification. Deux chemins existent.
Le chemin rapide, exécuté à chaque rendu de page, coûte O(queue ouverte + nombre d'époques) et non O(toutes les lignes). Il prouve la contiguïté de
chain_seq, l'intégrité de la queue ouverte et son raccordement au dernier
head_hash ancré, l'intégrité de chaque couture d'époque ancrée, et une
garde contre la troncature.
Le chemin profond, à la demande, rejoue la chaîne entière : chaque row_hash
est recalculé et comparé, et pour chaque ligne non effacée le commitment est
recalculé à partir de la déclaration et du sel déchiffré. C'est ce contrôle de
contenu qui attrape une réécriture des colonnes lisibles, laquelle laisserait
la chaîne se rejouer correctement.
La queue ouverte n'est pas prouvée par la chaîne
Les lignes postérieures au dernier seq_to ancré ne valent que la confiance que vous accordez à la base de données vivante, jusqu'à ce que leur racine atterrisse sur la chaîne. C'est déclaré comme tel (openTail) dans l'écran et dans l'export, et ce n'est jamais présenté comme prouvé par la chaîne.
L'export
Le registre s'exporte en JSON ou en CSV, au format agreely.access-log.v1.
L'export est auto-suffisant : il porte, en plus des entrées, tout ce qu'il
faut pour le revérifier hors ligne, sans Agreely et sans lire notre code.
- la recette du
row_hashet le génésis; - la convention de Merkle complète, en toutes lettres;
- les époques avec leur
seq_from,seq_to, racine,head_hashet transaction d'ancrage; - l'adresse du contrat
AgreelyRegistry, lechainIdet le nom de la chaîne, parce qu'une transaction qui porte une racine prouve seulement que quelqu'un a écrit cebytes32quelque part; sans le contrat et la chaîne, un auditeur ne peut pas confirmer que le registre est bien le nôtre. Le nom de la chaîne est aussi ce qui divulguerait un ancrage sur un réseau de test comme tel; - la signature d'époque, sa clé publique et son algorithme, quand elle existe;
- l'état de vérification (
chainOk,contentOk,epochsOk,anchoredOk,openTail) et les réserves.
Un export filtré sur un client est une projection : chaque entrée porte
toujours ses propres prev_hash et row_hash, mais un rejeu contigu de la chaîne
exige l'export non filtré de l'entreprise. La portée est déclarée dans l'artéfact
pour qu'une tranche détachée ne soit jamais prise pour le registre entier.
Ensuite
- Accès auditeur : ouvrir ce registre en lecture seule à un vérificateur externe.
- Signature d'époque et délégation : la clé qui signe chaque racine, et la délégation liée au DNS.
- Registre sur la chaîne : où atterrit la racine, et ce que la chaîne prouve.
- Portail citoyen : la vue que la personne concernée a de ses propres accès.