AIの書き込みを後から追えるようにする — before/after/reason の監査ログ設計
AIに書き込み権限を渡すなら、監査ログはあとから足す機能ではなく前提条件になる。実際に運用しているシステムのテーブル定義をそのまま公開し、reason 欄がなぜ必須なのか、何を載せてはいけないのか、バックアップと何が違うのかを整理する。
AIの書き込みに対する監査ログとは
AIの書き込みに対する監査ログとは、変更の前後の値だけでなく「AIが述べた変更理由」を、追記専用で残す記録である。
人間のオペレータの操作ログと決定的に違うのは、あとから当人に聞けないことだ。人間なら「なぜこれを消したのか」を本人に確認できる。AI にはそれができない。同じモデルに同じ質問をしても、それは当時の判断の再現ではなく新しい生成である。当時の理由は、その時に書き残さなければ永久に失われる。
だから設計としては、before / after に加えて reason を必須項目として扱う。以下は実際に運用しているシステムの実装である。
テーブル定義をそのまま出す
このシステムでは、AI による変更は例外なく1行が残る。実テーブルの列はこうなっている。
| 列 | 型 | 何を入れるか |
|---|---|---|
id | TEXT | 主キー(UUID) |
project_id | TEXT | どの案件の変更か(複数案件を1基盤で回す将来構成のための外部キー。現在は単一) |
actor | TEXT | human:<id> / agent:<name> / system |
action | TEXT | content.publish / experiment.adopt など動詞 |
entity_type / entity_id | TEXT | 何に対する変更か |
before_json / after_json | TEXT | 変更前後の値(JSON) |
reason | TEXT | AIが述べた変更理由 |
experiment_id / result | TEXT | 実験に紐づく変更かどうか |
created_at | INTEGER | 記録時刻(UNIX秒) |
索引は2つだけ置いている。(entity_type, entity_id, created_at DESC) と (actor, created_at DESC) である。この2本は、事故のときに実際に投げる質問がこの2種類に集約されるからだ。「この記事に何が起きたか」と「このエージェントが何をしたか」。前者は影響範囲の特定に、後者は暴走したエージェントの停止判断に使う。監査ログの索引は「あとで分析するため」ではなく、「事故対応で最初に打つクエリ」から決めるとよい。
書き込み関数は追記しかしない。UPDATE も DELETE も用意していない。更新できる監査ログは監査ログではないからで、これは性能や容量の話より先に決めるべき性質である。
reason 欄が実際に効く場面
before / after は「何が起きたか」しか示さない。ロールバックの判断に必要なのは、たいていそこではない。
差分だけを見て「これは戻すべき変更か」を判断できる場面は、そう多くない。値が変わったこと自体は正しい変更でも起きるからだ。判断を分けるのは、その変更が何を意図していたかである。「A/B の勝ち筋が出たので採用した」と「参照していた指標が欠測していたので暫定値で埋めた」は、after が同じでも扱いがまったく違う。
そこで、書き込みを行う AI に「なぜそうしたか」を自分の言葉で書かせる欄を必須にしている。運用してみて分かったのは、この欄の副作用のほうが大きいということだ。理由を書けない変更は、たいてい根拠が薄い。 reason が空欄になりがちな経路は、権限設計を見直したほうがいい箇所である。
載せてはいけないものを先に決める
監査ログは「全部記録する」が正しく見えるが、そのまま実装すると個人情報の複製装置になる。
このシステムでは、問い合わせフォームから届いた連絡先を after に載せないと決めてある。リードのレコード自体は保存するが、監査ログ側には残さない。監査ログは長期に残り、追記専用で消せず、権限の異なる複数の経路から読まれる。削除できない場所に個人情報を入れると、あとから取り消せない。
同じ理由で、APIキーなどの秘密情報も載せない。監査ログに残すべきは「誰が・いつ・何を・なぜ変えたか」であって、値そのものが必ず要るわけではない。機微なフィールドは、値ではなく「変更があった事実」だけを残す設計にできる。
医療・美容医療の領域でこの設計をそのまま使う場合、ここは特に慎重に扱う必要がある。監査のために残した記録が、本来アクセス制御されるべき情報の抜け道になりうるからだ。
監査ログとバックアップは別物である
「データベースの復元機能があるから監査ログは要らない」という整理は成り立たない。両者は目的が違う。
Cloudflare の D1 は公式ドキュメントの説明ではこうである。
D1 is Cloudflare's managed, serverless database with SQLite's SQL semantics, built-in disaster recovery, and Worker and HTTP API access.
同ドキュメントは復元機能(Time Travel)について、直近30日以内の任意の時点へ復元できると説明している。
| バックアップ / 復元 | 監査ログ | |
|---|---|---|
| 目的 | 状態を戻す | 何が起きたかを説明する |
| 粒度 | データベース全体 | 1変更ごと |
| 意図の記録 | 無い | reason に残る |
| 保持 | 期限がある | 追記して残す |
復元は「壊れた状態から戻す」ための手段で、どの時点に戻すべきかは教えてくれない。その判断材料が監査ログである。逆に監査ログはデータを戻せない。片方だけでは事故対応が完結しない。
付け忘れないための置き場所
監査ログの一番の失敗は、設計の欠陥ではなく付け忘れである。新しい書き込み経路を足したときに、そこだけログが無い。
このシステムでは、書き込みが必ず通るリポジトリ層の関数に権限チェックを置き、監査ログもその同じ経路に寄せている。AI がデータベースへ直接アクセスすることを禁じているので、権限チェックを通った書き込みは監査も通る。「必ず通る場所」を1か所に決めて、そこに全部載せるのが、規律で守るより確実である。
チェックリストとしては次の3つで足りる。
- 書き込み経路は1か所に集約されているか(迂回路が無いか)
- その経路で
reasonが必須になっているか - 個人情報・秘密情報が
before/afterに入らないことがテストで固定されているか
FAQ
Q. 監査ログは全部の書き込みに必要ですか。 AI が実行する書き込みには必要です。人間なら事後に意図を確認できますが、AI には確認先がありません。
Q. reason は何字くらい書かせるべきですか。 長さより粒度です。「更新した」ではなく「何を根拠にそう判断したか」が1文で読めれば足ります。
Q. ログが増えすぎませんか。 1変更1行なので、AI の書き込み頻度が現実的な範囲なら問題になりません。肥大するのは before / after に大きな本文を丸ごと入れた場合です。
Q. バックアップがあれば監査ログは不要では。 不要になりません。復元は状態を戻しますが、どこへ戻すべきかの判断材料は監査ログにしかありません。
まとめ
AI に書き込ませる設計で先に用意すべきなのは、賢さではなく説明可能性である。
before/afterに加えてreasonを必須にする(当時の意図は再現できない)- 追記専用にする(更新できる監査ログは監査ログではない)
- 個人情報・秘密情報は載せない(消せない場所に入れると取り消せない)
- 権限チェックと同じ「必ず通る場所」に置く(付け忘れを構造で防ぐ)
自律的に動く AI を業務に入れるとき、この記録があるかどうかが「試せる」と「試せない」の境目になります。設計と実装のご相談を承っています。
出典
- Cloudflare Docs(公式ドキュメントのソース)— D1 overview(参照 2026-08-09)