Business content-gen 14 min read

Allow-list 駆動の brand check — 他組織名 SSOT 化による exception 設計

brand rule の例外管理を SSOT に集約する設計。brand.ts に allow-list と reference pattern を定義し、gate スクリプトがそれを参照する構造を解説する。

公開 2026-05-23 森本拓見

pre-publish gate に brand rule チェックを組み込もうとした時点で、八雲のサイト内には他組織の正式名や「自称 AI 開発会社」のような他社カテゴリ言及が既に存在していた。flat な禁則語リストで「会社」「企業」「firm」「company」を一律に禁止すると、brand rule の例外条項(他社への言及は問題ない)に該当する表現まで全てブロックされてしまう。gate スクリプトには「Yakumo 自身への言及」と「他社への言及」を区別する機構が必要だった。

exception を gate スクリプト側にハードコードする修正は避けた。新しい他組織名が記事に登場するたびに gate を修正するのは持続できない運用だからだ。解決策は exception の SSOT 化——brand.ts に allow-list として構造化して持ち、gate スクリプトは参照するだけにする設計だ。

→ gate の全体設計は owned media 運用エンジン mcluhan の構造設計 を参照してほしい。本記事は brand check の exception 設計(SSOT 化)に特化する。


allow-list と pattern-list の 2 層設計

Layer 1: externalOrgAllowlist — 既知の組織名(完全一致)

最初の層は既知の他組織の正式名を完全一致で照合するリストだ。

記事に他組織の正式名が登場する場面は具体的だ。「前職の事業会社で AI 推進を担当していた」という経歴記述や、「市場にいる他組織との比較」という文脈——いずれも Yakumo 自身への言及ではない。そこに含まれる組織名を allow-list に登録することで、該当する文字列を pass にできる。

完全一致の判定は単純だ。body.includes(orgName) が allow-list 内の文字列で true なら、その周辺の「会社」マッチは false positive として除外する。

Layer 2: externalReferencePatterns — 他社カテゴリ言及パターン(正規表現)

第 2 層は正規表現で「他社への言及パターン」を構造化する。allow-list が個別の組織名を管理するのに対して、pattern-list は「他社カテゴリを指している文脈」を汎用的に捉える。

典型的なパターンは次の 3 種類だ。

  • 他社カテゴリ対比: 「他の AI 開発会社との違いは〜」「Web 制作会社とは異なり〜」
  • 他社批判: 「自称 AI 開発会社の多くは〜」「OpenAI を叩くだけの会社は〜」
  • 過去経歴: 「共同創業した会社での経験を〜」「以前所属していた会社の慣習が〜」

これら 3 パターンはいずれも自己言及ではなく、brand rule の対象外だ。正規表現で構造化することで、個々の文字列を allow-list に登録せずに pass にできる。

2 層の使い分け基準

対象管理方法
Layer 1 (externalOrgAllowlist)既知の具体的な組織名(「株式会社〜」「〜Inc」など)完全一致の文字列リスト
Layer 2 (externalReferencePatterns)汎用的な他社言及パターン(「他の〜会社」「自称〜」など)正規表現

基準: 「この言及が今後も繰り返し登場するか」で判断する。特定の組織名は Layer 1 へ、「他社カテゴリ比較」のような汎用表現は Layer 2 へ。迷ったら Layer 2 の正規表現で始めて、誤検出が増えたら Layer 1 に個別登録する方向で調整する。


brand.ts への SSOT 化

externalOrgAllowlist の型定義と初期値

src/config/brand.ts に 2 つのフィールドを追加した。

// src/config/brand.ts(追加分)
externalOrgAllowlist: [
  '〈他組織の正式名 A〉',
  '〈Other Org A〉',
] as const,

型は readonly string[] 相当。as const により TypeScript が literal union 型に推論するため、typo があればコンパイル時に検出できる。

現時点の externalOrgAllowlist には 2 件の組織名が登録されている。

externalReferencePatterns の正規表現定義

