FR EN

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 :

  1. un arbre de Merkle est construit sur les row_hash de la plage, en ordre de chain_seq;
  2. la racine et le head_hash (le row_hash à seq_to) sont enregistrés;
  3. la racine, un seul bytes32 opaque, est mise en file pour l'ancrage sur la chaîne (anchorAuditRoot, événement AuditRootAnchored).

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_hash tel quel, jamais re-haché.
  • Appariement : keccak256(a || b)a et b sont les deux enfants de 32 octets, ordonnés croissant comme chaînes d'octets.
  • Disposition : le tableau OpenZeppelin de 2N-1 nœuds, les N feuilles écrites à la fin du tableau en ordre inverse (feuille i à l'indice 2N-2-i), nœud i = appariement des nœuds 2i+1 et 2i+2, racine à l'indice 0. Un nœud impair est promu, jamais dupliqué.
  • N = 1 n'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_hash et le génésis;
  • la convention de Merkle complète, en toutes lettres;
  • les époques avec leur seq_from, seq_to, racine, head_hash et transaction d'ancrage;
  • l'adresse du contrat AgreelyRegistry, le chainId et le nom de la chaîne, parce qu'une transaction qui porte une racine prouve seulement que quelqu'un a écrit ce bytes32 quelque 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