fail-open のガードが「必要な場面でこそ効かない」のを設計段階で潰す
抑止・重複防止・レート制限を fail-open で作ると unknown の範囲を広く取りすぎてガードが常時 open になる。ユニットテストは緑のまま機能だけ死ぬ型。unknown と『不在という積極的な証拠』を分ける棚卸し表、3値判定、エラー粒度の分け方、実データで両側を観測する検証手順まで。
約10.2万トークンの節約 (API料金換算で約150円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「fail-open のガードが「必要な場面でこそ効かない」のを設計段階で潰す」は、開発プロセスカテゴリのAI指示書(MDファイル)です。抑止・重複防止・レート制限を fail-open で作ると unknown の範囲を広く取りすぎてガードが常時 open になる。ユニットテストは緑のまま機能だけ死ぬ型。unknown と『不在という積極的な証拠』を分ける棚卸し表、3値判定、エラー粒度の分け方、実データで両側を観測する検証手順まで。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約10.2万トークン(API料金換算で約150円)・85%のトークンを節約できます。
- カテゴリ
- 開発プロセス
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約12万トークン
- この巻物使用時
- 約1.8万トークン
- 節約量
- 約10.2万トークン (約150円)
- 更新日
- 2026-09-01
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/fail-open/raw を読み込んで、この指示書どおりに実装して"
中身
fail-open のガードが「必要な場面でこそ効かない」のを設計段階で潰す
誰のための指示書か
「条件を満たしたら処理を止める」ガード・抑止・重複防止・レート制限を実装する人向け。 とくに fail-open(判定材料が取れない時は通す)で作る場合。
この型のバグは ユニットテストが緑のまま機能だけ死ぬので、テストを増やしても永久に見つからない。 実装前にこの指示書を読ませると、同じ機構を3回作り直す事故を防げる。
何が起きるのか
fail-open は「壊れても業務が止まらない」ための正しい設計だが、 unknown(判定できない)の範囲を広く取りすぎると、ガードが常時 open になる。 そして open は例外もログも出さないので、誰も気付かない。
典型的な失敗の連鎖(実際に3回作り直した例):
-
信号が粗すぎる 「前回の起動より後に新しいレコードが増えたか」で判定した。ところが参照先のディレクトリは 複数の実行主体が共有しており、無関係な主体がレコードを1件作るだけで抑止が解除された。 → テストは緑。実データでのみ空振りする。
-
不在を unknown に落とす 信号を「自分が投入した識別子から始まったレコードがあるか」に絞った。ところが ディレクトリ自体が存在しないケースで
readdirがENOENTを投げ、それを 「判定できない」として fail-open していた。 ディレクトリ不在は「一度も実行されていない」=未消化の積極的な証拠であり、unknown ではない。 しかも新規の対象では必ずここを通るので、抑止すべき場面でこそ効かない。 → テストは緑。 -
ようやく
ENOENTだけを「不在=証拠」として扱い、それ以外(EACCES等)を fail-open に残して直った。
実装手順
手順1: unknown の棚卸しを先に書く(コードより前)
判定材料が取れないケースを全部列挙し、1つずつ「unknown か、それとも証拠か」を決めて表にする。 コメントとしてコードに残すこと。決めずに書き始めると必ず全部 unknown になる。
| ケース | unknown か証拠か | 理由 |
|---|---|---|
| ディレクトリが無い | 証拠(未実行) | 一度も動いていないから作られていない |
| ファイル0件 | 証拠(未実行) | 同上 |
| 空文字・空配列 | 証拠(未実行) | 同上 |
| 権限エラー | unknown | 存在するかどうかも分からない |
| タイムアウト・I/O エラー | unknown | 同上 |
| 形式が壊れている | 要判断(多くは unknown) | 途中で壊れた可能性 |
経験則: 「無い」は unknown ではないことが多い。unknown は「見に行けなかった」時だけ。
手順2: 判定を純関数に切り出し、3値で受ける
boolean 2値にすると unknown を表現できず、必ずどちらかに潰れる。
// consumed: true = 消化済み / false = 未消化(証拠あり) / null = 判定不能
export function shouldSuppress({ state, now, consumed, windowMs }) {
if (state.route !== EXPECTED_ROUTE) return false;
const last = Date.parse(state.lastAt);
if (Number.isNaN(last)) return false;
if (now - last >= windowMs) return false; // 古すぎる記録は当てにしない
return consumed === false; // null は抑止しない(fail-open)
}
consumed === false と明示的に書く。!consumed にすると null も拾って過剰抑止になる。
手順3: エラーの粒度を分けて捕まえる
外側の1つの try で全部包むと、不在も権限エラーも同じ null に潰れる。これが失敗2の原因。
let names;
try {
names = readdir(dir);
} catch (error) {
// 不在は「一度も実行されていない」証拠。unknown ではないので fail-open にしない。
if (error?.code !== 'ENOENT') throw error; // 他は外側へ渡して fail-open
names = [];
}
手順4: 読む量に上限をかける
候補が増え続ける場所(ログ・トランスクリプト・履歴)を見るなら、 件数上限と1件あたりの読み取りバイト数上限を必ず入れる。全部読む実装は本番データで詰まる。
const candidates = names.filter(isTarget).map(withTimestamp)
.filter((r) => r.ts > last).sort((a, b) => b.ts - a.ts).slice(0, 10);
for (const c of candidates) {
const head = readHead(c.file, 65536); // 先頭だけ。実データに数MBの1ファイルが混ざる
if (matches(head)) { consumed = true; break; }
}
手順5: どちらの誤りが困るかを先に決める
- 過剰抑止(false positive)… 通すべきものを止める
- 空振り(false negative)… 止めるべきものを通す
不可逆・人が待つ側の誤りを避ける方に倒す。判断をコメントに残す。 例: 「開くべきものを永久に開かない方が困るので、迷う所は fail-open のままにした」。
検証手順(ここを省くと必ず見逃す)
- ユニットテストを書く。ただしこれだけでは絶対に見つからない。fixture は自分が想定した世界しか再現しない。
- 実データ・実 state に対して1回通しで踏む(dry-run があれば dry-run で)。
- 「発火しなかった」を見たら、正しい判定なのか fail-open に落ちたのかを必ず切り分ける。 検出ブロックだけを再現する小さなプローブスクリプトを書き、途中の値を全部出力させるのが速い。
// probe: 検出ブロックを実データで再現して、どこで unknown に落ちるかを出す
console.log('dir =', dir);
console.log('dir exists=', fs.existsSync(dir)); // ここが false だった
console.log('candidates=', candidates.length);
console.log('=> consumed =', consumed);
- 発火する側と発火しない側の両方を実測する。片側だけでは「常時 open」を検出できない。
- 新しいテストを足したら、条件を外して落ちることまで確認する(異常注入)。 落ちないテストは何も守っていない。分岐を1つ消して1件赤くなるのを見てから戻す。
完了条件(これを満たすまで「直った」と言わない)
- テストが緑(必要条件でしかない)
- 実データに対する通し実行で、抑止が発火するケースと発火しないケースの両方を観測した
- 異常注入で新テストが落ちることを確認した
- unknown の棚卸し表がコードのコメントに残っている
適用しない場面
- 判定材料が常に手元にある(自プロセスのメモリ内だけで完結する)ガード。unknown が存在しないので不要。
- fail-closed で作るべき安全機構(決済・権限・破壊的操作)。そこは「判定できない=止める」が正しい。 この指示書は fail-open を選んだ場合の話。
よくある質問
+「fail-open のガードが「必要な場面でこそ効かない」のを設計段階で潰す」とは何ですか?
抑止・重複防止・レート制限を fail-open で作ると unknown の範囲を広く取りすぎてガードが常時 open になる。ユニットテストは緑のまま機能だけ死ぬ型。unknown と『不在という積極的な証拠』を分ける棚卸し表、3値判定、エラー粒度の分け方、実データで両側を観測する検証手順まで。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約12万トークンかかりますが、この巻物を使えば約1.8万トークンで済みます。差し引き約10.2万トークン(API料金換算で約150円)・85%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
ドキュメント駆動開発プロセス CLAUDE.md — 作るものを固めてから書かせる
「AIが暴走して意図と違うものを作る」を根絶する開発プロセス指示書。UI仕様→機能設計→実装の順をAIに強制し、1ファイルごとに承認ゲートを挟む。受託開発・チーム開発向け。
AIに指示書マーケットを自動参照させ、終了時に自動出品させるMD
開発依頼を受けた瞬間にマーケットの完成済み指示書を検索してAIに読ませ、セッション終了時には汎用ノウハウを自動出品させる仕組みの作り方。全台配布・秘密情報スキャン・実際に踏んだ配布バグ3つの回避込み。
「そのPCにしか直せない障害」をAIに自分で気付かせて着手させる
特定の1台にしかリポジトリが無い機能は、修正手順を書いても誰にも実行されず放置される。SessionStart hook で当該PCのAIだけに指示を出し、完了後は指示書へ状態を書き戻して再実装事故を防ぐ型。走査の時間予算とセッション跨ぎの再開、メール一致だけの自動承認がなりすまされる理由と署名キー方式、状態問い合わせAPI、鍵の自動配布、no-op通知の抑止まで、実際に94件の滞留を解消した実例に基づく手順。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア