feat(nightly-todo): 夜間 todo 消化ループを実装し無人実装から draft PR までを決定論経路で閉じる (ADR-072) - #363
Conversation
WP-18 PR 3 (1/5)。夜間ループの「何を実装するか」を決める層。ユーザー確認済みの設計 (2026-08-06): 選択ロジックは workflow の shell ではなく Rust exe に置く。 ## なぜ LLM でも shell でもないのか ADR-052 は「分類ロジックを Rust 分類関数を用意せず自律 actor の実行時 LLM 判断に委ねる」 ことをアンチパターンとして挙げている。夜間ループで何を実装するかは自律動作の起点であり、 ここが揺れると下流のゲートがいくら堅くても「意図しないタスクを正しく実装した draft PR」が 出てくる。 shell (awk/grep) 案を採らなかったのは、markdown table の境界 (列ずれ・全角・エスケープ されたパイプ・無関係な表の混在) に回帰テストを書く場がないため。本 crate は 25 件の unit test でその境界を固定している。 ## 毎晩同じタスクを実装し直す問題 台帳の行はタスクが**マージされるまで**残る。素朴に「無人可の先頭行」を選ぶと毎晩同じ タスクを実装する。ブランチ名に順位を埋め (claude/nightly-<順位>)、open な同名ブランチの 順位を --exclude-ranks で除外する形で決定論のまま解いた。 --exclude-ranks は空でも省略できない。空文字は「数えた結果 0 件」、フラグ欠落は 「数えられなかった」で意味が違う。省略可能にすると gh api が失敗した run が「開いている draft は無い」と解釈して同じタスクを二重実装する (PR 1 の --open-draft-prs と同じ設計)。 ## 曖昧さはすべて停止側へ 台帳は人間が手で編集する markdown なので、列ずれ・順位の重複・未知のマーク表記が起こる。 これらは読み飛ばさず **エラー (exit 2)** にする。読み飛ばした行が本来の選択対象だった場合、 ループは黙って別のタスクを実装するため。 「対象ファイル」列の解決も同じ方針を採る。実表記が「対象ファイル」と「対象ファイル (実パス)」 の 2 種あるため前方一致で探すが、**複数ヒットしたら先頭を黙って採らずエラーにする**。将来 「対象ファイル案」のような列が先に追加されると、誤った列を agent への指示に使ってしまう (pre-push simplicity review の指摘 SIM-NEW-ledger-rs-L865 を反映)。 「✅ (条件付き)」のような書き足しもエラーにする。無人可でない側へ倒すと人間の意図と判定が ずれたまま静かに進む。 exit コードは 0 = 選択 / 2 = 入力不正・台帳破損 / 3 = 該当なし (正常な no-op)。3 と 2 を 分けるのは run log で「何もすることが無かった」と「台帳が壊れている」を切り分けるためで、 後続を動かさない点は同じ。 ## 実データ検証 PR 2 (#362) マージ後の master 台帳で実走した: - 除外なし → rank=203 branch=claude/nightly-203 (Batch 1 の先頭 ✅ 行) - 203 除外 → rank=240 - 無人可 7 件すべて除外 → exit 3 (no-op) - 棚卸し履歴 / 無人可としなかった理由 の 2 表 (順位 列を持つが 無人可 列を持たない) は 正しく無視された - **PR 2 未マージの旧台帳 → exit 2** で loud に停止。台帳が旧構成のままなら夜間ループは 黙って no-op せず、理由を出して止まる 依存 crate ゼロ。本 exe は夜間ループで唯一「何を実装するか」を決める存在なので、供給元を 増やさないこと自体を設計制約とした。 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
WP-18 PR 3 (2/5)。他の cli-* と同じ形 (cargo build --release + deploy-artifacts) を踏襲する。 CI 経路 (夜間 workflow) は master ref から cargo build するため deploy 先の .claude/*.exe を 使わないが、cli-fix-push-gate も同じく CI 専用でありながら build script + deploy を持つ。 形を揃えることで、ローカル drill が他の exe と同じ `node scripts/run-artifact.mjs` 経由で 実行できる。 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthrough夜間 GitHub Actions workflow と Rust 製台帳選択 CLI を追加しました。workflow は対象タスクを選択し、Claude Code agent、テスト、clippy、複数の改ざん・ガードレール検査を実行します。全ゲート通過時だけ draft PR を作成します。 Changes夜間 todo 消化ループ
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Actions as GitHub Actions
participant API as GitHub API
participant Selector as cli-nightly-task-select
participant Agent as Claude Code agent
participant Gate as cli-autonomy-gate
participant GitHub as GitHub
Actions->>API: open PR と着手済み順位を取得
Actions->>Gate: pre-flight 判定を依頼
Actions->>Selector: 台帳と除外順位を渡す
Selector-->>Actions: 選択タスクまたは停止結果
Actions->>Agent: 制約付き実装を依頼
Actions->>Actions: test、clippy、変更検査を実行
Actions->>Gate: authority 判定を依頼
Gate-->>Actions: gate 結果
Actions->>GitHub: commit、push、draft PR 作成
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🤖 PR Monitor 分析 (GitHub Actions バックストップ)
Applicable Findings (Critical / High / Major)該当なし Applicable Findings (Medium 以下)該当なし Filtered (not applicable)該当なし 軽量サマリー (レビュー指摘 0 件のため)
次のアクション
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
src/cli-nightly-task-select/src/main.rs (1)
67-86: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value同一フラグの重複指定を停止側へ倒すことを検討してください。
現在は
--ledger a.md --ledger b.mdのように同じフラグを 2 回渡すと、後の値が黙って前の値を上書きします。本 exe の設計方針は「曖昧な入力はすべて停止側へ」です。台帳パーサ側は順位の重複をエラーにしています。引数側も同じ扱いに揃えると、workflow の組み立てミスが黙って通る経路がなくなります。♻️ 重複フラグを引数不正にする案
match flag { - "--ledger" => ledger_path = Some(PathBuf::from(take()?)), - "--exclude-ranks" => excluded_ranks = Some(parse_ranks(take()?)?), + "--ledger" if ledger_path.is_some() => { + return Err("--ledger が 2 回指定されています".to_string()) + } + "--exclude-ranks" if excluded_ranks.is_some() => { + return Err("--exclude-ranks が 2 回指定されています".to_string()) + } + "--ledger" => ledger_path = Some(PathBuf::from(take()?)), + "--exclude-ranks" => excluded_ranks = Some(parse_ranks(take()?)?), other => return Err(format!("未知の引数です: {other:?}")), }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/cli-nightly-task-select/src/main.rs` around lines 67 - 86, Update parse_args to reject duplicate --ledger and --exclude-ranks flags instead of overwriting earlier values. Before assigning each option, detect whether its corresponding Option is already set and return an argument error; preserve the existing missing-value and unknown-argument handling..github/workflows/nightly-todo.yml (3)
57-66: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueRust のビルドキャッシュ導入を検討してください。
本 job は
master-ref/で 2 つの crate を release ビルドします。さらにVerify deterministicallystep がwork/で workspace 全体を test + clippy ビルドします。両者は別 target ディレクトリのため、毎晩フルビルドが 2 回走ります。Swatinem/rust-cacheなどの導入で run 時間を短縮できます。ただしキャッシュはゲート exe の調達経路でもあります。キャッシュ汚染はゲートの改ざんと等価になります。
master-ref/側のビルドはキャッシュせず、work/側の verify ビルドだけをキャッシュ対象にする分離を推奨します。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/nightly-todo.yml around lines 57 - 66, Add Rust caching only to the work/ build used by the Verify deterministically step, using a cache configuration scoped to that target directory and its Cargo files. Do not cache the master-ref/ release build in Build deterministic gates from master, preserving its independently rebuilt gate executables and integrity checks.
100-109: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winstep の値は
env:経由で渡してください。zizmor が 3 箇所で template-injection を報告しています (109 / 123 / 267 行目)。現在の値は同一 workflow 内の
jqが生成した数値と数値 CSV なので、実際に注入が成立する経路はありません。ただし${{ }}をrun:本文へ直接展開すると、上流の生成ロジックを変更したときに注入が復活します。他の step (publish/Report outcome) は既にenv:経由です。同じ形へ揃えてください。♻️ `env:` 経由へ揃える案 (preflight の例)
env: AUTONOMY_ENABLED: ${{ vars.AUTONOMY_ENABLED }} + OPEN_DRAFTS: ${{ steps.inflight.outputs.open_drafts }} run: | master-ref/target/release/cli-autonomy-gate \ --operation draft-pr \ --config master-ref/autonomy-config.toml \ - --open-draft-prs "${{ steps.inflight.outputs.open_drafts }}" + --open-draft-prs "$OPEN_DRAFTS"Also applies to: 115-126, 257-267
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/nightly-todo.yml around lines 100 - 109, Update the preflight, publish, and Report outcome steps to pass all interpolated step values through each step’s env block, then reference those environment variables in the run scripts instead of expanding steps.* directly in run:. Preserve the existing CLI arguments and reporting behavior while applying this consistently at the referenced step sections.Source: Linters/SAST tools
269-274: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
inputscontext を使って、boolean 入力として比較してください。
workflow_dispatchではinputscontext が typebooleanを保持します。dry_runは boolean input なので、inputs.dry_run !== trueでworkflow_dispatchの入力入力を扱えます。github.event.inputsは文字列扱いになるため、!= 'true'比較を使う際は型入力の宣言と一致しない表現になります。♻️ 修正案
if: >- steps.gate.outcome == 'success' && - github.event.inputs.dry_run != 'true' + inputs.dry_run != true🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/nightly-todo.yml around lines 269 - 274, Update the publish step’s if condition to use the typed inputs context, replacing github.event.inputs.dry_run string comparison with a boolean comparison against inputs.dry_run. Preserve the existing gate outcome and non-dry-run requirements.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/adr/adr-072-nightly-todo-loop.md`:
- Around line 121-123: Update docs/adr/adr-072-nightly-todo-loop.md lines
121-123 to state 25 unit tests with the breakdown “台帳パーサ 17 件 / 引数解析 8 件”. Also
update docs/harness-improvement-plan.md line 191 to replace “unit test 23 件 +
実データ選択” with “unit test 25 件 + 実データ選択”.
- Around line 150-158:
見出しと導入文のレビュー指摘件数を、表の実際の3件に一致させて更新してください。対象は「静的レビューが著者の見落としを2件捕捉した」とその直後の「blockingな欠陥が2件」という記述で、表の内容や各指摘は変更しないでください。
---
Nitpick comments:
In @.github/workflows/nightly-todo.yml:
- Around line 57-66: Add Rust caching only to the work/ build used by the Verify
deterministically step, using a cache configuration scoped to that target
directory and its Cargo files. Do not cache the master-ref/ release build in
Build deterministic gates from master, preserving its independently rebuilt gate
executables and integrity checks.
- Around line 100-109: Update the preflight, publish, and Report outcome steps
to pass all interpolated step values through each step’s env block, then
reference those environment variables in the run scripts instead of expanding
steps.* directly in run:. Preserve the existing CLI arguments and reporting
behavior while applying this consistently at the referenced step sections.
- Around line 269-274: Update the publish step’s if condition to use the typed
inputs context, replacing github.event.inputs.dry_run string comparison with a
boolean comparison against inputs.dry_run. Preserve the existing gate outcome
and non-dry-run requirements.
In `@src/cli-nightly-task-select/src/main.rs`:
- Around line 67-86: Update parse_args to reject duplicate --ledger and
--exclude-ranks flags instead of overwriting earlier values. Before assigning
each option, detect whether its corresponding Option is already set and return
an argument error; preserve the existing missing-value and unknown-argument
handling.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c45df1a2-bac7-497e-8642-627b8ac276df
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
.github/workflows/nightly-todo.ymlCLAUDE.mdCargo.tomldocs/adr/adr-072-nightly-todo-loop.mddocs/harness-improvement-plan.mdpackage.jsonsrc/cli-nightly-task-select/Cargo.tomlsrc/cli-nightly-task-select/src/ledger.rssrc/cli-nightly-task-select/src/main.rs
| ### `cli-nightly-task-select` の unit test (23 件) | ||
|
|
||
| 台帳パーサ 17 件 / 引数解析 6 件。境界として固定したもの: |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
unit test 件数の記載が実装と一致しません。 実装のテスト関数は src/cli-nightly-task-select/src/ledger.rs に 17 件、src/cli-nightly-task-select/src/main.rs に 8 件で、合計 25 件です。2 つの文書はいずれも 23 件と記載しています。PR 説明は 25 件と記載しており、文書側だけが古い状態です。
docs/adr/adr-072-nightly-todo-loop.md#L121-L123: 見出しを「unit test (25 件)」に、内訳を「台帳パーサ 17 件 / 引数解析 8 件」に更新してください。docs/harness-improvement-plan.md#L191-L191: 受け入れ基準表の「unit test 23 件 + 実データ選択」を「unit test 25 件 + 実データ選択」に更新してください。
📍 Affects 2 files
docs/adr/adr-072-nightly-todo-loop.md#L121-L123(this comment)docs/harness-improvement-plan.md#L191-L191
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/adr/adr-072-nightly-todo-loop.md` around lines 121 - 123, Update
docs/adr/adr-072-nightly-todo-loop.md lines 121-123 to state 25 unit tests with
the breakdown “台帳パーサ 17 件 / 引数解析 8 件”. Also update
docs/harness-improvement-plan.md line 191 to replace “unit test 23 件 + 実データ選択”
with “unit test 25 件 + 実データ選択”.
| ### 静的レビューが著者の見落としを 2 件捕捉した (2026-08-06) | ||
|
|
||
| pre-push review が simplicity / security ともに **REJECT** を返し、初版に blocking な欠陥が 2 件あったことが分かった。どちらも著者 (Claude) が設計時に気づけなかったもので、記録しておく価値がある。 | ||
|
|
||
| | # | 指摘 | 何を見落としていたか | | ||
| |---|---|---| | ||
| | 1 (simplicity) | `Pre-flight gate` だけが `id` / `continue-on-error` を持たず、背圧 deny が job 全体の failure になる | 4 つある停止点のうち 3 つを graceful に設計しておきながら、**最も高頻度で踏まれる最初の 1 つ**だけ落としていた。背圧が効くたびに毎晩赤い × が出て、`Report outcome` も停止段を特定できない | | ||
| | 2 (security) | agent の unscoped な file tools が `master-ref/` に届き、ゲート exe / config を改ざんできる | 決定 6 の禁止リストを「ガードレール保護」として設計した時点で、**保護対象を `work/` の diff だけに限定していた**。調達元そのものが書き換え可能である経路を見ていなかった (→ 決定 7 を新設) | | ||
| | 3 (security) | `Bash(cargo test:*)` は前方一致でシェルを解釈しないため `cargo test; curl ...` が通り、diff に痕跡を残さず任意コマンド実行できる | 許可リストの**書式**を信頼して、それが何を許すかを検めていなかった。加えて「入力の出所が違う」という Phase B との差別化は *何を吹き込まれうるか* の議論で、ここで効く *何を実行できるか* に答えていなかった (→ 決定 5 を全面改訂) | |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
見出しと表の件数が一致しません。
見出しと 152 行目の本文は「2 件」と書いています。表には # 1 / # 2 / # 3 の 3 件があります。決定 5 の全面改訂 (指摘 3) を追記した際に、件数を更新していないと考えられます。
📝 修正案
-### 静的レビューが著者の見落としを 2 件捕捉した (2026-08-06)
+### 静的レビューが著者の見落としを 3 件捕捉した (2026-08-06)
-pre-push review が simplicity / security ともに **REJECT** を返し、初版に blocking な欠陥が 2 件あったことが分かった。
+pre-push review が simplicity / security ともに **REJECT** を返し、初版に blocking な欠陥が 3 件あったことが分かった。📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### 静的レビューが著者の見落としを 2 件捕捉した (2026-08-06) | |
| pre-push review が simplicity / security ともに **REJECT** を返し、初版に blocking な欠陥が 2 件あったことが分かった。どちらも著者 (Claude) が設計時に気づけなかったもので、記録しておく価値がある。 | |
| | # | 指摘 | 何を見落としていたか | | |
| |---|---|---| | |
| | 1 (simplicity) | `Pre-flight gate` だけが `id` / `continue-on-error` を持たず、背圧 deny が job 全体の failure になる | 4 つある停止点のうち 3 つを graceful に設計しておきながら、**最も高頻度で踏まれる最初の 1 つ**だけ落としていた。背圧が効くたびに毎晩赤い × が出て、`Report outcome` も停止段を特定できない | | |
| | 2 (security) | agent の unscoped な file tools が `master-ref/` に届き、ゲート exe / config を改ざんできる | 決定 6 の禁止リストを「ガードレール保護」として設計した時点で、**保護対象を `work/` の diff だけに限定していた**。調達元そのものが書き換え可能である経路を見ていなかった (→ 決定 7 を新設) | | |
| | 3 (security) | `Bash(cargo test:*)` は前方一致でシェルを解釈しないため `cargo test; curl ...` が通り、diff に痕跡を残さず任意コマンド実行できる | 許可リストの**書式**を信頼して、それが何を許すかを検めていなかった。加えて「入力の出所が違う」という Phase B との差別化は *何を吹き込まれうるか* の議論で、ここで効く *何を実行できるか* に答えていなかった (→ 決定 5 を全面改訂) | | |
| ### 静的レビューが著者の見落としを 3 件捕捉した (2026-08-06) | |
| pre-push review が simplicity / security ともに **REJECT** を返し、初版に blocking な欠陥が 3 件あったことが分かった。どちらも著者 (Claude) が設計時に気づけなかったもので、記録しておく価値がある。 | |
| | # | 指摘 | 何を見落としていたか | | |
| |---|---|---| | |
| | 1 (simplicity) | `Pre-flight gate` だけが `id` / `continue-on-error` を持たず、背圧 deny が job 全体の failure になる | 4 つある停止点のうち 3 つを graceful に設計しておきながら、**最も高頻度で踏まれる最初の 1 つ**だけ落としていた。背圧が効くたびに毎晩赤い × が出て、`Report outcome` も停止段を特定できない | | |
| | 2 (security) | agent の unscoped な file tools が `master-ref/` に届き、ゲート exe / config を改ざんできる | 決定 6 の禁止リストを「ガードレール保護」として設計した時点で、**保護対象を `work/` の diff だけに限定していた**。調達元そのものが書き換え可能である経路を見ていなかった (→ 決定 7 を新設) | | |
| | 3 (security) | `Bash(cargo test:*)` は前方一致でシェルを解釈しないため `cargo test; curl ...` が通り、diff に痕跡を残さず任意コマンド実行できる | 許可リストの**書式**を信頼して、それが何を許すかを検めていなかった。加えて「入力の出所が違う」という Phase B との差別化は *何を吹き込まれうるか* の議論で、ここで効く *何を実行できるか* に答えていなかった (→ 決定 5 を全面改訂) | |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/adr/adr-072-nightly-todo-loop.md` around lines 150 - 158,
見出しと導入文のレビュー指摘件数を、表の実際の3件に一致させて更新してください。対象は「静的レビューが著者の見落としを2件捕捉した」とその直後の「blockingな欠陥が2件」という記述で、表の内容や各指摘は変更しないでください。
🤖 PR Monitor 分析 (GitHub Actions バックストップ)
Applicable Findings (Critical / High / Major)該当なし Applicable Findings (Medium 以下)
Filtered (not applicable)
次のアクション
|
6230497 to
3515a87
Compare
WP-18 PR 3 (3/5)。台帳の無人可タスクを 1 件無人実装し **draft PR 作成で停止**する 15 step の workflow。マージ判断は人間 (ADR-052 の commitment 点の手前で止まる操作)。 schedule は毎日 1 回 18:00 UTC = 03:00 JST。反復検証のため workflow_dispatch も持たせた (ADR-067 段 2 の知見 2)。dry_run 入力でゲート通過まで走らせて push を止められる。 ## 信頼境界 (ADR-066 決定 3 / ADR-067 と同型) 台帳・ゲート exe・autonomy-config.toml はすべて master ref の写しから調達する。schedule イベントは GitHub の仕様上 default branch の workflow 定義で実行されるため、本ファイル自体も PR ブランチからは差し替えられない。push / PR 作成は workflow step が行い agent には gh / git を 与えない。 ## 背圧ゲートを 2 回呼ぶ pre-flight (agent 起動前) は Max 枠の節約、authority (push 直前) は push の権威。pre-flight は continue-on-error で受け Select task に if を付けて deny 後は後続を走らせない。背圧 deny は 設計上の正常動作なので job failure にはしない。 ## agent には Bash を与えない --allowedTools は Read/Edit/Write/Glob/Grep のみ。cargo を許さないのは、cargo test が build.rs と テストバイナリ = agent 自身が直前に書いたコードを実行するため。agent のターン中はプロセス env に action の資格情報が載るので、cargo を与えることは「自分で書いたコードを資格情報のある環境で、 いかなるゲートより前に実行させる」ことに等しい。詳細は ADR-072 決定 5。 ## CI を draft PR へ紐づける App token (ADR-072 決定 8) GITHUB_TOKEN で作成した PR の pull_request イベントは**承認待ちの run** になり、人間が Approve を押すまで ci.yml が動かない。Windows を主開発環境とする本プロジェクトで 2 OS 検証を 人間の操作待ちにする設計は採れないため、push と PR 作成に App installation token を使う。 PAT ではなく App を選ぶのは、オーナーの PAT が Repository admin として動き ADR-067 の ruleset backstop を bypass するため。App installation は独立 actor で admin ではない。 App 権限は Contents / Pull requests の write のみで **Workflows は付けない** — .github/workflows/** を含む push が権限側でも通らず Guard step の禁止リストと二重になる。token 発行は publish 直前 (寿命 1 時間に対し実装 agent が最大 60 ターン走るため)。背圧の計数は GITHUB_TOKEN のままで、 読み取りに triggering の含意が無い以上 App token の露出面を広げない。 ## 内部 Verify はコストフィルタであって品質保証ではない ubuntu 単独・--ignored 無し・hooks smoke 無しで ci.yml の真部分集合。目的は「コンパイルも 通らない変更で draft PR を作り背圧の枠と人間のレビュー時間を浪費しない」ことに限る。品質の 保証は PR に紐づく ci.yml (2 OS matrix) が担う。2 OS matrix をここへ複製しないのは検証の定義が 2 箇所へ分かれて drift するため。この区別は step 名・コメント・PR 本文の 3 箇所に明記した。 ## ガードレールの保護 (2 層) - **禁止リスト**: work/ の diff が autonomy-config.toml / .github/workflows/ / 各ゲート crate に 触れていたら push しない。空 diff も deny - **改ざん検知**: agent の file tools は $GITHUB_WORKSPACE 全体に届き master-ref/ を書き換え られる。Build 段で採った sha256 と authority gate の直前で照合する。基準値は $RUNNER_TEMP = ワークスペース外に置く ## 停止段の切り分けを 1 行で完結させる Report outcome が全 step の outcome を 1 行に出す (implement / app_token 含む)。 「Stop when there is nothing to implement」は 2 経路 (select 非ゼロ / pre-flight deny で select 自体が skip) で発火するため、経路ごとに参照すべきログ行を出し分ける。 ## その他の fail-closed - open PR の取得が上限 100 件に張り付いたら停止する (背圧の過小評価を防ぐ) - 着手済み順位の除外は draft かどうかに依らない (ready 化された claude/nightly-N も再実装しない) ## 禁止リストに台帳自身を追加 (ADR-072 決定 6) docs/claude-code-web-tasks.md は「次に何を実装するか」を決める仕組みであり、config や gate exe と同じく **agent を縛る側**にある。ここが draft PR 経由で書き換わると、以後の タスク選択が静かに汚染される。初版はここを落としていた。 ## Count open drafts に continue-on-error を付けない理由をコメント化 設計上の停止点 (背圧 deny / タスク無し / guard deny) は green + NIGHTLY_SKIP、インフラ 障害は red、という色分けは意図的。ADR に書いていなかったため pre-push review に再指摘 された。決定 10 として明文化し、workflow 側にも根拠を残す。 ## timeout-minutes: 60 を明示 他 workflow は例外なく明示済み (ci.yml=60 / pr-monitor.yml=15, 20 / release-binaries.yml=30) で、本 job だけ無指定 = 既定 360 分だった。無人・毎日回る job で ハングを 6 時間放置すると Max 枠を焼き続ける (ADR-072 決定 4 が節約のためにゲートを 二重に呼ぶ設計と矛盾する)。Implement 60 turns + 後続 step を見込んだ上限。 js-yaml で構文検証済み (17 step)。 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…実装と一致させる WP-18 PR 3。pre-push review が計 8 サイクルで返した REJECT と warning、および CodeRabbit の 指摘 2 件を ADR へ反映する。fix step は毎回 workflow を直すが ADR を一度も更新しないため、 設計根拠を追いつかせる。 ## 決定 8 — Windows CI が走らない問題への対処 GITHUB_TOKEN で作成した PR の pull_request イベントは **承認待ちの run** になり、人間が Approve を押すまで ci.yml が動かない。Windows を主開発環境とする本プロジェクトで 2 OS 検証を 人間の操作待ちにする設計は採れない (2026-08-07 ユーザー判断)。 公式は App installation token または PAT を回避策として挙げるが **PAT は採れない** — ADR-067 段 0 の ruleset は bypass を Repository admin ロールに与えており、オーナーの PAT は その admin として動くため 5 層目の防波堤を素通りする。 副次効果として job の GITHUB_TOKEN から write を落とせた。GITHUB_TOKEN は claude-code-action の github_token 入力として agent (未信頼) が触れる唯一の GitHub 資格情報であり、write のままだと 決定 6/7 の前提を token 側から崩しうる。App token の導入で Phase B より弱い権限で同じことが できる構成になった。 ## 決定 9 — git 操作は agent が触れていない作業ツリーで行う 決定 6 の禁止リストは staged path の列挙で .git 内部を構造的に見ない。一方 add/commit/push は core.hooksPath / filter.* / core.fsmonitor / credential.helper / core.sshCommand / *.textconv 等 多数の設定経路から外部プログラムを起動する。刺さる先は決定 8 の App token を持つ publish step。 最初は deny-list で対処したが **2 回連続でレビュアーが漏れを見つけた**。列挙で追随する限り 往復は終わらない。publish/ を Implement 終了後に新規 clone し、作業ツリーのファイルだけを rsync --delete --exclude '.git/' で運ぶ。agent はターン終了後に何も書けないので、その後に 作られた .git は定義上手が届かず、クラスごと消える。 決定 7 とは非対称。master-ref は作り直せないので検知にとどまるが、publish は作り直せるので 排除する。 ## 決定 3 — 除外判定を PR 状態からブランチ存在へ クローズされた draft の順位が再選択され push が non-fast-forward で失敗する無駄ループを塞ぐ。 ls-remote は一致なしでも exit 0 + 空出力なので「0 件」と「取得失敗」を取り違えない。 ## CodeRabbit 指摘 2 件への対応 1. **unit test 件数が実装と不一致** — 23 件 (内訳 17/6) と書いていたが実測は 25 件 (17/8)。 fix step が追加した 2 件を反映していなかった。実測して修正 2. **見出しと本文の件数が不一致** — 見出しと表は 6 件へ更新済みだったが、**本文が「2 件」の まま**残っていた。指摘の要約 (「見出しと表の件数」) だけを見ると解消済みに見えるが、 コメント本文が本文側も名指ししていた。本文を 6 件へ更新し、うち 2 件は指摘の具体例自体が 誤っていたこと (#3 は誤りに気づかず設計を動かし、#6 は誤りの中の正しい構造を拾った) も 併記した ## その他 内部 Verify はコストフィルタであって品質保証ではない (ubuntu 単独・--ignored 無し・ hooks smoke 無しで ci.yml の真部分集合)。品質の保証は draft PR に紐づく ci.yml が担う。 ## 決定 6 — 禁止リストに台帳自身を追加 「自分を縛る仕組み」として config と gate exe は列挙したのに、**選択元である台帳** (docs/claude-code-web-tasks.md) を同じクラスだと認識していなかった。決定 1 で「台帳は master ref から読む」と信頼境界を引きながら、台帳への書き込み経路を塞いでいない。 agent が台帳を書き換えた diff が draft PR に紛れ、人間が見落としてマージすると、以後の タスク選択が静かに汚染される。禁止リストへ 1 エントリ追加して塞いだ。 ## 決定 10 — 停止の色分けを決定として明文化する pre-push simplicity review が「Count open drafts に continue-on-error が無く、他の停止点の ように graceful degradation しない」と指摘した。**現状を維持し、理由を決定として残す。** 設計上の正常な結末 (背圧 deny / タスク無し / guard deny / 空 diff) は green + NIGHTLY_SKIP、 インフラ障害 (gh / network / clone の失敗) は red。両者を同じ扱いにすると、run 一覧から 「本当に壊れた夜」と「何もすることが無かった夜」の区別が消える。毎晩回る無人ループでは、 この 2 つが混ざった時点で run 一覧が読まれなくなる。 Report outcome は if: '!cancelled()' なので red でも 1 行サマリは出る。診断は失われない。 ## 件数 ## 捕捉 9 — job の実行時間に上限が無かった 同リポジトリの他 workflow は例外なく timeout-minutes を明示している (ci.yml=60 / pr-monitor.yml=15, 20 / release-binaries.yml=30) のに、本 job だけ無指定 = 既定 360 分。 同じ claude-code-action を使う pr-monitor.yml の fix job が 30 turns に対し 20 分を課す のに対し、本 job は turns 2 倍 (60) で上限なしだった。 決定 4 で Max 枠の節約のためにゲートを二重に呼ぶ設計にしておきながら、**ハングした run が 枠を焼き続ける経路**を空けていた。timeout-minutes: 60 を明示。 スモーク観測 8 項目 / 静的レビューの捕捉実績 9 件 / js-yaml 17 step / 決定 10 件 — 数値の 記載はすべて実測と突き合わせて一致を確認した。 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
WP-18 PR 3 (5/5)。 ## 記帳 - WP-18 節の見出しと全体像表を「着手中」→「実装済(実走スモーク待ち)」へ - PR 2 / PR 3 の項目を実装内容付きで確定記録。PR 3 は 16 step 構成 (選択 → 実装 → コストフィルタ → clean publish tree → 禁止リスト → 改ざん検知 → gate → App token → draft PR 作成) と、決定 7 / 8 / 9 の由来を残した - 受け入れ基準を表へ変え、充足 3 件と**未実施 2 件**を分けた ## 件数記載を実測と一致させた CodeRabbit が ADR 側で指摘した「件数が実装と一致しない」型の不整合が、計画書側にも 同型で存在していた: - unit test 23 件 → **25 件** (fix step が追加した 2 件を反映していなかった) - workflow 15 step → **16 step** (clean publish tree 段の追加を反映していなかった) - スモークの同梱観測 7 項目 → **8 項目** 観測項目については件数だけ直さず、**内訳の列挙をやめて ADR-072 の表を指す形**に変えた。 同じ一覧を 2 箇所に持つ限り再び drift するため (#362 の post-merge feedback が指摘した single source-of-truth 問題と同型)。 ## 受け入れ基準の状態を正直に書く 実走スモークは本 WP の受け入れ基準の中核だが**未実施**である。「実装が全部 land した = WP 完了」と書ける状態ではないため表で未実施を明示した。 採用率 50% が統計的な意味を持たない点 (2 週間・最大 14 件) も併記した。 ## chain 宣言 PR 1 が導入した 3 点の消費側を実装済みの step 名で具体化した。PR 2 → PR 3 の**実行時** 依存も明記した (PR 2 未マージだと選択 exe が exit 2 で止まるが、これは設計どおりの fail-closed で静かな no-op にはならない)。 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
3515a87 to
7e0d8a6
Compare
* docs(todo): WP-18 で検出した問題を 4 エントリへ登録 (順位 374-377) WP-18 (夜間 todo 消化ループ、#361 / #362 / #363) の実装中に pre-push review・ CodeRabbit・ユーザー指摘で検出した問題 9 件のうち、todo 登録が要る 8 件を **実装時の PR 粒度**で 4 エントリへまとめる。切り分けは 2026-08-06 にユーザー確認済み。 ## 評価時の #1 を検証し、todo 登録が不要になった #1 は「Bash prefix 許可の悪用可能性検証と全経路への横展開」として登録予定だった。 ユーザー指示により登録前に検証したところ、**前提が誤りだった**。 #363 の security review は「`Bash(cargo test:*)` は前方一致でシェルを解釈しないため `cargo test` に任意コマンドを連結すると通過する」と主張していた。公式ドキュメントは これを明確に否定している: Claude Code is aware of shell operators, so a rule like `Bash(safe-cmd *)` won't give it permission to run the command `safe-cmd && other-cmd`. The recognized command separators are `&&`, `||`, `;`, `|`, `|&`, `&`, and newlines. A rule must match each subcommand independently. `--allowedTools` も同じルール体系に属する (managed settings の deny を --allowedTools で 上書きできない、と明記されている)。 結果: - `pr-monitor.yml` の Phase A 分析 agent に**当該の穴は無く、対処不要**。production の live な穴という当初の見立ては誤りだった - 残作業は `ADR-072` 決定 5 の根拠記述の訂正のみで、#363 が open のうちに同 PR へ直接 反映する。よって todo エントリを立てない 検証結果と経緯はセクション冒頭の対応表に残した。 ## この一件自体を教訓として取り込んだ 「レビュー指摘への対応時チェックリスト」エントリに 4 項目目を追加した — **指摘が技術的 前提 (ツールの挙動・仕様) に依拠しているなら、対処より先にその前提を検証する**。とくに 設計変更や他経路への横展開を伴う場合。 今回は未検証の前提のまま (a) agent から Bash を落とす設計変更を行い、(b) それを ADR の 決定として記録し、(c) さらに「同じ形が production にもある」と横展開の警告まで出していた。 一次情報に当たれば 1 回の WebFetch で否定できた。 ## 内訳 - 順位 374: WP-18 夜間ループの実走スモーク実施 (Tier 1、評価時の #2/#3) - 順位 375: レビュー指摘への対応時チェックリスト (Tier 2、評価時の #4/#5/#6 + 今回の #1) - 順位 376: push-runner の bookmark 自動前進がスタック境界を壊す (Tier 2、評価時の #7) - 順位 377: 夜間ループの防御を検知から防止へ格上げする判断 (Tier 3、評価時の #8/#9) ## まとめ方の方針 リポジトリの既存バッチ登録 (#350〜#357 の 24 件を 8 エントリへ) と同じく実装時の PR 粒度で まとめた。評価時の番号との対応はセクション冒頭に表で残してある。 順位 374 (スモーク) の観測項目は `ADR-072` の実走スモーク節に表があるため、todo 側は スケジューリングの掛かりだけを持ちチェックリストを複製しない。同じ表を 2 箇所で管理すると 必ず drift する (#362 の post-merge feedback が指摘した single source-of-truth 問題と同型)。 ADR-033 (絶対番号は table のみに保持) に従い、エントリ本文には順位番号を書いていない。 todo20.md は 50KB 閾値内。pnpm lint:docs / markdownlint ともに green。 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(todo): #363 post-merge feedback の採用 6 件を登録 (順位 378-383) WP-18 最終 PR (#363、ADR-072) のマージ後 feedback が Tier 1 に 4 件・Tier 2 に 2 件を 採用候補として挙げた。ユーザー承認 (2026-08-07) を得て登録する。 ## 6 件は 1 本の根から出ている 台帳 (docs/claude-code-web-tasks.md) の 内容 / 対象ファイル / 注意 は自由記述のまま 無人 agent のプロンプトへ流入する。agent は $GITHUB_WORKSPACE 全体に書き込め、その 出力は draft PR 本文という公開面に出る。ADR-054 の信頼境界そのもの。 - 378 (XS) 台帳を ADR-035 の docs-only 除外パス表へ追加 — 他 3 件の前提。台帳だけを 変える PR が緩い評価経路に乗ると、対策そのものを迂回する台帳 PR が通りうる - 379 (S) tool scope を work/** へ限定 — ADR-072 決定 7 の改ざん検知が必要になって いる根本原因。実装後も検知層は残す (防御を 1 枚に減らす変更ではない) - 380 (M) 台帳フィールドを untrusted data として明示 framing - 381 (S) 台帳由来 SUMMARY の draft PR 本文出力に screening - 382 (M) injection payload の regression test (380 に依存) - 383 (S) is_separator_row のパイプ検証欠落 ## 期限を「定常運用開始前」に固定する 実効リスクは現時点では低い — 悪意ある台帳行を master へマージするのはユーザー自身で、 単独運用では外部からの注入経路が無い。ただし夜間ループが定常運用に入り draft PR の 流量が増えると前提が変わるため、無期限の Tier 積みにしない。 順位 374 (実走スモーク) は dry_run で PR を作らないため本件の実害が無く、待たせない。 ## 383 は実コードで確認済み is_table_row は行頭 | を要求するが、is_separator_row は split_cells の結果しか見ない。 split_cells("---") は ["---"] を返し全セルが '-' のみなので真になる。markdown の 水平線がセパレータ行として通る (todo ファイル自身が --- を使っている)。 ADR-072 決定 2 の fail-closed 設計の coverage hole。 ## 併せて 順位 374 のスモーク観測項目数を 4 → 8 へ修正した (ADR-072 側の実測と不一致だった)。 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(harness-plan): WP-18 を 3 PR マージ済へ更新し残作業 2 系統を明示する #363 のマージ (2026-08-07) で WP-18 の実装 3 本がすべて land した。 ## 「実装完了 = WP 完了」ではないことを表で残す コードは全部 master にあるが、**夜間ループはまだ 1 度も走っていない**。この状態を 「実装済」の一語で片付けると、次のセッションが受け入れ基準を満たしたものと誤読する。 残作業を 2 系統に分けて表にした: 1. 実走スモーク (順位 374) — 受け入れ基準の中核 2. prompt injection 対策 4 件 (順位 378-381) — 定常運用開始前に必須 ## スモークの前提が充足したことを記録 受け入れ基準の表は「(a) workflow が master にある (b) 台帳に無人可マークがある」を 未充足として書いていたが、**両方ともマージで解消した**。残る操作は GitHub UI 側の AUTONOMY_ENABLED 設定のみなので、その 1 点へ書き換えた。 ## 依存関係を明示する 378-381 は 1 本の根 (台帳の自由記述が無検証で agent プロンプトへ流入) から出ている。 一方スモークは dry_run で PR を作らないため本件の実害が無い。したがって **スモークは 378-381 を待たずに着手してよい**と明記した。次セッションが順序で 迷わないようにするため。 ## 未 push の改善 3 点の所在を残す #363 の最終 push が security REJECT で止まったため、改ざん検知の red 化 / 決定 10 の 色分け表 / 決定 6 の列挙基準が master に載っていない。ローカル bookmark wp18/unpushed-improvements (fc22403c) に保持していることを記録した。いずれも 可観測性と文書の改善で、fail-closed 自体は master 版でも成立している。 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(todo): 外部設定 (GitHub App / variables / secrets) の実体記録を登録 (順位 384) 新セッションでの指摘 (2026-08-07): workflow は vars.NIGHTLY_APP_ID / secrets.NIGHTLY_APP_PRIVATE_KEY を参照するが、App の作成・インストール・登録を 記録した文書がリポジトリ内に無い。 ## 欠けているのは設計根拠ではなく運用実体 ADR-072 決定 8 は「なぜ App token か」「なぜ PAT ではないか (オーナー PAT は Repository admin として ADR-067 の ruleset backstop を素通りする)」「どの権限を 付けるか (Workflows は付けない)」「なぜ publish 直前に発行するか (token 寿命 1 時間)」を厚く残している。 記録が無いのは以下: - App を実際に作成した事実・日付・名称・インストール範囲 - NIGHTLY_APP_ID = variable / NIGHTLY_APP_PRIVATE_KEY = secret という登録先の別 - 既存の Claude GitHub App との区別 (あちらは Workflows を含む広い権限を持つ別物) - 再構築手順 (鍵ローテーション・派生プロジェクト展開) NIGHTLY_APP の文字列はリポジトリ全体で workflow の 2 行と ADR 残課題の 1 行にしか 現れない。 ## これは ADR-051 違反 ADR-051 (クロスシステム設定 coupling) は内部設定と外部 SaaS 設定が論理結合する場合に (1) 両設定ファイルへの相互参照コメント (2) 期待値の組み合わせ表の ADR 必須記載 (3) 変更は両側を同一 PR、の 3 点を規律として定めている。workflow ↔ GitHub App + repository variables/secrets はこの型で、3 点とも未実施。 前例として ADR-067 段 0 は repository ruleset を ruleset 名つきで「設定済み」と 記録している。ADR-072 は同じ扱いをしていない。同型の欠落が AUTONOMY_ENABLED にも あり (ADR-066 は「Actions variable を使う」とは書くが現状値を記録していない)、 本エントリで一緒に扱う。 ## 順位 374 と同時実施にする理由 スモークでは AUTONOMY_ENABLED の設定と App token の実動確認のため GitHub UI を 触るので、その過程で実値がすべて揃う。先行して記録しようとすると値が確定せず 二度手間になる。 ## 教訓を残す App の作成手順・Expire user authorization tokens の扱い・既存 App との違いは 2026-08-07 のセッションでユーザーへ提示したが、リポジトリへ残さなかった。会話は 次のセッションに残らないが workflow は残る。参照だけが残って由来が消える状態を 作った。順位 375 と同じクラスの失敗としてエントリ本文に記録した。 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(review): CodeRabbit Major 4 件を妥当性判定のうえ 3 件へ対応する (#364) 自動 fix 経路 (f698d19) が 1 件 (#4 台帳への非記録ルール追加) を対応済みで、 本コミットはその内容を含んだうえで残りを手で対応した結果である。同一ファイルの 近接行のため path 単位で分離できず 1 コミットに畳み込まれている。 ## 妥当性の判定 severity ラベルではなく、プロジェクトの設計方針に照らして 1 件ずつ判定した。 - #1 adr-072:335 (秘密値を ADR に記録しない) — **妥当**。「実走スモークで実値を 確認し ADR へ追記する」は秘密鍵本文まで書くと読める。ADR-051 が記録を課すのは 結合の存在と期待値の組み合わせであって秘密の実値ではない。設定メタデータに 限定し、鍵本文と token は ADR にも git 履歴にも残さないことを明記した - #2 harness-improvement-plan:223 (受け入れ基準が成功経路だけ) — **妥当**。本 プロジェクトは背圧 12 シナリオ・kill-switch 8 シナリオと停止側を drill で 固めてきたが、夜間ループの停止側は実走未観測。WP-17 の残課題 (明示的 false と config 側 deny が実走未観測) と同じ穴。AUTONOMY_ENABLED の 3 状態を受け入れ 基準へ追加した。指摘本文が名指しした 2 箇所 (計画書 L223 / todo20 L293-304) の 両方に反映している - #3 todo20:480 (prompt injection の回帰 fixture) — **妥当**。順位 382 の payload 例 "; echo PWNED; #" は shell injection であって prompt injection ではない。 台帳テキストが流れ込む先は shell ではなく LLM プロンプトなので、テストが目的と 噛み合っていなかった。自然言語 adversarial payload (本命) と shell/パース形式 payload (堅牢性) の 2 系統へ分離した ## 自動 fix の実測確認 f698d19 は 1 ファイル 2 行追加のみで範囲外の編集ゼロ。内容も妥当だったため そのまま採用した (ADR-068 の後退検知の趣旨に沿って diff を実測で確認済み)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#366) * feat(nightly-todo): 改ざん検知を red 化し決定 10 の色分けと決定 6 の列挙基準を明文化する (ADR-072) #363 の最終 push が pre-push security review の REJECT で止まったため master に 載らなかった 3 点を、master 上に載せ直す。いずれも可観測性と文書の改善で、 fail-closed 自体は master 版でも成立している。 ## 改ざん検知を green から red へ (workflow) ゲート資産の改ざん検知 step から continue-on-error を除去する。 fail-closed は元から成立していた — continue-on-error: true でも下流の `if: steps.integrity.outcome == 'success'` で push は止まる。問題は**色**で、 green で終わるため run 一覧上「何もすることが無かった夜」と区別が付かなかった。 毎晩回る無人ループでは、この 2 つが混ざった時点で run 一覧が読まれなくなる。 改ざん検知は「何かがゲートを無効化しようとした」という、この系が出しうる最も 大きい信号である。red で落ちても後続 step は if: の評価前に skip されるため push には到達せず、Report outcome は if: '!cancelled()' なので診断行は出る。 ## 決定 10 に色分け表を追加 (ADR-072) 「設計された停止」と「インフラ障害」の 2 分類を表にしたところ、**改ざん検知が どちらにも入っていない**ことが露出した。分類を明文化すると分類に入らない結末が 可視になる、の実例として § 静的レビューが捕捉した件 の #10 に記録した。 見落としていたのは安全性ではなく可観測性の側だった。 ## 決定 6 に列挙基準を追加 (ADR-072) 禁止リストの基準は「危険か」ではなく「**将来の無人 run のゲートを緩めるか**」で ある。security review が挙げた Cargo.toml / Cargo.lock の欠落を採らない根拠が これで、通常の diff は人間の PR レビューとマージという既存の防衛線が効く。 基準を持たないと禁止リストは「怪しいもの全部」へ膨らみ正当なタスクを弾き始める。 ## 適用方法 保持していたローカル bookmark (wp18/unpushed-improvements) は #363 マージ前の スタックのため、そのまま復元すると **#364 で入れた ADR-072 § 残課題 の追記 (外部設定の実体が未記録 / 秘密値は記録しない) を巻き戻す**。したがって workflow は ファイル単位で restore し、ADR-072 は追加分 4 箇所のみ手で適用した。適用後に lpzvttwu との差分が #364 の 1 行だけであることを実測確認している。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(nightly-todo): 決定 10 の red 分類を残り 3 step へ適用する (#366 CodeRabbit 指摘) 自動 fix 経路が作った commit を、内容を実測検証したうえで採用したもの。 **push 自体は scope guard (ADR-054) が BLOCK した** — finding の anchor が docs/adr/adr-072 なのに fix が .github/workflows/ を触ったため。指摘の remedy が anchor と別ファイルにある典型で、guard の設計どおりの挙動だが本件は injection では ないため誤検知にあたる (WP-11 の enforce 期間の観測データとして計上すべき)。 ## 指摘の妥当性 同 PR で追加した決定 10 の色分け表は「gh / network / clone の失敗 → red」と 定めているのに、その 3 経路が continue-on-error: true で green に落ちていた。 表を追加した PR 自身が作った不整合であり、妥当と判断して採用する。 - Prepare a clean publish tree — git clone (ネットワーク I/O) - Mint App token — GitHub API 呼び出し (secret 誤設定・GitHub 障害) - Push branch and open draft PR — git push / gh pr create ## 実測検証 (fix の出力を鵜呑みにしない) - 下流の if: はいずれも `steps.<id>.outcome == 'success'` 形式のため、失敗時は 後続が skip され push へ到達しない (fail-closed は維持) - Report outcome は `if: '!cancelled()'` なので red でも診断行は出る - dry_run=true では app-token / publish は if: により **skipped** (failed ではない) ため、dry_run の run は green のまま ## ADR への記帳 表を書いた著者自身は 1 件 (改ざん検知) しか見つけられず、残り 3 件は他者の レビューで出た。**分類の明文化は露出の必要条件であって十分条件ではない**ことの 実例として決定 10 へ追記した。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…creening (ADR-072 決定 12-14、順位 379/380/381) (#369) * feat(nightly-todo): agent の tool scope 限定・台帳の untrusted framing・公開面 screening (ADR-072 決定 12-14、順位 379/380/381) 夜間ループの信頼境界を 3 決定で埋める。台帳の自由記述が無検証で無人 agent の プロンプトへ流入し、agent は workspace 全体に書け、その出力が公開面 (draft PR 本文) に 出る、という ADR-054 の信頼境界そのものへの対処 (#363 post-merge feedback Tier 1)。 ## 決定 12 — tool scope (順位 379) agent の file tools を Edit(work/**) へ限定し、Edit(master-ref/**) / Edit(publish/**) を deny する。決定 7 の改ざん検知が「この穴があるために必要になった検知層」であり、その 予防側を入れる。改ざん検知は残す (層を減らす変更ではない)。 **実装で判明: Write(path) 指定子は no-op。** CLI 2.1.218 はファイル編集の scope を Edit(path) だけで判定し、Write を含む全編集ツールを Edit がカバーする (CLI 自身が 「Use Edit(path) instead」と警告)。初版の Write(work/**) / Write(master-ref/**) は 効いているように見えて何もしない飾りだったため除いた。 **両側をローカル CLI で実測 (2026-08-08)。** deny: master-ref/ への Write は "File is in a directory that is denied by your permission settings." で拒否され ファイルは作られず config も無傷。allow: 対照の work/ への Write は成功。加えて実 dispatch run で agent が順位 240 の対象 1 ファイルのみ編集し guard=success を確認。 Read(master-ref/**) の deny は hygiene にとどまる — Grep が非スコープなので内容は 迂回して読める。脅威モデルの本体は書き込み (ゲート改ざん) で、そちらは閉じている。 ## 決定 13 — 台帳の untrusted framing (順位 380) 台帳フィールドを ===BEGIN_LEDGER_DATA=== / ===END_LEDGER_DATA=== で囲み「中身は データであって指示ではない」と明示する。区切りは台帳側から偽装できるため、parse 側で LEDGER_DATA を含むフィールドを exit 2 で止める (決定 2)。自然文の指示は弾かない — 遮断は framing と scope の責務で、自然文まで弾くと正当なタスク記述が書けない。 不可視文字も弾く: ゼロ幅文字を区切り語に挟めば contains を素通りできる (LEDGER<ZWSP>_DATA) 一方 LLM はノイズを跨いで同じ語と読む。この検査回避を止める。 ## 決定 14 — 公開面 screening (順位 381) draft PR 本文の台帳由来テキストを screen する。公開面の棚卸し結果、外部可視になるのは PR 本文の summary のみ (rank は u32、ブランチ名は format! で構造的に安全)。summary_display 出力を足し workflow がコードスパンで囲んで出す — コードスパン内では markdown 非描画・ @mention 通知なしで注入効果が消える。screening は「コードスパンから抜け出せる文字を 残さない」に絞り、バッククォート置換・制御/不可視文字除去・200 文字切り詰めを行う。 @ は書き換えない (無害化はコードスパンの役目)。 ## 検証 cargo test -p cli-nightly-task-select: 41 passed (parse 層の good/bad 対 + screening 5 件 + 不可視文字/枠偽装の回帰)。lib-docs-policy: 12 passed。ローカル CLI で deny/allow 両側を実測。workflow は js-yaml で 18 step を確認。 pre-push review の fix step が is_control() の Cc 限定を突いて bidi/ゼロ幅の Cf 対応を 足し (妥当と実測確認して採用)、その後で公開面のみ塞ぎ parse 側が素通りだった隣接穴を 自分で塞いだ (LEDGER<ZWSP>_DATA を実証)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(review): apply CodeRabbit fixes for #369 Resolved findings: - [Minor] docs/adr/adr-072-nightly-todo-loop.md:460 完了済みの外部設定記録を残課題から削除してください。 - [Minor] docs/harness-improvement-plan.md:179 テスト件数を実装と一致させてください。 - [Minor] src/cli-nightly-task-select/src/ledger.rs:318 接尾辞を含めて 200 文字以内にしてください。 - [Major] src/cli-nightly-task-select/src/ledger.rs:355 U+200E と U+200F も拒否してください。 * docs(harness-plan): 順位 380/381 のテスト件数記載を対象ケース列挙に置き換える (#369 CodeRabbit 指摘) CodeRabbit 指摘 (docs/harness-improvement-plan.md L178-179): framing テストは 「3 件」ではなく実際は 9 件、public screening は「5 件」ではなく 10 件。自動 fix (8aac859) が ledger.rs のコードとテストは直したが、計画書側の件数記載は stale の まま残っていた。 件数は今後も変動する (この PR 内でも RLM/LRM 追加で 2 件増えた) ため、指摘の代替案 「件数を記載せず対象ケースを記載」を採り、固定値ではなく検証している観点を書く形に した。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
概要
WP-18(夜間 todo 消化ループ)の PR 3 / 3 本、最終。台帳の「無人可」タスクを 1 件無人実装し draft PR 作成で停止するループを実装する。マージ判断は人間(ADR-052 の commitment 点の手前で止まる操作)。
設計は ADR-072 に起票した(試験運用、bounded lifetime 2026-11-06)。PR 1(#361)の背圧と PR 2(#362)の台帳マークを前提にしており、どちらもマージ済み。
変更内容(5 コミット)
feat(nightly-task-select)chore(build)build:allへの配線feat(nightly-todo)workflow_dispatch)docs(adr-072)CLAUDE.md索引docs(harness-plan)PR size gate の override について
PR_SIZE_CHECK_OVERRIDE=1を使用しました。1510 行 / block 閾値 1500 行、0.7% 超過です。切れる関節が 2 つありますが、どちらも ADR-069 が問題視した形でした。selector crate と workflow の間で切ると、workflow は selector の最初の呼び手なので §3.1「抽出と最初の呼び手の間で切らない」に反します。実装と docs の間で切ると、ADR-072 が実装から離れ、この PR の価値の中心(レビューが捕捉した 6 件の失敗記録)が実装レビュー時に見えなくなります。
ADR-069 §3.3 は「悪い関節で切った分割はチェーン全体のコストで上限超過 1 回分を上回り得る。incident の初回分割(1613 行 = block の 8% 超過)はこの判断を誤った実例」と記録しており、今回はそれより小さい margin で同じ形の関節しかありません。ADR の記述を削って閾値内に収める案は取っていません(6 件の失敗記録はこの PR の主要な成果物です)。
設計上の要点
「何を実装するか」は Rust 分類関数が決める
ADR-052 は「分類ロジックを Rust 分類関数を用意せず自律 actor の実行時 LLM 判断に委ねる」ことをアンチパターンとして挙げています。何を実装するかは自律動作の起点で、ここが揺れると下流のゲートがいくら堅くても「意図しないタスクを正しく実装した draft PR」が出てきます。
毎晩同じタスクを実装し直さない
ブランチ名に順位を埋め(
claude/nightly-<順位>)、同名ブランチが存在する順位を除外します。open PR の有無で判定すると、クローズされた draft の順位が再選択され、push が non-fast-forward で失敗する無駄ループになります(クローズは受け入れ基準が前提にしている運用なので必ず踏みます)。agent には Bash を与えない
cargo testはbuild.rsとテストバイナリ、つまり agent 自身が直前に書いたコードを実行します。agent のターン中はプロセス env に action の資格情報が載るため、cargo を与えることは「自分で書いたコードを資格情報のある環境で、いかなるゲートより前に実行させる」ことに等しくなります。CI を draft PR へ紐づける App token(決定 8)
GITHUB_TOKENで作成した PR のpull_requestイベントは承認待ちの run になり、人間が Approve するまでci.ymlが動きません。Windows を主開発環境とする本プロジェクトで 2 OS 検証を人間の操作待ちにする設計は採れないため、push と PR 作成に App installation token を使います。PAT ではなく App を選んだのは、オーナーの PAT が Repository admin として ADR-067 の ruleset backstop を bypass するためです。
副次効果として job の
GITHUB_TOKENから write を落とせました。GITHUB_TOKENはclaude-code-actionのgithub_token入力として agent(未信頼)が触れる唯一の GitHub 資格情報であり、write のままだと「agent は push できない」前提を token 側から崩せます。App token の導入で Phase B より弱い権限で同じことができる構成になりました。git 操作は agent が触れていない作業ツリーで行う(決定 9)
publish/を Implement ステップ終了後に新規 clone し、成果物は作業ツリーのファイルだけをrsync --delete --exclude '.git/'で運びます。以後の git 操作はすべてpublish/に対して行います。当初は deny-list(
core.hooksPath/filter.*等)で対処しましたが、2 回連続でレビュアーが列挙漏れを見つけました。git はcore.fsmonitor/credential.helper/core.sshCommand/*.textconvなど多数の経路から外部プログラムを起動するため、列挙で追随する限り終わりません。agent はターン終了後に何も書けないので、その後に作られた.gitは定義上手が届かず、クラスごと消えます。決定 7(
master-ref/の sha256 照合)とは非対称です。master-refは作り直せないので検知にとどまりますが、publishは作り直せるので排除します。静的レビューが捕捉した 6 件(すべて著者の見落とし)
pre-push review を 8 サイクル通しました。
Pre-flight gateだけid/continue-on-errorが無く背圧 deny が job failure になるmaster-ref/に届きゲート資産を改ざんできるwork/の diff に限定していた(→ 決定 7)Bash(cargo test:*)の prefix 許可でコマンド連結できるGITHUB_TOKENが write のままで agent が触れるwork/.gitを書けば App token を持つ step で任意コマンドが走るgit diffで実装した時点で.git/が見えないことに気づかず、同じ step に live な資格情報を置いていたalias.*/core.fsmonitor/credential.helperを漏らしている3 と 6 は指摘の具体例が誤っていたものです。3 は誤りに気づかず設計を動かし、6 は誤りの中の正しい構造を拾いました。この非対称も ADR に記録しています。
検証
rank=203、203除外 →rank=240、7 件除外 → exit 3、旧台帳 → exit 2cargo test --workspace50 スイート green /cargo clippy --workspace --all-targets警告ゼロpnpm lint:docs/ markdownlint green受け入れ基準の状態
実走スモークは本 PR マージ後に、ADR-067 段 2 の知見に従いマージせずブランチ ref への
workflow_dispatchで反復します(dry_run入力あり)。観測 8 項目の一覧は ADR-072 § 実走スモークの表が正です。既知の残課題(ADR-072 § 残課題)
master-ref/の改ざんは検知であって防止ではない。防止には別 job + artifact への構造変更が要るSummary by CodeRabbit
新機能
ドキュメント