Business content-gen 12 min read

Pre-publish gate 設計 — false positive と false negative の両立

gate を厳しくすると失敗開示記事が block され、ゆるくすると本番 NG が通過する。解析粒度の 3 レベルと fail / warn の二段階判定で両立する設計を解説する。

公開 2026-05-23 森本拓見

本記事に登場する HUMAN_INPUT マーカーとは、AI 執筆 skill が記事本文に残す「ここは人間が後で確定値を埋めるべき」を示すプレースホルダー(形式例: <!-- HUMAN_INPUT: 数値を記入 -->)。

「AI 量産ブログを 5 日で全撤退した話」を記事として書こうとしたとき、gate が 16 件の fail を報告した。本文に HUMAN_INPUT という文字列が 10 件、/blog/ というパスが 6 件——どちらも失敗事例を「説明している」prose であって、実際のプレースホルダーや死リンクではなかった。gate を厳しくすると失敗を開示しようとする記事が通らない。ゆるくすると実際の未完成マーカーが公開される。どちらが致命的かという問いに対する答えは、コンテキストによって変わる。

→ gate の全体構造は owned media 運用エンジン mcluhan の構造設計 を参照してほしい。本記事は false positive / false negative のバランス設計に特化する。非エンジニアの読者に向けて、技術用語には括弧で補足を入れる。


false positive と false negative の定義

false positive: 問題ない記事が弾かれる(書き続けられなくなる)

false positive(偽陽性)は「問題がないのに gate が止める」ケースだ。

八雲が経験した具体例: 失敗事例を説明する記事に「HUMAN_INPUT マーカーが 7 か所残っていた」という記述がある。gate はその文字列(HUMAN_INPUT)を見つけ、「未完成のプレースホルダーが残存している」と判定して fail を出す。実際には HUMAN_INPUT について語っている説明文だ。

gate が false positive を出し続けると、書くべき記事が書けなくなる。「自分たちの失敗を開示する」という owned media のコンセプトそのものが崩れる。

false negative: 問題ある記事が通過する(本番 NG が公開される)

false negative(偽陰性)は「問題があるのに gate が見逃す」ケースだ。

八雲が Phase 0 で経験した具体例: HUMAN_INPUT マーカーが残ったまま公開された記事が 4 本。内部リンクが存在しないページへの参照が 76 本。これらは gate が存在しなかった(または実装が甘かった)結果として、本番に出た。

gate が false negative を出し続けると、品質 NG な記事が公開され続ける。

pillar 記事「AI 量産ブログを 5 日で全撤退した話」の draft で実際に発生した false positive は、G1(HUMAN_INPUT 残存)が 10 件・G2(dead link)が 6 件、合計 16 件だった。Phase 0 の false negative は HUMAN_INPUT 漏出 4 本・死リンク 76 本の本番公開だった。

どちらが致命的かはコンテキストによる

2 種類の問題のどちらが深刻かは、owned media の運用フェーズと目的によって変わる。

観点false positive が致命的な場面false negative が致命的な場面
コンセプト失敗開示型 owned media で失敗記事が書けない品質保証が訴求の owned media でNG記事が公開される
SEO リスク記事が書けないことで情報量が減る品質 NG 記事が Google に indexed される
読者体験書けない記事がある(読者には見えない)読んだ記事が未完成(読者に直撃する)

SEO と読者体験のどちらを優先するかという問いと同構造だ。八雲の場合、「失敗を開示することが訴求力」という性格から、false positive の防止と false negative の防止を同等に重要と扱っている。


解析粒度の 3 レベルとトレードオフ

Level 1(plain match): false positive 多 / 実装コスト低

body.includes('HUMAN_INPUT')

本文全体を対象に文字列検索する。実装は 1 行で完結するが、code block・inline code・prose での説明を区別しない。失敗事例を説明する記事は構造的に false positive が出る。

適切な用途: 「本文のどこにも絶対に登場してはいけない語」の検出。たとえば forbiddenWords(画期的・革命的・DX など)はどの文脈でも NG なので Level 1 で十分だ。

Level 2(code-stripped): 中間

const stripped = stripCodeContexts(本文から code block / inline code を除去した文字列);
stripped.match(/特定フォーマットのパターン/)

fenced code block(バッククォート 3 つで囲まれた箇所)と inline code(バッククォート 1 つで囲まれた箇所)を前処理で除去してから regex(正規表現)でマッチする。「コードとして説明しているテキスト」と「実際のプレースホルダー」を区別できる。

適切な用途: HUMAN_INPUT マーカーや内部リンクのパスのような、「prose の中では許可して、実際のマーカーやリンクとしては禁止する」ケース。

Level 3(proximity + allow-list): false positive 少 / 実装コスト高

// allow-list でまず除外
// externalReferencePatterns で除外
// 残ったマッチに proximity(前後 50 文字)を適用

「誰を指しているか」まで判定する。実装は複数の判定層が必要で、コストは高い。しかし false positive を最小化できる。

適切な用途: G10(brand rule の自己言及チェック)のように、同じ単語が文脈によって OK/NG に分かれるルール。

3 レベルの比較表

レベル解析手法false positive リスク実装コスト適用ルール例
L1plain match高(文脈を区別しない)forbiddenWords(絶対禁止語)
L2code-stripped + regexG1(HUMAN_INPUT)/ G2(dead link)
L3allow-list + pattern + proximityG10(自己言及チェック)

ルールに応じて適切なレベルを割り当てることが、false positive / false negative のバランスを取る設計の核心だ。


fail / warn の二段階判定設計

fail: draft: false への移行を block するルール(G1〜G13 相当)

fail(失敗)は「このルールに引っかかったら記事を公開できない」という判定だ。draft: false(公開可能状態)への移行を物理的に block する。

