diff --git a/.github/workflows/nightly-todo.yml b/.github/workflows/nightly-todo.yml index ef817ef0..4c018d06 100644 --- a/.github/workflows/nightly-todo.yml +++ b/.github/workflows/nightly-todo.yml @@ -292,7 +292,7 @@ jobs: 制約 (逸脱すると後続の決定論ゲートが push を拒否します): - **自律動作のガードレール自体を変更しないこと**: `autonomy-config.toml`、 `.github/workflows/**`、`src/cli-autonomy-gate/**`、`src/lib-autonomy-policy/**`、 - `src/cli-fix-push-gate/**`、`src/cli-nightly-task-select/**` + `src/cli-fix-push-gate/**`、`src/cli-nightly-task-select/**`、`src/lib-ledger/**` - コミット・push・ブランチ操作・PR 操作は行わないこと (あなたの権限外) - タスクに関係しないファイルを整形・修正しないこと # tool scope (ADR-072 決定 12、順位 379)。 @@ -402,7 +402,7 @@ jobs: # 決める仕組みであり、config や gate exe と同じく **自分を縛る側**にある。agent が # 台帳を書き換えた diff が PR に紛れ、人間が見落としてマージすると、以後の # タスク選択が静かに汚染される (ADR-072 決定 6)。 - if grep -Eq '^(autonomy-config\.toml|\.github/workflows/|docs/claude-code-web-tasks\.md|src/cli-autonomy-gate/|src/lib-autonomy-policy/|src/cli-fix-push-gate/|src/cli-nightly-task-select/)' \ + if grep -Eq '^(autonomy-config\.toml|\.github/workflows/|docs/claude-code-web-tasks\.md|src/cli-autonomy-gate/|src/lib-autonomy-policy/|src/cli-fix-push-gate/|src/cli-nightly-task-select/|src/lib-ledger/)' \ "$RUNNER_TEMP/changed.txt"; then echo "[NIGHTLY_DENY] 自律動作のガードレールを変更しているため push しません" exit 1 diff --git a/Cargo.lock b/Cargo.lock index 1372b85d..980eaaff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,6 +172,9 @@ dependencies = [ [[package]] name = "cli-nightly-task-select" version = "0.1.0" +dependencies = [ + "lib-ledger", +] [[package]] name = "cli-pr-monitor" @@ -792,6 +795,10 @@ dependencies = [ "tempfile", ] +[[package]] +name = "lib-ledger" +version = "0.1.0" + [[package]] name = "lib-ollama-client" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 0671b32f..08f1dd48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ members = [ "src/lib-docs-policy", "src/lib-hook-output", "src/lib-jj-helpers", + "src/lib-ledger", "src/lib-ollama-client", "src/lib-pending-file", "src/lib-report-formatter", diff --git a/docs/adr/adr-072-nightly-todo-loop.md b/docs/adr/adr-072-nightly-todo-loop.md index a2bed428..a6e6707d 100644 --- a/docs/adr/adr-072-nightly-todo-loop.md +++ b/docs/adr/adr-072-nightly-todo-loop.md @@ -110,11 +110,13 @@ security review の主張は「**`--allowedTools` の `Bash(cmd:*)` は文字列 draft PR は commitment 点の手前なので人間のレビューが最終防衛になるが、「自分を縛る仕組みを自分で書き換えた diff」だけは例外とし、決定論的な path 禁止リストで push を止める。 -対象: `autonomy-config.toml` / `.github/workflows/**` / **`docs/claude-code-web-tasks.md`** / `src/cli-autonomy-gate/**` / `src/lib-autonomy-policy/**` / `src/cli-fix-push-gate/**` / `src/cli-nightly-task-select/**`。 +対象: `autonomy-config.toml` / `.github/workflows/**` / **`docs/claude-code-web-tasks.md`** / `src/cli-autonomy-gate/**` / `src/lib-autonomy-policy/**` / `src/cli-fix-push-gate/**` / `src/cli-nightly-task-select/**` / `src/lib-ledger/**`。 + +**`src/lib-ledger/**` は 2026-08-14 に追加した。** 台帳パーサを `cli-nightly-task-select` から共有 lib へ抽出した際、パースの実体だけが禁止リストの外へ出た。exe 名で列挙していたため、**中身を別 crate へ動かすと保護が自動では追随しない**。禁止リストは「どの exe か」ではなく「どのロジックが自分を縛るか」で決まる — 台帳の解釈は agent が読む指示そのものを組み立てる位置にあり、exe 側に残った CLI 面と同じ保護が要る。 **台帳 (`docs/claude-code-web-tasks.md`) を含めるのは、それが「次に何を実装するか」を決める仕組みだから。** config や gate exe と同じく*自分を縛る側*にある。agent が台帳を書き換えた diff が draft PR に紛れ、人間が見落としてマージすると、以後のタスク選択が静かに汚染される。初版はここを落としていた (§ 静的レビューが捕捉した件)。 -**列挙の基準は「危険か」ではなく「将来の無人 run のゲートを緩めるか」である。** security review は `Cargo.toml` / `Cargo.lock` の欠落を warning として挙げたが(agent が追加した build-script 依存が Guard を素通りする)、**採らない** — これらは通常の diff であり、人間の PR レビューとマージという既存の防衛線が効く。一方リストに載せた 7 つは、書き換わると*それ以降の run*がゲートを経ずに走るようになる。この基準を持たないと禁止リストは「怪しいもの全部」へ膨らみ、正当なタスクを弾き始める。 +**列挙の基準は「危険か」ではなく「将来の無人 run のゲートを緩めるか」である。** security review は `Cargo.toml` / `Cargo.lock` の欠落を warning として挙げたが(agent が追加した build-script 依存が Guard を素通りする)、**採らない** — これらは通常の diff であり、人間の PR レビューとマージという既存の防衛線が効く。一方リストに載せた 8 つは、書き換わると*それ以降の run*がゲートを経ずに走るようになる。この基準を持たないと禁止リストは「怪しいもの全部」へ膨らみ、正当なタスクを弾き始める。 **許可リストではなく禁止リストにした**のは、台帳の「対象ファイル」列が自由記述の markdown (バッククォート・括弧・複数パスの列挙) で、path allowlist に落とすと正当なタスクまで弾くため。allowlist 化は台帳を機械可読にしてからの課題とする (§ 残課題)。 diff --git a/docs/todo20.md b/docs/todo20.md index 3caea2f2..d18b293a 100644 --- a/docs/todo20.md +++ b/docs/todo20.md @@ -428,7 +428,7 @@ > **動機**: `is_table_row` は行頭 `|` を要求するのに対し、`is_separator_row` は `split_cells` の結果だけを見るため**パイプを 1 つも含まない行が通る**。`split_cells("---")` は `["---"]` を返し、全セルが `-` のみなので真になる。 > -> **2026-08-07 に実コードで確認済み** ([ledger.rs:262-272](../src/cli-nightly-task-select/src/ledger.rs#L262-L272))。markdown の水平線 `---` は本 todo ファイル自身が使っており、台帳に現れうる。表の直前に水平線があると、それをセパレータ行と誤認して表構造の解釈がずれる。 +> **2026-08-07 に実コードで確認済み** ([lib.rs:357-363](../src/lib-ledger/src/lib.rs#L357-L363))。markdown の水平線 `---` は本 todo ファイル自身が使っており、台帳に現れうる。表の直前に水平線があると、それをセパレータ行と誤認して表構造の解釈がずれる。 > > [ADR-072](adr/adr-072-nightly-todo-loop.md) 決定 2 が「台帳の曖昧さはすべて停止側へ」と定めた fail-closed 設計の coverage hole にあたる。 > diff --git a/docs/todo22.md b/docs/todo22.md index f3fa2bb8..ba64c79b 100644 --- a/docs/todo22.md +++ b/docs/todo22.md @@ -50,7 +50,7 @@ > > **対処案**: (a) 「新しい出力面を足すときは、既存 screening を流用してよいかを**囲いの有無**から判断する」を convention として明文化、(b) [ADR-054](adr/adr-054-prompt-injection-trust-boundary-defense.md) へ **output surface × wrapping context の対応表**を追記する (どの出口がどんな囲いを持ち、それゆえ何を追加処理すべきか)。 > -> **参照**: [ADR-054](adr/adr-054-prompt-injection-trust-boundary-defense.md)、[ADR-072](adr/adr-072-nightly-todo-loop.md) 決定 14 § 3 つ目の公開面、[screening.rs](../src/cli-nightly-task-select/src/ledger/screening.rs) (2 関数の対照が実装済み)。 +> **参照**: [ADR-054](adr/adr-054-prompt-injection-trust-boundary-defense.md)、[ADR-072](adr/adr-072-nightly-todo-loop.md) 決定 14 § 3 つ目の公開面、[screening.rs](../src/lib-ledger/src/screening.rs) (2 関数の対照が実装済み)。 > > **実行優先度**: 🚀 Tier 1 — Severity Medium / Frequency **High** (出力面は増え続ける) / Effort S / Adoption Risk None。 @@ -363,7 +363,7 @@ > > **対処案**: 上記を `docs/dev-conventions.md` へ明記する。表パーサに optional 列を足すときのチェック項目 (列数検証への反映) も併記する。 > -> **参照**: [ledger.rs](../src/cli-nightly-task-select/src/ledger.rs) (`max_index` の doc に教訓を記録済み)、[ADR-072](adr/adr-072-nightly-todo-loop.md) 決定 17。 +> **参照**: [lib.rs](../src/lib-ledger/src/lib.rs) (`max_index` の doc に教訓を記録済み)、[ADR-072](adr/adr-072-nightly-todo-loop.md) 決定 17。 > > **実行優先度**: 💎 Tier 3 — Severity Low (テストで捕捉済み) / Frequency Medium (今後の列追加で再発見込み) / Effort XS / Adoption Risk None。 diff --git a/docs/todo23.md b/docs/todo23.md index 93a84257..424982e2 100644 --- a/docs/todo23.md +++ b/docs/todo23.md @@ -39,7 +39,7 @@ 実装先の候補が 2 つあり、着手時に選ぶ (両方入れる選択もある): - **(a) custom lint rule** (`.claude/custom-lint-rules.toml`、`paths=["docs/claude-code-web-tasks.md"]`) — 台帳を編集した人へ即時フィードバック。既存 12 rule の確立パターンに乗る -- **(b) 台帳パーサの fail-closed 検査** (`src/cli-nightly-task-select/src/ledger.rs`) — 夜間ループがタスクを選ぶ瞬間に停止する。同 module の設計方針「曖昧さはすべて停止側へ」および [ADR-043](adr/adr-043-security-gates-fail-closed.md) と整合し、**マークが誤っていても自動実行に到達しない** +- **(b) 台帳パーサの fail-closed 検査** (`src/lib-ledger/src/lib.rs`) — 夜間ループがタスクを選ぶ瞬間に停止する。同 crate の設計方針「曖昧さはすべて停止側へ」および [ADR-043](adr/adr-043-security-gates-fail-closed.md) と整合し、**マークが誤っていても自動実行に到達しない** (a) は書き手への予防、(b) は自動実行の直前での遮断で、守る対象が違う。 diff --git a/src/cli-nightly-task-select/Cargo.toml b/src/cli-nightly-task-select/Cargo.toml index 2b45fcb8..3391ca52 100644 --- a/src/cli-nightly-task-select/Cargo.toml +++ b/src/cli-nightly-task-select/Cargo.toml @@ -7,8 +7,13 @@ edition = "2021" name = "cli-nightly-task-select" path = "src/main.rs" -# 依存なし。台帳の markdown table を読むだけで、外部 crate を必要としない。 -# 依存を足さないこと自体が設計上の制約 — 本 exe は夜間ループの唯一の「何を実装するか」 -# 決定者であり、供給元が増えるほど無人経路の攻撃面が広がる。 +[dependencies] +# 台帳パーサは lib-ledger に置く (ADR-024 共通 lib)。cli-* crate から別の cli-* crate を +# 直接呼ばない方針のため、台帳の後始末を行う cli-ledger-cleanup と共有するパースロジックは +# lib 側に持つ。 +lib-ledger = { path = "../lib-ledger" } + +# 外部 crate 依存は足さない。依存を足さないこと自体が設計上の制約 — 本 exe は夜間ループの +# 唯一の「何を実装するか」決定者であり、供給元が増えるほど無人経路の攻撃面が広がる。 # [profile.release] は workspace root (Cargo.toml) に集約 (ADR-026) diff --git a/src/cli-nightly-task-select/src/main.rs b/src/cli-nightly-task-select/src/main.rs index 4c78a795..37a32fed 100644 --- a/src/cli-nightly-task-select/src/main.rs +++ b/src/cli-nightly-task-select/src/main.rs @@ -2,7 +2,7 @@ //! //! 台帳 (`docs/claude-code-web-tasks.md`) の「無人可」マークが付いた行から 1 件を決定論的に //! 選び、夜間 workflow の後続 step が使う値 (順位・ブランチ名・対象ファイル・指示文) を -//! `GITHUB_OUTPUT` 形式で出す。選択そのものは [`ledger`] が持ち、本 exe は CLI 面 +//! `GITHUB_OUTPUT` 形式で出す。選択そのものは `lib-ledger` crate が持ち、本 exe は CLI 面 //! (引数解析・loud 出力・exit コード) だけを担う。 //! //! # 使い方 @@ -32,12 +32,10 @@ //! 選択の有無にかかわらず loud に出す (無音 no-op 禁止、ADR-064 と同じ論理)。選択は stdout の //! `[NIGHTLY_TASK]`、no-op と失敗は stderr の `[NIGHTLY_SKIP]`。 -mod ledger; - use std::collections::BTreeSet; use std::path::PathBuf; -use ledger::{screen_for_public_output, screen_for_title, Task}; +use lib_ledger::{screen_for_public_output, screen_for_title, Task}; const MARKER_SELECTED: &str = "[NIGHTLY_TASK]"; const MARKER_SKIP: &str = "[NIGHTLY_SKIP]"; @@ -116,7 +114,7 @@ fn run(args: Vec) -> i32 { ) } }; - match ledger::select(&markdown, &cli.excluded_ranks) { + match lib_ledger::select(&markdown, &cli.excluded_ranks) { Err(message) => skip( EXIT_USAGE, &format!("台帳を解釈できません ({display}): {message}"), diff --git a/src/lib-ledger/Cargo.toml b/src/lib-ledger/Cargo.toml new file mode 100644 index 00000000..9b3fb299 --- /dev/null +++ b/src/lib-ledger/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "lib-ledger" +version = "0.1.0" +edition = "2021" + +[lib] +name = "lib_ledger" +path = "src/lib.rs" + +[dependencies] +# 外部 crate 依存を持たない。本 crate は夜間ループが「何を実装してよいか」を決める +# 台帳の解釈を担い、無人経路の入口にあたる。供給元が増えるほど攻撃面が広がるため、 +# 依存を足さないこと自体が設計上の制約 (移設元 cli-nightly-task-select から継承)。 + +# [profile.release] は workspace root (Cargo.toml) に集約 (ADR-026) diff --git a/src/cli-nightly-task-select/src/ledger.rs b/src/lib-ledger/src/lib.rs similarity index 100% rename from src/cli-nightly-task-select/src/ledger.rs rename to src/lib-ledger/src/lib.rs diff --git a/src/cli-nightly-task-select/src/ledger/screening.rs b/src/lib-ledger/src/screening.rs similarity index 100% rename from src/cli-nightly-task-select/src/ledger/screening.rs rename to src/lib-ledger/src/screening.rs