Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion .github/workflows/nightly-todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,44 @@ jobs:
if: steps.preflight.outcome == 'success'
run: |
set -euo pipefail
# **tee ではなくリダイレクトにする (ADR-072 決定 14、post-merge feedback #369)。**
# tee は exe の全出力を Actions ログへも出すが、そこには生の自由記述
# (summary / target_files / caution) が含まれる。**step ログも public repo では
# 第三者に可視**なので、tee は決定 14 の screening を迂回する公開面になっていた。
# 生の出力はファイルに留め ($GITHUB_OUTPUT 経由でのみ使う)、ログへは安全な行だけを出す。
master-ref/target/release/cli-nightly-task-select \
--ledger master-ref/docs/claude-code-web-tasks.md \
--exclude-ranks "${{ steps.inflight.outputs.exclude_ranks }}" \
| tee "$RUNNER_TEMP/selected.txt"
> "$RUNNER_TEMP/selected.txt"
# 許可リスト方式 — exe が出す key のうち、ここに列挙したものだけを転送する。
# 想定外の key 注入を防ぐ fail-closed だが、**exe 側に出力を足したらここも足す**
# 必要がある (片方だけ変えると新しい出力が黙って捨てられ、参照側は空文字になる)。
grep -E '^(rank|branch|target_files|summary|summary_display|caution)=' "$RUNNER_TEMP/selected.txt" \
>> "$GITHUB_OUTPUT"

# 出力契約の検証と可観測ログ。**この step には continue-on-error を付けない**
# (ADR-072 決定 14、#370 CodeRabbit 指摘)。
#
# Select task は exit 2 (台帳破損) / exit 3 (該当タスク無し) / exit 0 を区別するため
# continue-on-error が必要で、その非 success は下段の Stop step が「正常な no-op」として
# green で終える。ところが検証を Select task の**中**に置くと、exe は成功したのに出力が
# 壊れている (マーカー欠落) ケースの exit 1 も同じ continue-on-error に飲まれ、
# 「出力契約違反」が「今夜は何も無かった」に化ける。検証を別 step に切り出し
# continue-on-error を付けないことで、この失敗を job の red として顕在化させる。
#
# 生の summary/target_files/caution はログへ出さない。ログへ出すのはマーカー行
# (rank/branch/ledger のみ = 構造的に安全) と screening 済みの summary_display だけ。
- name: Validate and echo the task-selection output contract
if: steps.select.outcome == 'success'
run: |
set -euo pipefail
if ! grep -qE '^\[NIGHTLY_TASK\]' "$RUNNER_TEMP/selected.txt" \
|| ! grep -qE '^summary_display=' "$RUNNER_TEMP/selected.txt"; then
echo "::error::output-contract violation: cli-nightly-task-select の出力に [NIGHTLY_TASK] または summary_display= が見つかりません (出力フォーマットのドリフトの可能性)。台帳選択は成功しているため no-op ではなく job を止めます。" >&2
exit 1
fi
grep -E '^\[NIGHTLY_TASK\]|^summary_display=' "$RUNNER_TEMP/selected.txt"

# 本 step は 2 つの経路で発火する — select が非ゼロだった場合と、Pre-flight gate の
# 背圧 deny で select 自体が skip された場合。後者では select の [NIGHTLY_SKIP] 行が
# 存在しないため、「直前の行を見よ」と一律に案内すると誤誘導になる。経路で出し分ける。
Expand Down
11 changes: 9 additions & 2 deletions docs/adr/adr-072-nightly-todo-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,16 @@ pre-push simplicity review はここを「他の停止点と同様に graceful d

**draft PR でも public repository では第三者に可視**であり、台帳の自由記述がそのまま公開面へ出ていた (順位 381)。

公開面の棚卸し結果、台帳由来で外部可視になるのは **PR 本文の `内容` だけ**だった。`RANK` は `u32` にパース済み、ブランチ名は `format!("claude/nightly-{rank}")` で、どちらも**構造的に安全**である。
公開面の棚卸し結果、台帳由来で外部可視になるのは **PR 本文の `内容`** と **step ログ**の 2 つだった。`RANK` は `u32` にパース済み、ブランチ名は `format!("claude/nightly-{rank}")` で、どちらも**構造的に安全**である。

