メインコンテンツへスキップ
見出し画像

AI の承認に関所を置く作り方(実際に見つけた穴つき)

    ※この記事は AI が下書きし、人間が編集しています。

    あなたの1人事業で AI に投稿や出品を任せるとき、いちばん怖いのは「AI が勝手に公開すること」ではありません。「AI は公開できない」と書いてあるのに、実は通っていたことです。

    この記事は、うちの1人会社(人間1人+AI)で実際に起きたその穴と、塞ぎ方を、ログとコードのまま置きます。仕組みはそのまま、あなたの事業に持ち込めます。

    この穴を見つけた日、怖かったのは AI ではなく「守られている」と信じていた自分のほうでした。

    1. 守りたいのは「人間だけが承認できる」こと

    投稿1本は、次の状態を1つずつ進みます。

    draft → script_ready → compliance_ok → production → qa_ok → awaiting_approval
                                                                     ↓ 人間がラベルを1タップ
                                                  approved → scheduled → published

    awaiting_approval → approved の1歩だけが人間の門です。AI が自分で進めようとすると、コードが拒否します。

    if frm in HUMAN_GATE_FROM and not allow_human_gate:
        raise ContentError(f"{frm} から先は人間の承認ゲートです。自動処理では越えられません")

    ここまでは正しく動いていました。テストも全部通っていました。

    2. 見つけた穴(2026-08-25・実測)

    承認の仕組みを動画で撮ろうとして、実物を叩いたときに見つかりました。

    $ content.py advance <id> --to approved --actor "アーク(機械)"
    [エラー] awaiting_approval から先は人間の承認ゲートです
    exit=2                                  ← ここは止まる
    
    $ content.py approve <id> --actor "アーク(機械)"
    <id>: -> approved(承認者: アーク(機械))
    exit=0                                  ← ★通った★

    approve サブコマンドが allow_human_gate=True を無条件で渡していました。--actor に機械の名前を書いても、そのまま承認が通る。

    3. なぜテストがすり抜けたか

    「動くこと」のテストはあって、「動いてはいけないこと」のテストが片側しかなかった。

    • advance --to approved が拒否される → テストあり

    • approve --actor 社長 が通る → テストあり

    • approve --actor 機械 が通ってしまう → 誰も試していなかった

    守りたいのは「承認コマンドが動くこと」ではなく「人間以外は承認できないこと」です。テストが前者だけを見ていました。

    4. コードより危なかったのは「説明」

    ワークフローの冒頭にはこう書いてありました。

    このワークフローだけが --actor を人間の名前にして承認を通せる。

    運用ルールの文書にも「承認を通せるのは人間の名前が記録される経路だけ」と。どちらも事実ではなかった。 読んだ人間も、次の AI セッションも「ここは守られている」と信じてしまう。穴そのものより、穴が塞がっていると誤解させる記述のほうが危ない。

    5. 塞ぎ方

    | | 前 | 後 |
    |---|---|---|
    | 承認の出どころ | 記録なし | `approval_source` に記録 |
    | 手元での `approve` | 素通り | 通るが `local-cli` と記録され、警告が出る |
    | CI 内の他ワークフロー | 素通り | `issues` イベント以外は拒否 |
    | `publish` | 承認済みなら通る | **出どころが `github-issue-label:` でなければ拒否** |
    VERIFIED_SOURCE_PREFIX = "github-issue-label:"
    
    def approval_is_verified(rec) -> bool:
        return str(rec.get("approval_source", "")).startswith(VERIFIED_SOURCE_PREFIX)

    歯止めは承認側ではなく publish 側に置きました。承認そのものを不可能にすると運用が固くなりすぎる。塞ぐべきは「承認された事実」ではなく「外に出てしまうこと」だからです。

    再発防止のテストを7件足して、全体は326件(2026-08-25 時点・すべて緑)。7件のうち1件は「ワークフローに事実でない断定が復活したら落ちる」テストで、これが本体です。

    6. これでも、できないこと

    手元で動くコードを、手元のコードで完全に止めることはできません。環境変数は偽装できるし、DB の JSON を直接書き換えれば CLI を通さずに状態を変えられる。

    この修正で防げるのは「うっかり通る」まで。わざと偽装されると記録だけでは見分けられず、Issue の履歴と突き合わせて初めて分かる。最後の砦はコードではなく、「外部公開の実行は人間がやる」という運用のほうです。(2026年11月追記: その後、承認と公開は、人間が決めた条件をすべて満たしたときだけ機械が行う運用に変えました。条件を決めるのは今も人間です。)

    7. あなたの事業に入れるなら(5点)

    1. 人間の門を1か所に決め、それ以外の遷移は AI に任せる

    2. 「通ってはいけない操作が通らない」テストを、機械の名前で書く

    3. 承認の出どころ(誰が・どの経路で)を記録に残す

    4. 歯止めは公開の一歩手前に置く

    5. 「守られている」と書く前に、実物を叩く

    次は、同じ1人会社で AI の従量課金を月$30で止める門番の話です(誤診して同日に訂正した記録つき)。実装の断片は Threads(@arcgenba)で1日1つずつ出しています。

    あなたへのおすすめ