WordPress下書きを自動チェックする公開前CIの作り方

WordPress下書きを自動チェックする公開前CIの作り方

AIに記事の下書きを作らせるようになってから、「公開ボタンを押す前の点検」が急に重みを増しました。

人間が全文を目視するには量が多く、かといって無点検で公開するのは怖い——そんな板挟みを感じたことはありませんか?

WordPressはREST APIで記事を読み書きできるので、下書きの段階で本文を取り出し、機械的に監査するゲートを組めます。

私は、AIが自動投稿した下書きを公開前に点検する、いわば「公開前CI」と呼べる仕組みを自作しました。

途中でWAFが非ブラウザ通信を弾く、ログインCookieが認証に干渉するという2つの壁にぶつかったので、この記事はその越え方も含めた記録です。

前提:なぜ「公開の手前」に検査を置くのか

公開前CIの全体図:AIの下書き投稿から機械検査・人間レビュー・公開までの流れ

前提としてWordPressのREST APIでは、認証付きで context=edit を指定すると、下書き本文の編集用ソースを取得できます。

作成や更新もエンドポイント越しに行えるため、下書きは「プログラムから読み書きできるテキスト」として扱えるわけです。

仕様の全体像はWordPress REST API ハンドブックにまとまっています。

REST API Handbook | Developer.WordPress.org

設計にあたって軸にしたのは、可逆な「下書きの編集」と不可逆な「公開」を分けて考えることでした。

下書きのうちなら、間違えても直せばよいだけ。

しかし公開してしまえば、読者の目に触れ、検索エンジンにも拾われ、簡単には取り消せません。

だからこそ、ソフトウェア開発のCIがマージ前にテストを走らせるのと同じ発想で、公開という不可逆な操作の直前に機械の検問所を置く価値があるのです。

AIが下書きを量産する運用では、ここに検査が無いと品質のばらつきがそのまま公開面に出てしまいます。

壁その1:WAF越しに下書きを読み、Cookieの干渉をかわす

最初の壁は、そもそもREST APIに外からたどり着けないことでした。

対象サイトにはWAF(Webアプリケーションファイアウォール)が入っており、curlのような非ブラウザ通信は弾かれてしまいます。

そこで発想を変え、ブラウザで対象サイト自身を開き、同一オリジンのfetchからRESTを叩く構成にしました。

WAFから見れば正規のブラウザ通信なので、これはすんなり通ります。

認証に使ったのは、アプリケーションパスワードによるBasic認証です。

WordPressの管理画面にある、ユーザーのプロフィールから発行できるあの機能ですね。

なぜ401になるのか

ログインCookieの干渉で401になる流れと、credentialsのomit指定で200になる流れの比較図

ところが、正しい認証情報を渡しているのに、編集系のアクセスだけが401で拒否される現象に遭遇しました。

原因は、ブラウザがそのWordPressにログイン済みだったことにあります。

同一オリジンのfetchは、既定でログインCookieを一緒に送ってしまうのです。

するとWordPressはCookie認証を優先し、nonceが無いという理由で編集系の操作を弾きます。

Basic認証のヘッダを付けていても、Cookieが同乗した時点でそちらが勝ってしまう、という優先順位の問題でした。

credentials:”omit” で直る理由

対策はシンプルで、すべてのfetchにcredentialsのomit指定を付け、Cookieを送らずBasic認証だけを使わせる構成に改めます。

コードの要点を示すと、以下のようなイメージです。

const auth = "Basic " + btoa(WP_USER + ":" + WP_APP_PASS);
const r = await fetch(
  `${location.origin}/wp-json/wp/v2/posts?status=draft&context=edit&_fields=id,title,content`,
  { headers: { Authorization: auth }, credentials: "omit" } // ←Cookieを送らせない
);
const drafts = await r.json();
// drafts[i].content.raw に Gutenberg ソースが入る
// 省略: エラーハンドリングなど

認証の経路をBasic認証の一本に絞ったことで、401は再現しなくなりました。

壁その2:参照原稿なしで文字化けと破損を拾う

こうして読めるようになった下書きは、Gutenberg(WordPressのブロックエディタ)のブロックコメントを含む編集用のソースです。

まずは見出しと段落を取り出して正規化し、語尾の連続や装飾の使用数といった、機械で数えられる文体項目を検査しました。

厄介だったのは文字化けの検出です。

過去にサーバー側の保存で、「独立」の「独」が字形の似た見慣れない漢字にすり替わる散発的な化けが起きたことがあります。

投稿元の正しい原稿が常に手元にあるとは限らないため、突き合わせ以外の検出方法が必要でした。

使ったのは、CJK文字を1文字ずつcp932(Shift-JIS)へエンコード試行する方法です。

日本語の記事で使う漢字はほぼcp932に収まるため、変換に失敗した文字だけを「化け疑い」として機械的に拾えます。

コードの骨子はこんな形です。

def detect_mojibake(text):
    hits = []
    for i, ch in enumerate(text):
        o = ord(ch)
        if 0x4E00 <= o <= 0x9FFF or 0x3400 <= o <= 0x4DBF:
            try:
                ch.encode("cp932")  # Shift-JIS外なら化け疑い
            except UnicodeEncodeError:
                hits.append((ch, text[max(0, i-8):i+8]))
    return hits
# 略: レポート整形などは省略

完全な検出ではないものの、参照原稿ゼロでも異常に気づける網としては十分でした。

装飾用ショートコードについても、色ごとに開きタグと閉じタグの数が一致するかを数え、表示崩れの芽を検出しています。

「直す」と「指摘する」をどこで分けるか

検出の次は修正ですが、私は見つけた問題をすべて自動で直すことを最初から諦めました。

基準にしたのは、正しい形が一意に定まるものだけを機械に直させるという線引きです。

例えば閉じタグが1つ欠けたショートコードの崩れは、正解が1通りしかないため安全に直せます。

一方で、語尾連続のリライトや化けた文字の正字推定は、正解が1つに定まりません。

そうした判断の要るものは自動では触らず、修正すべき点の一覧として人間へ渡すことにしました。

書き戻しにも小さなコツがあり、更新リクエストに本文だけを載せてstatusを送らなければ、下書きのまま安全に編集できます

概念を示すと以下のようなイメージです。

await fetch(`${location.origin}/wp-json/wp/v2/posts/${id}`, {
  method: "POST",
  headers: { Authorization: auth, "Content-Type": "application/json" },
  body: JSON.stringify({ content: fixed }), // statusを入れない=draft維持
  credentials: "omit"
});
// 省略: 書き戻し後の再取得と照合

さらに自動修正の後は必ず本文を読み戻して照合し、修正そのものが新たな崩れを生んでいないかまで確かめました。

WordPressが自動でリビジョンを残してくれるので、万一壊しても管理画面から戻せるのは心強い保険ですね。

最後に

出来上がった仕組みを振り返って、自動化を壊れにくくするのは高度な修正能力ではなく、責任範囲の分け方なのだと腑に落ちました。

品質保証は不可逆な操作の直前に置き、機械で一意に直せるものだけ機械が直し、判断の要るものは人間へ委ねる——この2層の分離は、WordPressに限らず通用する型だと感じています。

デプロイの前、送信の前、そして公開の前。

生成AIで何かを量産する場面が増えるほど、作る力そのものより「出す前に検分する仕組み」の設計が効いてくるはずです。

私もまずは、この検問所の検査項目を1つずつ足しながら育てていきます。

以上です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA