When brand rule checks were first integrated into the pre-publish gate, Yakumo’s site already contained official names of external organizations and references to third-party categories such as “self-described AI development companies.” A flat forbidden-word list that uniformly blocks “会社”, “企業”, “firm”, and “company” would catch even expressions that fall under the brand rule’s exception clause (references to other organizations are acceptable), blocking them all. The gate script needed a mechanism to distinguish between “self-references to Yakumo’s own organization” and “references to other firms.”
Hardcoding exceptions directly into the gate script was not an option. Updating the gate every time a new external organization name appeared in an article would be an unsustainable workflow. The solution was to make exceptions SSOT-based — structuring them as an allow-list in brand.ts so that gate scripts only need to reference it.
→ For the overall gate architecture, see Structural Design of the Owned Media Engine mcluhan. This article focuses specifically on the exception design (SSOT approach) for brand checks.
Two-layer Design: Allow-list and Pattern-list
Layer 1: externalOrgAllowlist — Known Organization Names (Exact Match)
The first layer is a list of known external organization names matched by exact string comparison.
Situations where external organization names appear in articles are concrete: career background descriptions such as “handled AI initiatives at a previous business enterprise,” or comparative contexts like “differences from other organizations in the market” — none of these are self-references to Yakumo’s own team. By registering the organization names found in those contexts in the allow-list, matching strings can be marked as pass.
Exact-match evaluation is straightforward. If body.includes(orgName) returns true for a string in the allow-list, any “company” match in that vicinity is excluded as a false positive.
Layer 2: externalReferencePatterns — External Reference Category Patterns (Regex)
The second layer uses regular expressions to structure “patterns indicating reference to other companies.” While the allow-list manages individual organization names, the pattern-list captures context that generally points to external categories.
There are three typical patterns:
- Contrast with other firms: “‘How we differ from other AI development firms…’” or “‘Unlike web production companies…’”
- Criticism of other firms: “‘Many self-described AI development companies…’” or “‘Companies that just bash OpenAI…’”
- Past career history: “‘Experience from the firm I co-founded…’” or “‘Practices from the company I used to work at…’”
All three of these patterns are self-reference-free and fall outside the scope of the brand rule. Structuring them as regular expressions allows them to pass without needing to register each individual string in the allow-list.
Criteria for Choosing Between the Two Layers
| Layer | Target | Management Approach |
|---|---|---|
| Layer 1 (externalOrgAllowlist) | Known specific organization names (e.g., “株式会社〜”, “〜Inc”) | Exact-match string list |
| Layer 2 (externalReferencePatterns) | Generic external reference patterns (e.g., “other 〜 companies”, “self-described 〜“) | Regular expressions |
Guideline: Decide based on “will this reference appear repeatedly in the future?” Specific organization names go to Layer 1; generic expressions like “comparison with other firms in the category” go to Layer 2. When in doubt, start with a Layer 2 regex and move to individual Layer 1 registration if false positives increase.
SSOT Consolidation into brand.ts
Type Definition and Initial Values for externalOrgAllowlist
Two fields were added to src/config/brand.ts.
// src/config/brand.ts(追加分)
externalOrgAllowlist: [
'〈他組織の正式名 A〉',
'〈Other Org A〉',
] as const,
The type is equivalent to readonly string[]. Using as const causes TypeScript to infer a literal union type, so typos are caught at compile time.
At the time of writing, externalOrgAllowlist contains 2 registered organization names.
Regex Definitions for 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,
Each pattern is designed with “preventing cross-sentence false positives” as the priority. [^。]{0,30} means up to 30 characters that do not include a Japanese period (。). This ensures that a self-reference in the previous sentence followed by “会社” (a NG self-reference) does not accidentally pass by blending into an “other 〜” pattern in the next sentence.
At the time of writing, externalReferencePatterns contains 7 defined patterns.
Responsibility Split with Existing forbiddenWords
brand.ts already had forbiddenWords defined (e.g., “画期的”, “革命的”, “DX”). These are absolute prohibited terms — words that must never be used in any context.
By contrast, the newly added externalOrgAllowlist and externalReferencePatterns handle “terms that are acceptable depending on context.” The responsibilities are clearly distinct.
| Field | Responsibility | Evaluation Logic |
|---|---|---|
forbiddenWords | Absolute prohibited terms (context-independent) | Plain match → fail |
externalOrgAllowlist | Official names of external organizations (false positive prevention) | Exact match → exclude as pass |
externalReferencePatterns | Generic patterns for external references (false positive prevention) | Regex match → exclude as pass |
The essence of SSOT consolidation is organizing this responsibility split into a single file within brand.ts. Gate scripts only need to import brand.ts and reference each field — they don’t need to carry their own exception logic.
Reference Design from Gate Scripts
Pattern for Importing brand.ts and Matching Against the Allow-list
The G10 implementation in the gate script is a thin wrapper that references 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' };
}
Match Priority Order (allow-list → pattern → proximity → fail)
The evaluation flow is as follows:
- Exclude via externalOrgAllowlist → Sentences containing known external organization names pass
- Exclude via externalReferencePatterns → Portions matching external reference patterns pass
- Evaluate via proximity analysis → Remaining matches that are in close proximity to Yakumo / 八雲 / 私たち fail
- All others → warning → Context is unclear (possibly another organization). Deferred to human review
This order matters. Processing the allow-list and patterns first reduces the risk of proximity analysis catching false positives.
The Gate rule that handles allow-list matching is implemented as G10 (checkG10_CompanySelfReference in scripts/blog-gate.ts). Its first appearance in the repository was in the 2026-05-18 commit d1e3ae7 (feat(brand/gate): G10 catches Yakumo self-reference as 会社 / firm), in which the externalOrgAllowlist / externalReferencePatterns fields were also added to src/config/brand.ts.
Allow-list Update Workflow
One-file Update Process When Using a New External Organization Name in an Article
The greatest benefit of making the allow-list SSOT-based is that “when a new external organization name is used in an article, the work is completed by updating a single file.”
To add context: this allow-list was not created reactively after false positives appeared. At the time G10 was implemented, Yakumo’s site already contained official names of external organizations and references to third-party categories (e.g., comparative expressions like “self-described AI development companies”). A flat forbidden-word implementation would have blocked all of these. Therefore, G10 was introduced from day one with an allow-list and reference patterns already included. Adding new external organization names later requires the same single-file update to that allow-list.
The process is three steps:
- Add the organization name string to
externalOrgAllowlistinsrc/config/brand.ts - Run
npm run blog-gateto confirm the false positive is resolved - Submit a PR for the change to be reviewed, then merge
No changes to the gate script are needed. Updating brand.ts alone changes the gate’s behavior.
PR Review Checklist for Verifying Allow-list Validity
Adding entries to the allow-list requires careful judgment. There is a risk that “allowing everything indiscriminately makes the brand rule meaningless.” The following points should be verified during PR review:
- Is the organization name being added the official name of a real external organization other than Yakumo?
- Is it used in close proximity to expressions like “within our organization” or “our team” (i.e., not disguising a self-reference)?
- Does it fall under BRAND.md’s exception clause (“references to other organizations and firms are acceptable”)?
- Is the reason for adding it (which article it’s used in, what context) documented in the PR description?
Only when all four criteria are satisfied should the allow-list addition be approved.
Summary — Reducing Gate Maintenance Cost Through SSOT
Design Principle: Keeping the Gate Script a Thin Wrapper
After consolidating the allow-list and pattern-list into brand.ts, the gate script becomes “a thin wrapper that simply references brand.ts and executes the evaluation logic.”
| Task | Before SSOT | After SSOT |
|---|---|---|
| Use a new external organization name in an article | Modify the gate script | Add one line to brand.ts only |
| Add an external reference pattern | Modify the gate script | Add one regex to brand.ts only |
| Check “why does this pass?” | Read the gate script code | Read the fields in brand.ts |
The value of SSOT consolidation is being able to always answer “where are the exceptions?” with “they’re in brand.ts.” When the gate script holds no business logic and brand.ts becomes the single source of truth, the total cost of handoff, review, and change management decreases.
In practice, the update frequency is quite low. The allow-list and pattern set registered alongside G10 continue to function as-is, and there have been zero instances of adding organization names since the SSOT consolidation. The allow-list is not a mechanism whose “value increases with higher addition frequency” — it is better understood as a mechanism that “covers known external references with an initial registration, then vets any subsequent additions carefully as exceptions.” The moment an addition becomes necessary is precisely when the PR review checklist above should be run.
For technical implementation details (proximity analysis and stripCodeContexts design), see Automating Brand Rule Checks — Patterns for Distinguishing Self-reference from External References. The balance between false positives and false negatives is covered in Pre-publish Gate Design — Balancing False Positives and False Negatives.