G ルール(G1〜G13)が fail に相当する。HUMAN_INPUT の残存・死リンク・著者情報の不整合・自己言及 brand rule 違反——いずれも公開してはいけない状態を表す。現時点で 13 件の G ルールが実装されている。

warn: 推奨違反として記録するがブロックしない(W1, W2, W3, W4, W5, W7 相当)

warn(警告)は「ルールに引っかかっているが、公開を止めはしない」という判定だ。記録は残るが、ビルドは通る。

W ルール(W1, W2, W3, W4, W5, W7)が warn に相当する。1 日に 6 本超を公開しようとしている・description が推奨文字数を外れている——これらは品質の警告であって、絶対的な禁止ではない。現時点で 6 件の W ルールが実装されている。

warn の蓄積をレビュー判断材料にする設計

warn は積み上がる。「description が短い記事が 5 本ある」「scheduledAt が同一日に 4 本集中している」という状態をレビュアーが見て判断できる。

実装上は、gate の実行結果を summary として出力し、warn の件数と内容を一覧にする。レビュアーはその summary を確認して「warn のまま公開する」か「warn を解消してから公開する」を決断する。完全自動化するのではなく、warn をレビュアーへの情報提供として使う設計だ。


meta-content(失敗開示記事)への対応

自己参照コンテンツが構造的に発生するコンテキスト

八雲が目指す owned media は「pipeline 自身の改善を記事にする」という自己参照的な構造を持つ。gate を改善したら gate 改善の記事を書く、失敗したら失敗の記事を書く——この構造から meta-content(パイプラインやルールを本文で説明する記事)は避けられない。

meta-content には以下の種類がある。

  • 失敗開示記事: 「HUMAN_INPUT マーカーが 7 か所残っていた」「/blog/ リンクが 76 本死んでいた」を本文で説明する
  • gate 解説記事: G1〜G10 のルール内容を本文で説明する(本記事もその 1 つ)
  • パターン解説記事: 「externalReferencePatterns」「stripCodeContexts」を本文で紹介する

これらはすべて、gate のチェック対象になる文字列を本文中に持つ。

gate が自己矛盾を起こさないための allow パターン定義

meta-content への対応策は Level 2(code-stripping)だ。code block や inline code 内で説明されているテキストは検出から外す。

失敗開示記事が「HUMAN_INPUT というプレースホルダーが 7 か所残存していた」と書く場合、HUMAN_INPUT はバッククォートで囲まれた inline code として表記するルールにする。stripping 後には「プレースホルダーが 7 か所残存していた」という文字列になり、G1 には引っかからない。

記述ルールを決めるだけで構造的に解決できる。「meta-content の中では HUMAN_INPUT を説明するときは inline code か code block で囲む」という執筆規約と gate の Level 2 実装が組み合わさることで、自己矛盾は発生しない。


ルール別の適切な解析レベル割り当て

G1 / G2 → Level 2 以上(code-stripped regex)

前節の具体事例で示した通り、G1(HUMAN_INPUT 残存)と G2(dead link)は Level 2 が適切だ。code context を除去した後に特定フォーマットに限定して検出する。

G10 → Level 3(proximity + allow-list)

G10(brand rule の自己言及チェック)は Level 3 が必要だ。会社 という単語が自分側を指しているかどうかは、allow-list と externalReferencePatterns で除外した後に proximity ウィンドウで判定する。

W 系 → plain でも warn に留める設計

W ルール(rate-limit・description 長など)は plain match で判定し、結果を warn として記録する。fail でないため公開は止まらない。レビュアーが warn の数・内容を確認して判断する。

W ルールを fail に昇格させるケースは、運用を経て判断する。「description が短い記事が毎週 10 本出ている」という状況が続いたなら、W4 を fail に変えることを検討できる。最初は warn で始めて、運用データを見て判断する。

現時点では warn から fail に昇格させたルールは無い。むしろ逆方向の例が 1 件ある。G1(HUMAN_INPUT 残存)は当初「draft 時 warn / 公開時 fail」で運用していたが、render 側で isPublished() が HUMAN_INPUT を含む記事を自動的に非公開扱いにする仕組みを入れた段階で、gate 側は常に warn のみに降格させた。執筆途中の記事が build を止めないようにするためで、二重防御を gate ではなく render 層で吸収する判断だ。

W4・W5・W7 については、当初の設計通り warn のままで運用している。description / title 長や series 不整合は、運用データ上「毎週 10 本出ている」ような閾値には届いておらず、レビュアーが warn を見て個別判断する現状で十分機能している。


まとめ

gate の false positive / false negative バランスは「ルールに適切な解析レベルを割り当て、fail と warn を使い分ける」設計で取れる。

整理すると 2 点だ。

  1. 解析レベルはルールの性質に応じて選ぶ。絶対禁止語は L1(plain)、prose と code の区別が必要な検出は L2(code-stripped)、文脈依存の判定は L3(proximity + allow-list)。
  2. fail と warn の境界は「公開してはいけない状態か」で決める。未完成マーカー・死リンク・brand rule 違反は fail、推奨違反は warn。warn はレビュアーへの情報提供として使い、完全自動化しない。

「gate を厳しくすると書けなくなる」という問いの答えは「解析レベルが間違っている」ということだ。Level 1 で書いた gate が meta-content に詰まるのは想定内であり、Level 2 / Level 3 に移行することで解消できる。

brand rule の SSOT 化と allow-list 設計の詳細は Allow-list 駆動の brand check を、技術実装の詳細は Brand rule の機械チェック化Pre-publish gate の高度化 を参照してほしい。

SHARE X でシェア B! はてブ