Comment tenir un journal qui montre les modifications
Une piste d’audit est un journal de qui a changé quoi et quand. Un journal que tout le monde peut modifier n’est pas une preuve, car personne ne peut savoir s’il a été changé.
Une chaîne d’empreintes fait apparaître une modification. Enregistrer une décision ne suffit pas. Cette leçon fait que l’enregistrement montre s’il a été changé.
À la fin de cette leçon
Remarque
Je peux tenir un journal qui montre s’il a été modifié.
Mots clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Ajout seul | on ajoute des entrées et on ne modifie jamais les anciennes | |
| Empreinte | un court code qui résume un texte | 1a47e90b |
| Chaîne | des entrées qui gardent chacune l’empreinte de la précédente | |
| Falsifier | changer un enregistrement sans en avoir le droit | |
| Ancre | la dernière empreinte, gardée ailleurs |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une empreinte résume un texte
Une empreinte transforme n’importe quel texte en un court code. Le même texte donne toujours le même code. Changez une seule lettre et le code change tout entier.
L’empreinte de cette leçon est petite et faite pour apprendre. Elle n’est pas sûre. Un vrai journal emploie une empreinte forte tirée d’une bibliothèque, et l’idée est la même.
Exemple détaillé : trois empreintes
| Texte | Empreinte |
|---|---|
| (rien) | 811c9dc5 |
| a | e40c292c |
| abc | 1a47e90b |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une chaîne relie chaque entrée à la précédente
Chaque entrée garde son texte, l’empreinte de l’entrée d’avant et sa propre empreinte. Sa propre empreinte est faite de l’empreinte précédente, d’une barre et de son texte.
Modifiez l’entrée 2 : sa propre empreinte ne correspond plus. Chaque entrée suivante garde l’ancienne empreinte, donc les maillons cassent à partir de là.
Exemple détaillé : cinq entrées
| Entrée | Texte | Empreinte précédente | Sa propre empreinte |
|---|---|---|---|
| 0 | modèle figé sur assistant-v3 | 00000000 | c62bba59 |
| 1 | canari à 5 pour cent | c62bba59 | f9964152 |
| 2 | canari à 20 pour cent | f9964152 | f3c42d5d |
| 3 | mise en ligne pour tous | f3c42d5d | 518ac8cd |
| 4 | ancienne version retirée | 518ac8cd | e7fac6c7 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une erreur courante
Attention
Beaucoup pensent qu’une chaîne d’empreintes empêche de changer le journal. Ce n’est pas le cas : elle montre seulement un changement. Qui peut écrire tout le fichier peut refaire toutes les empreintes. Gardez la dernière empreinte, l’ancre, là où cette personne ne peut pas aller, et comparez.
Écrire l’assistant du modèle, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Les jetons d’une réponse, ou null si elle n’en donne pas.' 'function compter(donnees, nom) {' ' return donnees.usage && typeof donnees.usage[nom] === \"number\" ? donnees.usage[nom] : null;' '}' > model.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire l’assistant du modèle, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function demander(messages, echantillon, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"statut \" + res.status);' ' const d = await res.json();' ' return { texte: d.choices[0].message.content, entree: compter(d, \"prompt_tokens\"), sortie: compter(d, \"completion_tokens\"), enregistre: false };' ' } catch (erreur) {' ' console.log(\"Échantillon enregistré : aucun modèle n’a répondu (\" + erreur.message + \").\");' ' return { texte: echantillon, entree: null, sortie: null, enregistre: true };' ' }' '}' 'module.exports = { demander };' >> model.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script de la piste, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' 'const DEBUT = \"00000000\";' 'function empreinte(texte) {' ' let x = 2166136261;' ' for (let i = 0; i < texte.length; i++) {' ' x = x ^ texte.charCodeAt(i);' ' x = Math.imul(x, 16777619) >>> 0;' ' }' ' return (\"00000000\" + x.toString(16)).slice(-8);' '}' > piste.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script de la piste, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function ajouter(chaine, texte) {' ' const prec = chaine.length > 0 ? chaine[chaine.length - 1].empreinte : DEBUT;' ' return chaine.concat([{ texte: texte, prec: prec, empreinte: empreinte(prec + \"|\" + texte) }]);' '}' 'function premierCasse(chaine) {' ' let prec = DEBUT;' ' for (let i = 0; i < chaine.length; i++) {' ' const e = chaine[i];' ' if (e.prec === prec && e.empreinte === empreinte(e.prec + \"|\" + e.texte)) prec = e.empreinte;' ' else return i;' ' }' ' return -1;' '}' >> piste.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script de la piste, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const notes = [\"modèle figé sur assistant-v3\", \"canari à 5 pour cent\", \"canari à 20 pour cent\",' ' \"mise en ligne pour tous\", \"ancienne version retirée\"];' 'let chaine = [];' 'notes.forEach(function (t) { chaine = ajouter(chaine, t); });' 'const ancre = chaine[4].empreinte;' 'const falsifie = chaine.map(function (e) { return { texte: e.texte, prec: e.prec, empreinte: e.empreinte }; });' 'falsifie[2].texte = \"canari à 90 pour cent\";' 'let refaite = [];' 'falsifie.forEach(function (e) { refaite = ajouter(refaite, e.texte); });' >> piste.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script de la piste, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' console.log(\"empreinte de abc : \" + empreinte(\"abc\"));' ' console.log(\"premier maillon cassé dans le vrai journal : \" + premierCasse(chaine));' ' console.log(\"premier maillon cassé après une modification : \" + premierCasse(falsifie));' ' console.log(\"le journal refait est cohérent : \" + (premierCasse(refaite) === -1));' ' console.log(\"le journal refait correspond à l’ancre : \" + (refaite[4].empreinte === ancre));' ' const q = \"En une phrase courte, pourquoi une entrée modifiée se repère-t-elle facilement dans une chaîne d’empreintes ?\";' ' const echantillon = \"Chaque entrée garde l’empreinte de la précédente : une seule modification casse tous les maillons suivants.\";' ' const reponse = await demander([{ role: \"user\", content: q }], echantillon, 60);' ' console.log(\"le modèle dit : \" + reponse.texte);' '}' 'main();' >> piste.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lancer la piste d’audit
docker exec -w /home/node/project agent node piste.jsAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Ce que le Run affiche
| Ligne | Ce qu’elle montre |
|---|---|
| empreinte de abc : 1a47e90b | La même empreinte à chaque fois |
| premier maillon cassé dans le vrai journal : -1 | Aucun maillon cassé |
| premier maillon cassé après une modification : 2 | La modification est trouvée à l’entrée 2 |
| le journal refait est cohérent : true | Une reconstruction complète passe le contrôle des maillons |
| le journal refait correspond à l’ancre : false | L’ancre montre quand même le changement |
| le modèle dit : ... | Une phrase du labo. Un échantillon enregistré si aucun modèle ne répond |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.