// src/config/brand.ts(追加分)
externalReferencePatterns: [
  /他の[^。]{0,30}?(会社|企業)/g,
  /自称[^。]{0,30}?(会社)/g,
  /(他|別の)[^。]{0,30}?(事業|開発)[^。]{0,30}?会社/g,
  /(共同|過去に)?創業(した|していた)?会社/g,
  /(Web|web)\s*制作会社/g,
  /other\s+(AI\s+)?(development\s+)?(firms|companies)/gi,
  /self-described\s+["「]?AI\s+(companies|firms)["」]?/gi,
] as const,

各パターンの設計は「文をまたいだ誤検出を防ぐ」ことを優先している。[^。]{0,30} は句点(。)を含まない最大 30 文字を意味する。これにより、直前の文に自己言及があって「会社」と書いた場合(自己言及 NG)が、次の文の「他の〜」パターンにまぎれ込んで pass にならない。

現時点の externalReferencePatterns には 7 件のパターンが定義されている。

既存 forbiddenWords との責務分担

brand.ts には forbiddenWords(「画期的」「革命的」「DX」など)が既に定義されていた。これらは「どの文脈でも使ってはいけない」絶対禁止語だ。

一方、今回追加した externalOrgAllowlistexternalReferencePatterns は「文脈によっては使ってよい語」を扱う。責務は明確に異なる。

フィールド責務判定ロジック
forbiddenWords絶対禁止語(context 不問)plain match で fail
externalOrgAllowlist他組織の正式名(false positive 防止)完全一致で pass に除外
externalReferencePatterns他社言及の汎用パターン(false positive 防止)正規表現でマッチしたら pass に除外

この責務分担を brand.ts 内で 1 ファイルに整理することが SSOT 化の本質だ。gate スクリプトは brand.ts を import して各フィールドを参照するだけで、exception ロジックを自分で持たなくてよい。


gate スクリプトからの参照設計

brand.ts を import して allow-list と照合するパターン

gate スクリプトの G10 実装は brand.ts を参照する薄いラッパーだ。

// scripts/blog-gate.ts
import { brand } from '../src/config/brand';

function checkG10(body: string): GateResult {
  // Step 1: allow-list 内文字列を含む部分を除外
  let processedBody = body;
  for (const orgName of brand.externalOrgAllowlist) {
    // orgName を含む sentence を一時的にプレースホルダーに置換
    processedBody = processedBody.replace(
      new RegExp(`[^。]*${escapeRegex(orgName)}[^。]*`, 'g'),
      '__ALLOWED__'
    );
  }

  // Step 2: externalReferencePatterns にマッチする部分を除外
  for (const pattern of brand.externalReferencePatterns) {
    processedBody = processedBody.replace(pattern, '__ALLOWED__');
  }

  // Step 3: 残ったマッチに proximity 解析を適用
  const companyWords = /会社|企業|firm|company|corporation/gi;
  const selfIdentifiers = ['Yakumo', '八雲', '私たち'];
  const PROXIMITY = 50;

  let match: RegExpExecArray | null;
  const failures: string[] = [];

  while ((match = companyWords.exec(processedBody)) !== null) {
    if (match[0] === '__ALLOWED__') continue;
    const window = processedBody.slice(
      Math.max(0, match.index - PROXIMITY),
      Math.min(processedBody.length, match.index + PROXIMITY)
    );
    if (selfIdentifiers.some(id => window.includes(id))) {
      failures.push(`"${match[0]}" at position ${match.index}`);
    }
  }

  return failures.length > 0
    ? { result: 'fail', rule: 'G10', message: failures.join(', ') }
    : { result: 'pass', rule: 'G10' };
}

マッチの優先順位(allow-list → pattern → proximity → fail)

判定フローを整理すると次のようになる。

  1. externalOrgAllowlist で除外 → 既知の他組織名を含む文は pass
  2. externalReferencePatterns で除外 → 他社言及パターンに一致する部分は pass
  3. proximity 解析で判定 → 残ったマッチが Yakumo / 八雲 / 私たちと近接していれば fail
  4. それ以外は warning → 文脈が不明(他組織の可能性)。人間確認に委ねる

