0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI生成記事を自動公開しないためのQC承認ファイルゲート:Pythonで作る公開前チェック

0
Last updated at Posted at 2026-06-23

AI生成記事を自動公開しない仕組み - 承認JSONでQiita/Zenn公開を止める

はじめに

AIで記事を書くパイプラインを作ると、つい生成から公開まで自動化したくなります。しかし、生成に成功したことと、公開してよいことは別です。

特にQiitaやZennのような技術媒体では、薄い記事や誤ったコードを量産すると、読者の信頼を落とします。Agent Memoriesでは、技術記事の公開前にQC担当の承認ファイルを必須にしています。

この記事では、Pythonで作るシンプルなQC承認ファイルゲートを紹介します。

QC承認ファイルで公開を止める - approved.jsonを検証してからQiita/Zennへ公開する

承認をチャットの発言だけにしない

最初にやりがちな運用は、DiscordやSlackで「QC通りました」と言ってもらい、それを見た担当者やcronが公開する方式です。

この方式は、人間が見ているうちは動きます。しかし、自動化が進むと次の問題が出ます。

  • 誰が承認したのか機械的に検証できない
  • どのslugの承認なのか曖昧になる
  • Qiitaは承認済みだがZennは未承認、のような差分を扱いにくい
  • cronがチャットの文脈を読めない

そこで、承認をファイルとして残します。

{
  "slug": "x-action-mcp-safety-gates",
  "decision": "approved",
  "approved_by": "Alice",
  "channels": ["qiita", "zenn"],
  "approved_at": "2026-06-23T10:30:00+09:00"
}

公開スクリプトは、このファイルがなければ公開しません。

承認ファイルの置き場所

承認ファイルは、公開する側とは別の担当領域に置きます。たとえば次のような構成です。

content/tech/
  x-action-mcp-safety-gates.md

state/
  tech-published.json

workspace-alice/docs/agentmemories-publish-approvals/
  x-action-mcp-safety-gates.approved.json

記事を書く担当と、承認を書く担当を分けることで、自分で書いて自分で公開する抜け道を減らせます。

Pythonで承認を検証する

公開スクリプト側では、slugとchannelを指定して承認を確認します。

import json
from pathlib import Path

QC_APPROVAL_DIR = Path("/home/node/.openclaw/workspace-alice/docs/agentmemories-publish-approvals")


def _normalize_channels(value: object) -> set[str]:
    if isinstance(value, str):
        return {value.lower()}
    if isinstance(value, list):
        return {str(v).lower() for v in value}
    return set()


def approval_allows(data: dict[str, object], slug: str, channel: str) -> bool:
    decision = str(data.get("decision") or data.get("status") or "").lower()
    qc_pass = data.get("qc_pass") is True or decision in {"pass", "approved", "qc_pass"}
    if not qc_pass:
        return False

    approved_by = str(data.get("approved_by") or data.get("reviewer") or "").lower()
    if "alice" not in approved_by and "アリス" not in approved_by:
        return False

    if data.get("slug") and str(data["slug"]) != slug:
        return False

    channels = _normalize_channels(data.get("channels") or data.get("channel"))
    return channel in channels or "all" in channels

この関数では、次の4点を見ています。

  • 承認ステータスが通過になっているか
  • 承認者がQC担当か
  • slugが一致しているか
  • channelが一致しているか

チャネルごとに未承認を出す

QiitaとZennへ同時に出す場合でも、承認はチャネル単位で見ます。

QC_CHANNELS = ("qiita", "zenn")


def has_qc_approval(slug: str, channel: str) -> bool:
    candidates = [
        QC_APPROVAL_DIR / f"{slug}.{channel}.approved.json",
        QC_APPROVAL_DIR / f"{slug}.approved.json",
    ]

    for file in candidates:
        if not file.exists():
            continue
        try:
            data = json.loads(file.read_text(encoding="utf-8"))
        except json.JSONDecodeError:
            continue

        if isinstance(data, dict) and approval_allows(data, slug, channel):
            return True

    return False


def missing_qc_channels(slug: str) -> list[str]:
    return [channel for channel in QC_CHANNELS if not has_qc_approval(slug, channel)]

この形にしておくと、dry-runで次のように返せます。

{
  "dry_run": true,
  "slug": "x-action-mcp-safety-gates",
  "alice_qc_missing": ["qiita", "zenn"]
}

在庫があるのに承認待ちなのか、本当に在庫が空なのかを分けて見られます。

公開処理の直前で止める

実際の公開処理では、書き込みやpushの直前にゲートを置きます。

def publish(item: dict[str, object], dry_run: bool = False) -> None:
    slug = str(item["slug"])

    if dry_run:
        print({
            "dry_run": True,
            "slug": slug,
            "alice_qc_missing": missing_qc_channels(slug),
        })
        return

    missing = missing_qc_channels(slug)
    if missing:
        print({
            "ok": True,
            "skipped": True,
            "reason": "alice_qc_required",
            "slug": slug,
            "channels": missing,
        })
        return

    publish_qiita(item)
    publish_zenn(item)

生成や在庫判定の時点ではなく、公開直前にも確認するのがポイントです。原稿を作った後に承認ファイルが消えたり、slugが差し替わったりしても、最後の出口で止まります。

承認ファイルに入れないもの

承認ファイルは公開可否の証跡であり、secret置き場ではありません。次のようなものは入れません。

  • API token
  • Cookie
  • Auth header
  • private repositoryの秘密情報
  • 投稿先アカウントのログイン情報

必要なのは、誰が、何を、どの媒体に、いつ承認したかだけです。

まとめ

AI生成記事の自動化では、生成成功と公開許可を分ける必要があります。

最小のQC承認ファイルゲートは、次の情報を検証します。

  1. slug
  2. channel
  3. decision
  4. approved_by

これを公開スクリプトの直前に置くだけで、未レビュー記事がQiita/Zennへ流れる事故を減らせます。Agent Memoriesでは、このような小さな機械ゲートと人のQCを組み合わせて、AIが記事を作っても公開判断までは自動で飛ばさない運用にしています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?