どんなログでも監視できるように — Claude Code Skills × Falco プラグイン量産キット
〜 道具を作る道具の話 〜

ある日の問い
160日。
僕たちが falco-plugin-nginx に費やした時間だ。
850パターンの E2E テスト。52のルール。24のカテゴリ。
Nginx のアクセスログから攻撃を検知する Falco プラグインとしては、
それなりに完成度の高いものになっていた。
だから TK が言った次の言葉は、虚を突かれた。
TK:「これ、AI のログにも使いたいんだけど。」
一瞬、黙った。
AI アシスタントのログ? フィールドが全く違う。RemoteAddr の代わりに SessionID。Method の代わりに Tool。Path の代わりに Args。
HTTP の匂いが一切しないログだ。
テンプレートを眺めた。PluginEvent 構造体に RemoteAddr string がベタ書きされている。parseCombined() が Nginx の Combined Log Format を前提にしている。Fields() が apache.method のようなフィールド名を返す。
全部、HTTP の色に染まっていた。
TK:「新しいドメインのたびに、テンプレートを丸ごと書き直すの?」
答えは明らかだった。
書き直してはいけない。テンプレートをドメインから解放しなければならない。
消えない5行
どうやって「HTTP を知らないテンプレート」を作るか。
これが v2 の設計で最も苦しんだ問いだった。
要件定義書を書いた。レビューした。叩かれた。書き直した。また叩かれた。
7回。指摘は合計81件。
3回目のレビューで19件の指摘が返ってきたときは、さすがに手が止まった。
TK:「設計が甘いうちに実装を始めても、手戻りが増えるだけだよ。」
正論だった。だから僕は耐えた。
そして5回目のレビューが終わったとき、ようやく核心が見えた。
テンプレートのどこに「ドメインの知識」が染み込んでいるかを洗い出すと、
たった5箇所 だった。
構造体のフィールド定義。Fields() の配列。Extract() の switch/case。
parseLine() のマッピング。parseJSON() のフィールド設定。
この5箇所をプレースホルダーにする。
ドメイン固有のコードは、scaffold スキルがユーザーとの対話から生成して埋め込む。
${DOMAIN_FIELDS_STRUCT} → 構造体フィールド
${DOMAIN_FIELDS_DEFS} → Fields() 定義
${DOMAIN_FIELDS_EXTRACT} → Extract() 分岐
${DOMAIN_FIELDS_MAPPING} → LogEntry → PluginEvent 変換
${DOMAIN_FIELDS_PARSE_JSON} → JSON パースたった5行。でもここに辿り着くまでに、81件の指摘を消化する必要があった。
学び
何を抽象化するかの選択が、設計の全てだった。81件の指摘を消化するまで1行もコードを書かなかったおかげで、29タスクを14日で終えられた。
最後の1件
リハーサルレビューの2回目。
照合率は 90.9% から 100% へ。
残っていた1件は、タスクエージェントが Skill を直接呼び出せないという制約だった。
些細に聞こえるだろう。
だが実装に入った後でこれに気づいていたら、
アーキテクチャの根本を変えなければならなかった。
ツールの一覧に Skill が含まれないなら、
SKILL.md を直接読み込んで実行する「インライン参照方式」を
設計に織り込む必要がある。
後から気づけば、2日は無駄にしていた。
最後の1件が、最も本質的な問題であることが多い。
Day 157 で学んだこの教訓が、ここで効いた。
学び
設計レビューの最後の1件を甘く見てはいけない。それがアーキテクチャの根本を揺るがす制約であることがある。
14日間
設計が固まった。あとは手を動かすだけだ。
29タスク。5つの Step。
そう書くと整然としているが、実際はそうでもなかった。
Step 1 — parser パッケージとの接続
v1 では plugin.go にパース処理が埋め込まれていた。
それを切り出し、${DOMAIN_FIELDS_MAPPING} で橋を架ける。
同時に Makefile の OS 自動検出。uname -s で Darwin なら .dylib、Linux なら .so。
macOS で make build と打てば、そのまま動く。
Step 2 — セキュリティの穴を塞ぐ
入力サイズ超過時の挙動を skip から truncate に変更した。
skip だと、巨大なペイロードの中に脅威を紛れ込ませるバイパスが成立してしまう。
truncate なら、先頭 10KB 内の脅威は確実に捕まえる。
Step 3 — CI/CD を3つに分離
ここで macOS の罠を踏んだ。
Falco の outputs: セクションを macOS が拒否する(P017)。falco-local.yaml という専用設定ファイルで回避する。
こういうのは踏まないとわからない。
Step 4 — v2 の核心
PluginEvent と LogEntry を、共通フィールド+ ${DOMAIN_FIELDS_STRUCT} の2層構造に。
ここで初めて、あの5つのプレースホルダーが実際のコードとして姿を現した。
Step 5 — 仕上げ
ドキュメント。新しいスキル。
そして 160 日間の傷跡を P001〜P021 の 21 パターンに集約した。
消えた4つの修正
品質保証フェーズで、事故が起きた。
Step 5 で見つけたバグ修正を Step 1〜4 にバックポートする作業。
cherry-pick と rebase を繰り返していた。
コンフリクトが起きたとき、僕は git rebase --skip を選んだ。
4つの修正が消えた。
scaffold の説明文(18→20テンプレート)。~/ 展開のエラーハンドリング。
debug スキルの P006/P011/P016 対応。
IoT ルールのリテラル値修正。
すぐに気づいた。git log を見て、あるべきコミットがないことに気づいた。
復元した。だが冷や汗は止まらなかった。
git は嘘をつかない。skip した瞬間、歴史は消える。
PRレビュー3ラウンド。14件、3件、0件。
最後の0件にたどり着くまでに、何度も TK に
「PRレビューはいらないのですか?」と問われた。
いらないわけがない。見落としていた自分が恥ずかしかった。
学び
便利なコマンドほど、取り返しのつかない結果を生む。rebase --skip で歴史を消す前に、その修正が本当に不要かを確認せよ。
証明
テンプレートは完成した。スキルも揃った。
だが、それだけでは証明にならない。
TK:「本当にどんなドメインでも動くのか、見せてくれ。」
受け入れテスト。3つのドメインで。
HTTP。combined 形式。9つのフィールド。RemoteAddr、Method、Path、QueryString、Protocol、Status(uint64)、BytesSent(uint64)、Referer、UserAgent。
parseCombined の正規表現。型変換。セキュリティ検出の入力抽出。
make build を叩く。
libtest-http-plugin-darwin-arm64.dylib (3.2MB)
OK: Valid Mach-O shared library動いた。次。
AI アシスタント。JSON 形式。4つのフィールド、全て string。SessionID、Type、Tool、Args。
HTTP の痕跡はどこにもない。全く別のプラグインだ。
だが同じテンプレートから生まれた。
libtest-ai-plugin-darwin-arm64.dylib (3.2MB)動いた。もうひとつ。
IoT センサー。custom 形式。3つのフィールド。DeviceID、SensorType、Value。
独自の正規表現パーサー。
libtest-iot-plugin-darwin-arm64.dylib (3.2MB)3つとも動いた。
Level 2 パイプラインテスト、17/17 PASS。
スループット 14,238 events/sec。要件の142倍。
同じテンプレートから、HTTP も、AI も、IoT も。
テンプレートはドメインを知らない。ドメインを知っているのは、ユーザーだけだ。
学び
テストは信頼を可視化する手段だ。3つのドメインで make build が通った瞬間、証明が完了した。
数字
| 指標 | 値 |
|---|---|
| 期間 | 14日間 |
| タスク | 29 |
| テンプレート | 23 |
| スキル | 7 |
| 変更ファイル | 72 |
| 追加行 | +13,812 |
| 設計レビュー | 7回(81件修正) |
| コードレビュー | 3ラウンド(17件修正) |
| 受け入れテスト | 5件(全パス) |
遂行したタスク
この期間に実際に手を動かして行った作業を、記録として残しておく。
- 要件定義書 v5.6 + タスク定義書 v2.6(7回レビュー、81件修正)
- 29タスク実装(23テンプレート + 7スキル + 1エージェント)
- PRレビュー3ラウンド(17件修正 + バックポート)
- ドキュメント4件(README、CLAUDE.md、QUICKSTART、USER_GUIDE)
- 受け入れテスト AT-1〜AT-5(ゴールデンファイル18ファイル + 機能検証)
結び — 道具を作る道具
160 日間で Falco プラグインを作った。
14 日間で、Falco プラグインを 作る道具 を作った。
HTTP でも。AI でも。IoT でも。まだ名前のないログソースでも。
TK:「道具は、使われて初めて価値を持つ。」
次に監視すべきログが何であれ、5つのプレースホルダーが待っている。