この順序が重要だ。allow-list と pattern を先に処理することで、proximity 解析が false positive をつかまえるリスクを下げる。

allow-list 照合を担う Gate ルールは G10scripts/blog-gate.ts 内の checkG10_CompanySelfReference)として実装されている。リポジトリ上の初出は 2026-05-18 の commit d1e3ae7feat(brand/gate): G10 catches Yakumo self-reference as 会社 / firm)で、同じ commit で src/config/brand.tsexternalOrgAllowlist / externalReferencePatterns フィールドが追加されている。


allow-list の更新フロー

新しい他組織名を記事で使うときの 1 ファイル更新手順

allow-list を SSOT 化した最大のメリットは「記事で新しい他組織名を使うときの作業が 1 ファイルの更新で完結する」ことだ。

補足しておくと、この allow-list は「false positive が出てから後追いで作った」ものではない。G10 を実装する時点で、Yakumo のサイト内には他組織の正式名と「他社カテゴリへの言及」(例: 「自称 AI 開発会社」のような対比表現)が既に存在しており、flat な禁則語実装ではこれらをすべてブロックしてしまう。そのため、G10 はリリース時点から allow-list と reference pattern を同梱した状態で導入した。新規の他組織名を後から追加する場合も、同じ allow-list を 1 ファイル更新するだけで済む。

手順は 3 ステップだ。

  1. src/config/brand.tsexternalOrgAllowlist に組織名の文字列を追加する
  2. npm run blog-gate を実行して false positive が解消されたことを確認する
  3. PR で変更をレビューしてもらい、merge する

gate スクリプトに手を入れる必要はない。brand.ts を変更するだけで gate の挙動が変わる。

PR レビューで allow-list の妥当性を確認するチェックリスト

allow-list への追加は慎重に判断する必要がある。「何でも allow にすると brand rule が形骸化する」リスクがあるからだ。PR レビューでは以下の点を確認する。

  • 追加する組織名は Yakumo 以外の実在する他組織 の正式名か
  • 自社内で〜私たちの〜 という表現と近接して使われていないか(自己言及の偽装でないか)
  • BRAND.md の例外条項(「他社・他組織への言及は問題ない」)に当てはまるか
  • 追加理由(どの記事で使うか、どの文脈か)が PR の説明に書かれているか

4 つをすべて満たす場合のみ allow-list への追加を承認する。


まとめ — SSOT 化で gate メンテコストを下げる

gate スクリプトを薄いラッパーに保つ設計方針の整理

allow-list と pattern-list を brand.ts に集約した後の gate スクリプトは「brand.ts を参照して判定ロジックを実行するだけの薄いラッパー」になる。

作業SSOT 化前SSOT 化後
新しい他組織名を記事で使うgate スクリプトを修正brand.ts の 1 行追加のみ
他社言及パターンを追加するgate スクリプトを修正brand.ts の 1 正規表現追加のみ
「なぜ pass になるのか」を確認するgate スクリプトのコードを読むbrand.ts のフィールドを読む

「exception はどこにあるか」という問いに対して、常に「brand.ts にある」と答えられる状態が SSOT 化の価値だ。gate スクリプトがビジネスルールを持たず、brand.ts が唯一の真実の情報源になることで、引き継ぎ・レビュー・変更管理の全コストが下がる。

実運用での更新頻度はかなり低い。G10 と同時に登録した allow-list / pattern set が現時点でもそのまま機能しており、SSOT 化以降に組織名を追加した実績は 0 件だ。allow-list は「追加頻度が高いほど価値が出る仕組み」ではなく、「初期登録で既知の他社言及を網羅し、以降の追加は例外として丁寧に審査する仕組み」として運用するのが実態に近い。追加が必要になった瞬間こそ、PR レビューで前述のチェックリストを通すべきタイミングになる。

技術的な実装の詳細(proximity 解析・stripCodeContexts の設計)は Brand rule の機械チェック化 — 自己言及 vs 他社言及の文脈判別パターン を参照してほしい。false positive と false negative のバランス設計については Pre-publish gate 設計 — false positive と false negative のバランス で扱っている。

SHARE X でシェア B! はてブ