適用制限(Phase 1 rollout eligibility)の正本:
rollout-policy.md実装本体:scripts/ai-loop/arbiter.pyテスト:scripts/ai-loop/test_arbiter.py
導入先リポジトリで ai-loop-workflow を初めて回す際の手順(詳細は各正本を参照。 本節は要点のみ):
docs/ai/ai-loop/ho-paths.md(plugin 導入先は references/ho-paths.md)の
雛形ヘッダ(同梱版に前置される「本ファイルは…配布時の参考例」注記)を
参照しつつ、導入先固有のパス一覧として定義する。未確定のまま run を
開始してはならない(規範。arbiter.py は --ho-paths 明示指定 → CWD →
スクリプト位置基準の順で実行時解決し、未確定・パース結果 0 件時は
全件 human escalate する fail-closed を実装済み — #809)scope.allowed_paths を宣言する: loopspec.md
の既存必須フィールドで、当該 run の変更可能範囲を明示するHUMAN_ESCALATED への降格を前提に運用する
(auto-approve の到達は 2 回目以降の検証課題とする)rules/branches/{ref} から required check 集合を取得し、
checks[] との ⊇ 照合を行う。required 集合が空(ruleset 未設定 /
classic protection / required ルール無し)の場合、⊇ 照合は自明に成立して
無音で消えるため、理由コード required_checks_empty を積んで fail-closed に
倒す設計になっている(R1 B-3 是正)。したがって required status check が
1 件も定義されていない導入先では、健全な PR であっても全 run が
required_checks_empty で HUMAN_ESCALATED になる。
これは Collector 側の不具合ではなく導入先の前提条件の不足である。
auto-approve を到達可能にするには、run を開始する前に base ブランチの
ruleset へ required status check を定義しておくこと。前提条件 4 と「repo 設定に依存しない設計」の関係: 本 PBI の判定ロジック (優先度表・裁定・state machine)は repo 設定に依存しない。依存するのは auto-approve へ到達できるか否かという運用上の到達可能性だけであり、 前提条件を満たさない導入先でも安全側(escalate)に倒れるという意味では 挙動は決定論的に定義されている。fail-closed を緩めて空集合を「required 無し」 と解釈すると R1 の D-03 fail-open が復活するため、緩和ではなく 前提条件の明示で解決する。
docs/workflows/ai-loop/decision-table.md / flow-detect.md / lite-criteria.md /
docs/ai/ai-loop/ho-paths.md に定義された flow→detect→escalate 判断ロジックを、
scripts/ai-loop/arbiter.py(L2 裁定エンジン PoC)を用いて 1 サイクル実行する
手順を定義する。
制約(絶対): 本エンジンは PlanGate 本番フロー(WF-00〜WF-07・bin/plangate・
scripts/hooks/)から一切呼ばれない隔離された実験実装である(適用制限の正本 = rollout-policy.md)。W チェック(Model A/B/C/D
の verdict)の品質そのものは L1(本 runbook を実行する呼び出し側)の責務であり、
arbiter.py(L2)は入力された verdict を決定論ロジックで裁定するのみで、verdict
自体の正しさを検証しない。
前提: 本サイクルは C-1 PASS・C-2 完了済みであることを起点とする
(C-3’ ゲートとしての位置づけ。00_concept.md §3 参照)。
ai-loop-workflow の哲学は「escalate まで自走・人間はループ上で監督」
(00_concept.md §3.3「人間の関与」行と整合)。
1 サイクル内(LoopSpec 作成 → W チェック → 裁定 → exec → grader)では
逐次 y/n 確認を行わず自走する。人間の介入ポイントは以下の 2 点のみ:
HUMAN_ESCALATED に至った時(手順 (5) の arbiter 裁定のほか、
手順 (5b) grader 再試行上限超過・手順 (7) Scheduling 判断表の escalate を含む)ただし HO パス接触・想定外のスコープ拡大を検知した場合は既存 Iron Law
(docs/ai/core-contract.md)に従い即停止する。
run 番号は起票時に git fetch origin 後、origin/main の
docs/working/ai-loop-runs/ 一覧
(git ls-tree --name-only origin/main docs/working/ai-loop-runs/)と open PR の
使用中ブランチ・記録ファイル
(gh pr list --state open --json headRefName --jq '.[].headRefName')を
照合し、最大番号 +1 を仮採番する。さらに PR 作成直前に同じ照合を再実行し、
並行 run による先取(同一パス add/add 衝突)を検出した場合は改番してから
PR を作成する(F-34: 同日 2 回の採番衝突 — Run-013→014→015 の二重改番が実害)。
git diff --name-only <base>...<head>
対象コミット(target_sha)の変更ファイル一覧を取得する。
approve / reject の verdict を得るapprove / reject の
verdict を得るreject の場合、reject 理由から reject_category を決定論
マッピング(flow-detect.md §3.2.1)に照合可能な
カテゴリ文字列で記録する(ho_path_contact / permission / irreversible /
security_break / public_api / data_integrity / migration /
auth_change / logic / performance / test_shortage / documentation /
format / naming のいずれか。一致しない場合は分類器側で critical 扱いに
フォールバックされる)flow-detect.md §3.3)arbiter.py へ入力し裁定を得るModel A/B(必要なら C/D)の verdict と、boundary/lite/class 判定に必要な
情報を JSON にまとめ、arbiter.py へ渡す。
python3 scripts/ai-loop/arbiter.py --input /path/to/input.json
# または stdin 経由:
echo '{...}' | python3 scripts/ai-loop/arbiter.py
入力 JSON のフィールド仕様は arbiter.py
モジュール docstring および decision-table.md §2・§5 を
正本とする。
Plan-first production run(TASK-0872 / issue #872): ai-loop run TASK-XXXX
から開始した run では、入力 JSON に production: true と plan_package ブロック
(scripts/ai-loop/plan_package.py が presence / evidence / hash を検証して組み立てた
もの)を必ず含める。production: true で plan_package が欠落・構造不正なら
priority 1.6 で escalate、reviewer snapshot 不一致・source_sha ≠ target_sha は
priority 1.65 で blocked(契約正本: c3-prime-contract.md)。
再現検証(同一入力 → byte 同一 record)が必要な場合は --timestamp で刻印時刻を
固定注入できる。
arbiter.py の stdout(decision record JSON)を、以下の命名規則で保存する。
正本性の注記(
decision-table.md§5 PoC スコープと整合):AUTO_APPROVEDの record のみが provenance 刻印(正本)。HUMAN_ESCALATED/BLOCKEDの record は audit record(暫定)であり、 正式な audit trail の定義は Phase 3 以降で行う。
docs/working/ai-loop-runs/<UTC日時: YYYYMMDDTHHMMSSZ>-<sha7>.json
mkdir -p docs/working/ai-loop-runs
python3 scripts/ai-loop/arbiter.py --input /path/to/input.json \
> "docs/working/ai-loop-runs/$(date -u +%Y%m%dT%H%M%SZ)-$(git rev-parse --short HEAD).json"
保存した decision record は次回以降の監査・L4 学習(review-feedback-loop.md)
の入力となる。
摩擦 ID は台帳(run-001-frictions.md)が単一権威。新しい F-NNN は台帳への
追記と同時にのみ発行する(run 記録・PR 本文のみでの新 ID 発行は不可 —
多セッション並行時の二重採番防止。F-34 と同根・2026-07-08 の F-35〜39 台帳欠落が実例)。
採番前に台帳の最大 ID を確認する(§2-(0) の run 採番照合と同型)。
| exit code | decision | 動作 |
|---|---|---|
0 |
AUTO_APPROVED |
自動承認として扱う。provenance 刻印(正本)を保存して 1 サイクル完了 |
2 |
HUMAN_ESCALATED |
停止して人間へ escalate。audit record(暫定)の w_check / boundary_check / lite_check を提示し、人間の判断を仰ぐ |
3 |
BLOCKED |
ブロックとして扱う。当該変更を採用しない。audit record(暫定)を保存し、理由(stderr の裁定サマリ)を記録する |
1 |
(入力エラー) | 入力 JSON の不備。stderr の理由メッセージに従い入力を修正して再実行する |
AUTO_APPROVED(exit code 0)で exec 完了後・PR 作成前に、maker と独立の
sonnet サブエージェント(rubric grader)へ exec 差分を委託する
(手順詳細・rubric 5 項目・委託プロンプト定型は
ai-loop-cycle SKILL.md
Step 5.5 を正本とし、本節では再定義しない)。
verdict: pass|fail /
failed_criteria / feedback を 3 行 raw で返す(fail は引用必須)verdict: fail → feedback を添えて maker に再試行を委託する。上限 2 回HUMAN_ESCALATED として扱い、grader の全出力(引用込み)を
人間へ提示して停止するverdict: pass を確認してから (6) 強化セルフレビューへ進むAUTO_APPROVED(exit code 0)で exec / L-0 / V 系が完了した後、PR 作成前に
強化セルフレビューを実施する(MERGE_READY 責務の担保。
00_concept.md §3.4 参照):
git diff --name-only <base>...HEAD を突合し、宣言外の変更がゼロであることを確認する
(宣言外変更あり → exec 差し戻し or C-3’ 再裁定)plan-review-readiness-gate.md
§7/§8 観点を通すreview-feedback-loop.md §2 で過去に還元済みの
観点(過去の CI 失敗・AI レビュー指摘から抽出されたチェック項目)を通す全観点 PASS を確認してから PR を作成する。FAIL がある場合は exec へ差し戻す。
PR 作成後、以下を MERGE_READY 到達まで繰り返す。
この手順は adaptive-production-loop.md の
6 層モデルにおける Schedule の実行点である。ただし Schedule は「次に何をするか」を
決めるだけであり、品質評価そのものは CI / AI review / DoD などの Evaluate 層に残す。
| 優先度 | 条件 | 次アクション | 次状態 |
|---|---|---|---|
| 1 | boundary=touches-HO / policy 変更 / irreversible 変更 | 停止して human escalate | HUMAN_ESCALATED |
| 2 | 対応ラウンド上限 3 超過 | 停止して human escalate | HUMAN_ESCALATED |
| 3 | 同型指摘の再発 | review-feedback-loop.md へ還元し、Optimize 対象へ送る |
recurse |
| 4 | CI failed | CI failure を調査・修正し、(6) を再実行して push | continue |
| 5 | merge conflict | conflict 解消、三点照合、lease-protected push | continue |
| 6 | critical / major の AI review 指摘あり | 採用して修正、または理由付き不採用を記録 | continue or escalate |
| 7 | minor / info のみ | 採用/不採用理由を記録し、DoD 判定へ進む | MERGE_READY candidate |
| 8 | CI green かつ AI review 全件対応済み | C-4 待ちへ遷移 | MERGE_READY |
gh pr view <n> --json mergeable が CONFLICTING)。
スタック PR の前段 squash マージ起因の場合は、固有コミットのみを
git rebase --onto origin/main <旧base> <branch> で main に載せ替え、
三点照合(git branch -vv・SHA 同定)のうえ lease-protected push で反映する。
push 直後の mergeable は再計算中の場合があるため数十秒後に再確認するMERGE_READY 報告を
行い、マージは Human が実行する(responsibility-classes: merge は Human-owned)。review-feedback-loop.md §2 の L4 学習閉ループへ
還元し、次回の強化セルフレビュー(手順 (6))で事前に捕捉されるようにするarbiter-policy.md §7 escalate 予算
と接続)。新規指摘が minor / info のみになった時点で、記録を条件に
MERGE_READY 判定へ進んでよい(00_concept.md §3.3)MERGE_READY と判定し、C-4(人間の
merge 承認、Human-owned 固定)待ちに遷移する:2:/:3:)や ours/theirs のラベル理解に依存せず、
各側の内容の冒頭を実際に表示して同定してから採用する。解消後は「維持すべき側の
ファイルが、その本来の比較基準(main 側を維持したなら origin/main、
自ブランチ側を維持したならマージ/リベース前の自コミット)と差分ゼロであること」を
機械検証する(実例: Run-015 — ラベル依存の解決が inverted となり、
内容表示による検証で自己検出・是正。F-36)orchestrator-mode.md §検証可能性
の 4 条件に対する arbiter.py の適合状況:
| 条件 | 適合内容 |
|---|---|
| 冪等性 | 同一入力 JSON に対し decision は常に同一(ProvenanceSchemaTests.test_auto_approve_provenance_fields で検証。timestamp のみ実行毎に変化するが裁定結果には影響しない) |
| 明示的失敗 | 入力エラー(exit code 1)は必ず理由メッセージを stderr に出力する([arbiter] 入力エラー: <理由>) |
| トレーサビリティ | boundary 判定で HO パターンに一致した場合、一致パス・パターン・分類を理由サマリ(stderr)に含める |
| テスト可能性 | test_arbiter.py(59 ケース)で decision table 全 priority・severity 全分類・C/D 全パターン・AC-8 安全側・boundary 全パターン・ho-paths.md との drift を機械的に検証可能 |
bin/plangate・
scripts/hooks/)から一切呼ばれない隔離された実験実装である
(docs/ai/ai-loop/phase3-impact-report.md §b.1
トリガー 1 の判断記録を参照)arbiter.py(L2)は入力された verdict の内容が
正しいかどうかを検証しない(決定論ロジックの適用のみ)ho-paths.md 判定ルール)issued_by は自己申告であり、署名等の発行元検証機構は
本 PoC のスコープ外(phase3-impact-report.md §d
リスク 5、issue #420 EH-3 発行元検証と同型の未解決課題)手順 (7) を機械実行する Executor(scripts/ai-loop/executor.py)の外部作用は
scripts/ai-loop/gh_exec.py を唯一の境界とする(D2-A / AC-5)。
設計上の保証はこの層だけに依存する。強制の主語は 2 つに分かれる:
| 何を強制するか | 誰が強制するか | 時点 |
|---|---|---|
許可された gh / 読み取り系 git サブコマンドの allowlist 照合 |
gh_exec.authorize_gh() / authorize_git()(runtime) |
コマンド発行のたび |
scripts/ai-loop/ の他モジュールが gh_exec を迂回していないこと(実行系トークン 0 件)と、gh_exec.py 内部の構造規律(shell=True 不使用 / _spawn() 単一経路 / 監査済み入口のみ) |
scripts/ai-loop/check_exec_boundary.py(AST 静的検査) |
CI / tests/extras/ta-57-pr-convergence.sh |
つまり check_exec_boundary.py は allowlist そのものを強制するのではなく、
allowlist を通らない実行経路が生えていないことを静的に保証する。
既存の scripts/hooks/check-delegation-commit-boundary.sh と GitHub の branch
protection は 多層防御の補助として併用してよいが、設計はこれらに依存しない。
補助に留める根拠(TASK-0917 plan R-001 / R-031 の 2026-07-31 実測):
PLANGATE_DELEGATION_NOCOMMIT != 1 のとき即 allow する(既定で無効).claude/settings.json は PreToolUse 未配線(bin/plangate doctor の
=== Hook Enforcement Wiring === が [FAIL] PlanGate hooks not wired)。
配布テンプレ .claude/settings.example.json のトップレベルキーは
["_comment_", "_usage_", "hooks"] で permissions キー自体が存在しない
(= deny 設定 0 件).git/hooks/ に非 sample hook は 0 件(scripts/install-pre-push.sh 未適用)required_approving_review_count: 0 のため承認を
強制していない(issue #928)Executor 実行ホストの前提条件: Executor を回すホストでは
sh scripts/install-pre-push.sh を適用し、main 直接 push を技術層で block した
状態にしておくこと(responsibility-classes.md
Defense in Depth / TASK-0114)。ただしこれも上記の意味で補助であり、AC-5 の
保証を肩代わりしない — 未適用のホストでも Executor 側の allowlist は同じ強度で働く。
check_exec_boundary.py の残存脅威モデル(何を守り、何を守らないか)完全性は主張しない。 TASK-0917 では AST 静的検査に対して敵対レビューを 3 ラウンド回し、毎回 1 つ深い回避クラスが新たに見つかった。この事実 自体が「静的検査で任意の回避を塞ぎ切れる」という主張が成り立たないことの 証拠である。次に触る人が「もう完全に塞がっている」と誤読しないために、 塞いだクラスと残る限界を明示する。
| ラウンド | 新たに出た回避クラス | 是正 |
|---|---|---|
| R1 | 直接記述(subprocess / os.system / pty / ctypes / multiprocessing / asyncio.create_subprocess_*)、getattr(os, "system")、eval・exec・compile、gh_exec.py 内部の shell=True / _spawn() 外の呼び出しサイト |
実行系トークンの列挙 + 動的属性 / 動的コード生成の deny + gh_exec.py 内部の構造規律 |
| R2 | ローカル別名(mod = os の 1 行で R1 の是正がすべて無効化された)、イントロスペクション属性(os.__dict__["system"]) |
束縛の代入伝播(不動点)+ fail-closed 既定への反転 |
| R3 | ast.Subscript 経路(vars(os)["system"] / sys.modules["subprocess"].run / globals()["subprocess"])。os.__dict__["system"] は検出できるのに vars(os)["system"] はすり抜ける、.Popen は検出できるのに .run はすり抜ける、という非対称性が症状として現れていた |
添字式の束縛解決 + 添字経路の fail-closed 層 |
守るもの:
subprocess / os.system /
pty / ctypes 等を書くこと守らないもの:
gh pr merge)scripts/ai-loop/*.py 以外のファイル。検査対象ディレクトリ外から実行
能力を渡す経路は視野外def f(m): m.run(x))。
偽陽性を避けるため run / call は間接実行名に載せていないしたがって check_exec_boundary.py は「多層防御の 1 層」であり、
単独で「NO MERGE BY AI」を保証するものではない。保証の主体は
gh_exec allowlist(authorize_gh() の deny 既定)であり、本検査器は「それらが気付かないうちに掘り崩されていないこと」を CI で 機械的に確かめる補助線に過ぎない。新しい回避クラスを見つけたら、塞いだうえで 必ず上表に 1 行追加すること(塞いだ範囲の記録を残さないと、次の読み手が 再び「完全に塞がっている」と誤読する)。
判定エンジン 3 ファイル(delivery.py / c3_contract.py / c3prime_verify.py)を
不変に保つ AC-7 は、tests/extras/ta-57-pr-convergence.sh が 3 点で検証する:
| # | 検査 | 実行条件 |
|---|---|---|
| TC-14 | git diff --stat <base> -- <3 ファイル> が 0 行 |
base ref(origin/main / main)が存在する checkout のみ |
| TC-15 | test_delivery.py が Ran 57 tests / OK |
常時 |
| TC-16 | delivery.py contract の emit と delivery-state-machine.md の contract ブロックが byte 一致 |
常時 |
PR 時の CI では TC-14 は実行されない(2026-07-31 実測)。.github/workflows/test.yml
の actions/checkout は fetch-depth を指定しておらず既定の 1(shallow・単一 ref)で
clone するため、pull_request イベントでは origin/main も main も解決できない。
ta-57 はこの場合 [WARN] を出して先へ進む(fail を増やして CI を落とすのではなく、
「3 点中 2 点しか機械検証されていない環境である」ことを可視化する)。
base ref が HEAD と同一 commit に解決する場合も同様に実行しない。`git diff –stat HEAD –