`cli-nightly-task-select` に `summary_display` 出力を足し、workflow はそれを**インラインコードスパンで囲んで**出す。コードスパンの内側では markdown が描画されず `@mention` の通知も飛ばないため、注入の効果がそこで消える。したがって screening の主眼は **「コードスパンから抜け出せる文字を残さないこと」**に絞り、バッククォートの置換・制御文字の除去・200 文字での切り詰めだけを行う。`@` は書き換えない — 無害化はコードスパンの役目で、`@` を潰すと正当なタスク記述が読めなくなる。
**初版は「公開面 = PR 本文」と狭く見ており、step ログを見落としていた** (#369 post-merge feedback が指摘)。`Select task` step は exe 出力を `tee` で `selected.txt` と**画面 (= Actions ログ) の両方**へ出しており、そこに生の `summary` / `target_files` / `caution` 行が含まれていた。**public repo では step ログも第三者に可視**なので、これは screening を迂回する 2 つ目の公開面だった。`tee` をリダイレクト (`> selected.txt`) に変え、ログへはマーカー行 (rank/branch/ledger のみ = 安全) と screening 済みの `summary_display` だけを `grep` で出す形にした。生の出力はファイルに留まり `$GITHUB_OUTPUT` 経由でのみ使われる。**「公開面」は出力先を 1 つ塞ぐたびに次が見つかる**ので、棚卸しは「PR 本文」で止めず経路単位で行う。
Comment on lines +284 to +286

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

step ログと PR 本文の保護方法を分けて記述してください。

.github/workflows/nightly-todo.yml の Lines 162-164 は summary_displaysummary_display= の後ろに直接ログ出力します。インラインコードスパンは同ファイルの Lines 486-490 の PR 本文でのみ使用します。

したがって、続く説明では、step ログは制御文字除去・1 行化・固定プレフィックスで保護し、PR 本文はインラインコードスパンで保護すると公開面ごとに明記してください。

🤖 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 284 - 286,
ADR-072のstepログとPR本文の保護方法を分けて記述してください。stepログについては、summary_displayを制御文字除去・1行化したうえで固定プレフィックス付きで出力する方法を明記し、PR本文についてはインラインコードスパンで保護する方法を明記してください。


`cli-nightly-task-select` に `summary_display` 出力を足す。**2 つの公開面は保護方法が異なる**:

- **PR 本文**: `summary_display` を**インラインコードスパンで囲んで**出す。コードスパンの内側では markdown が描画されず `@mention` の通知も飛ばないため、注入の効果がそこで消える。したがって screening の主眼は **「コードスパンから抜け出せる文字を残さないこと」**に絞り、バッククォートの置換・制御文字の除去・200 文字での切り詰めだけを行う。`@` は書き換えない — 無害化はコードスパンの役目で、`@` を潰すと正当なタスク記述が読めなくなる。
- **step ログ**: コードスパンは使えない (Actions ログは markdown 描画しない plain text)。代わりに **screening 済みの `summary_display` を固定プレフィックス `summary_display=` 付きの 1 行**として出す。screening が制御文字を除去し 1 行化しているため、ログ行を割って別の出力に見せかける経路が塞がる。生の `summary` はここには出さない (§ 実走スモークで塞いだ `tee` 露出)。

`summary_display` の screening 処理そのもの (バッククォート置換・制御/不可視文字除去・切り詰め) は両公開面で共通だが、**その値をどう囲むか**が公開面ごとに違う。

**agent プロンプト側はこの screening を通さない。** あちらが必要とするのは完全なタスク記述で、遮断の責務は決定 12 / 13 が持つ。**同じ文字列でも出口ごとに必要な処理が違う**ため、「安全な summary」1 本に統一していない。screening を Rust に置いたのは、順位 382 の injection payload 回帰テストが固定する対象を作るためでもある (shell に置くとテストの場が無い)。

Expand Down
Loading