クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

黙って書き換えられない監査記録

このレッスンのあとで: 変えられたら分かる記録を、つけることができる。

70分のレッスンコマンド7件AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

改ざんが見える記録の作り方

監査記録とは、誰がいつ何を変えたかを残す記録です。誰でも書き換えられる記録は、証拠になりません。変えられたかどうかが、誰にも分からないからです。

ハッシュの鎖を使うと、書き換えが見えるようになります。AIの変更を決めたとき、その判断は、記録が確かな分だけ確かです。ハッシュの鎖は、記録がいつか書き換えられたら、それを見せます。

このレッスンのゴール

メモ

変えられたら分かる記録を、つけることができる。

キーワード

ことばいみれい
追記のみ新しい項目を足すだけで、古い項目は書き換えない
ハッシュ文の、短い指紋1a47e90b
鎖各項目が、前の項目のハッシュを持つつながり
改ざん許しなく、記録を書き換えること
アンカー最新のハッシュを、別の場所に取っておいたもの

学生が入力するセルは、このプレビューでは空白です。

ハッシュは、文の指紋

ハッシュは、どんな文も短い符号にします。同じ文なら、いつも同じ符号です。一文字変えると、符号はまったく変わります。

このレッスンのハッシュは小さく、教えるために作ったものです。安全ではありません。本物の記録は、ライブラリの強いハッシュを使います。考え方は同じです。

例: 三つの指紋

文ハッシュ
(空)811c9dc5
ae40c292c
abc1a47e90b

学生が入力するセルは、このプレビューでは空白です。

鎖は、各項目を一つ前につなぐ

各項目は、自分の文、一つ前の項目のハッシュ、自分のハッシュを持ちます。自分のハッシュは、前のハッシュ、縦線、自分の文から作ります。

項目 2 を書き換えると、その項目のハッシュが合わなくなります。あとの項目は古いハッシュを持ったままなので、そこから先のつなぎ目が壊れます。

例: 五つの項目

項目文前のハッシュ自分のハッシュ
0モデルを chat-v3 に固定した000000001c54605c
1カナリアを 5 パーセントにした1c54605cb8ce95b0
2カナリアを 20 パーセントにしたb8ce95b067d99758
3全利用者に切り替えた67d99758e47de438
4古いバージョンを退役させたe47de438cb321a15

学生が入力するセルは、このプレビューでは空白です。

よくある間違い

注意

ハッシュの鎖が、記録の書き換えを防ぐと考える人が多いです。防ぎません。変更を見せるだけです。ファイル全体を書ける人は、すべてのハッシュを作り直せます。最新のハッシュ、つまりアンカーを、その人の手が届かない所に取っておき、比べます。

助っ人 model.js を書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答のトークン数。なければ null。' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

助っ人 model.js を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, 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(\"status \" + res.status);' '    const data = await res.json();' '    const text = data.choices[0].message.content;' '    return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' 'module.exports = { ask };' >> model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

監査のスクリプトを書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const START = \"00000000\";' 'function hash(text) {' '  let x = 2166136261;' '  for (let i = 0; i < text.length; i++) {' '    x = x ^ text.charCodeAt(i);' '    x = Math.imul(x, 16777619) >>> 0;' '  }' '  return (\"00000000\" + x.toString(16)).slice(-8);' '}' > audit.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

監査のスクリプトを書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function add(chain, text) {' '  const prev = chain.length > 0 ? chain[chain.length - 1].hash : START;' '  return chain.concat([{ text: text, prev: prev, hash: hash(prev + \"|\" + text) }]);' '}' 'function firstBroken(chain) {' '  let prev = START;' '  for (let i = 0; i < chain.length; i++) {' '    const e = chain[i];' '    if (e.prev === prev && e.hash === hash(e.prev + \"|\" + e.text)) prev = e.hash;' '    else return i;' '  }' '  return -1;' '}' >> audit.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

監査のスクリプトを書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const notes = [\"モデルを chat-v3 に固定した\", \"カナリアを 5 パーセントにした\", \"カナリアを 20 パーセントにした\",' '  \"全利用者に切り替えた\", \"古いバージョンを退役させた\"];' 'let chain = [];' 'notes.forEach(function (t) { chain = add(chain, t); });' 'const anchor = chain[4].hash;' 'const forged = chain.map(function (e) { return { text: e.text, prev: e.prev, hash: e.hash }; });' 'forged[2].text = \"カナリアを 90 パーセントにした\";' 'let again = [];' 'forged.forEach(function (e) { again = add(again, e.text); });' >> audit.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

監査のスクリプトを書く(4)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  console.log(\"abc のハッシュ: \" + hash(\"abc\"));' '  console.log(\"本物の記録で最初に壊れた項目: \" + firstBroken(chain));' '  console.log(\"書き換えたあとで最初に壊れた項目: \" + firstBroken(forged));' '  console.log(\"作り直した記録に矛盾はない: \" + (firstBroken(again) === -1));' '  console.log(\"作り直した記録はアンカーと一致: \" + (again[4].hash === anchor));' '  const q = \"短く一文で答えてください。ハッシュの鎖では、書き換えた記録が見つけやすいのは、なぜですか。\";' '  const sample = \"各項目が前の項目の指紋を持つので、一か所の変更が、あとのつなぎ目をすべて壊すからです。\";' '  const reply = await ask([{ role: \"user\", content: q }], sample, 80);' '  console.log(\"モデル: \" + reply.text);' '}' 'main();' >> audit.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

監査記録を動かす

コマンド
docker exec -w /home/node/project agent node audit.js

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

Run の出力

行意味
abc のハッシュ: 1a47e90bいつも同じ指紋
本物の記録で最初に壊れた項目: -1壊れたつなぎ目はない
書き換えたあとで最初に壊れた項目: 2書き換えが項目 2 で見つかる
作り直した記録に矛盾はない: true全部作り直せば、つなぎ目の検査は通る
作り直した記録はアンカーと一致: falseアンカーは、変更をまだ見せている
モデル: ...ラボからの一文。モデルが答えなければ記録済みサンプル

学生が入力するセルは、このプレビューでは空白です。

このラボとソースについて: AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。