fix(PLAN64): 機密の参照の見出しにグループの読み替えの前後を出す (#188) - #237
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SecretRef.label() にキーワード引数 group_display を足し、見出し用の表示を作る口を SecretStore.display_label(ref) の 1 つに置いた。読み替えの要否は storage_group が 決める (backend の種類・設定の有無・layout・グループの有無をまとめて見る唯一の判定)。 label() の既定の返り値は変えていない。読み替えの解決は BackendConfigError を送出 しうるため、43 か所のエラー文言・警告・ログを巻き込まない。 見出しの呼び出し 5 か所 (env list の節の見出しと件数の行・env backend test の参照 ごとの行・env backend migrate の計画の一覧と --to age の完了後の一覧) を display_label へ寄せた。読み替えの対応が無いグループ・version: 1・ファイル backend の出力と、 env backend status の表示は変わらない。 確定仕様の相反する 2 つの記述を、文言の種類で分ける形に書き分けた。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add characterization tests for current-group probe headings, skipped project aliases, and grouped references on a flat layout. Item-Id: R1-001 Round: 1 Impl-Runtime: codex Impl-Model: default
改修計画 — devbasex/devbase #237
ラウンド 1(実装 codex / レビュー agy / kiro)R1-001 —
|
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| branch | integration | — | agy / kiro | 採用 | 1 |
なぜ: cmd_env_backend_test の見出し表示テストにおいて、プローブ対象となる 4 種の参照のうち、チーム共通・個人共通・チームプロジェクトの 3 つしか機密が置かれておらず、個人プロジェクト参照(個人のプロジェクト 'web'(グループ default → nyle))の行が出力される分岐が固定されていない。
手順: 1. aliased フィクスチャ (version:2, default→nyle) に、宣言なしのプロジェクト api と with 宣言のプロジェクト web を用意する
2. openbao へ team/nyle/global と team/with/projects/web など、両グループの参照を put する
3. cwd を projects/web (グループ with) に置いて cmd_env_backend_test を呼ぶ
4. 戻り値が 0 で、調べた参照の見出しに web (with の置き場) は現れ、別グループの api は「読めた参照」側に現れないことを確認する
5. api が調査対象外として名指しされる出力に、読み替え後のグループ名 (nyle) が添えられていることを確認する(完全一致ではなくキーワードで観測する)
R1-002 — lib/devbase/commands/env_backend.py#cmd_env_backend_migrate
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| branch | integration | — | agy | 採用 | 1 |
なぜ: cmd_env_backend_migrate の移行計画一覧表示のテストにおいて、移行元にグローバル機密のみが置かれており、プロジェクトの機密が存在する場合にプロジェクト参照の見出し(プロジェクト 'web'(グループ default → nyle))が一覧に出力される分岐が固定されていない。
手順: 1. 移行元の OpenBao にプロジェクトの機密(team/nyle/projects/web)を配置する
2. cmd_env_backend_migrate(to='age', dry_run=True) を実行する
3. 計画一覧の出力に 'プロジェクト 'web'(グループ default → nyle)' とサーバ上のパスが含まれることを検証する
R1-003 — lib/devbase/commands/env_backend.py#cmd_env_backend_migrate
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| normal | integration | — | codex | 採用 | 1 |
なぜ: 既存の test_migration_plan_listing_shows_both_names は OpenBao から age への方向だけで読み替え前後の見出しを検証している。逆方向の test_grouped_dry_run_without_conflicts_writes_nothing は移行先パスと書き込みの不在を検証するが、見出しとの対応を固定していない。現在の backend が age でも、移行先 OpenBao の設定を使って default → nyle と移行先パスを同じ行へ表示する接続部分を、公開コマンドから固定する。単なる表示関数の分岐ではなく、ファイル側とサーバ側で異なる設定・参照を組み合わせるため結合テストとする。
手順: 1. 既存の aliased fixture の OpenBao 設定(version 2、default から nyle への読み替え)を保持して backend を age にし、グループ未宣言の web プロジェクトのローカル age 参照に既知のキーと値を保存する。移行先の対応参照は空にする。
2. backend.yml と移行元の暗号文を控え、cmd_env_backend_migrate(root, to="openbao", dry_run=True) を実行し、戻り値と標準出力を観測する。
3. 観測した現状を期待値にして、戻り値 0 と、web の移行予定行に default → nyle・devbase/team/nyle/projects/web・保存したキー名が共存することを部分一致で固定する。表示全体や空白による桁揃えは比較しない。
4. 秘密値が出力に含まれないこと、移行元の暗号文と backend.yml が不変であること、移行先の保存状態が空のままであることを確認する。内部関数や内部呼び出し回数は検証しない。
R1-004 — lib/devbase/env/secret_store.py#SecretStore.display_label
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| branch | unit | — | agy | 採用 | 1 |
なぜ: layout: flat の設定においてグループを持つ参照が渡された場合の分岐が未固定である。既存の test_display_label_does_not_map_on_a_flat_layout ではグループを持たない SecretRef.for_global() のみが渡されており、ref.group is None の判定で早期脱出するため settings.grouped が偽であることによる分岐が検証されていない。
手順: 1. layout: flat を設定した SecretStore を用意する
2. グループを持つ参照(SecretRef.for_global(group='default'))を display_label に渡す
3. 戻り値がグループ読み替えを含まない元の表示('グローバル(グループ default)')と一致することを検証する
R1-005 — lib/devbase/env/secret_store.py#SecretStore.display_label
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| normal | unit | — | agy | 採用 | 1 |
なぜ: SecretStore.display_label の単体テストではグローバル参照(kind='global')のみが対象となっており、プロジェクト参照(kind='project')および個人プロジェクト参照を渡したときにプロジェクト名と読み替え後のグループ名を含む表示が返る代表的な正常系が単体テストとして固定されていない。
手順: 1. layout: group かつ group_aliases を持つ SecretStore を用意する
2. プロジェクト参照(SecretRef.for_project('web', group='default'))および個人プロジェクト参照(owner='user')を display_label に渡す
3. 戻り値がそれぞれのプロジェクト表示に読み替え後のグループ名が付与された文字列になることを比較する
ラウンド 2(実装 agy / レビュー codex / kiro)
R2-001 — lib/devbase/env/secret_store.py#SecretStore.mode
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| duplication | consolidate_duplication | major | agy | 検証中 | 1 |
なぜ: SecretStore.mode は backend の選択状態の確認、暗号化・平文の存在判定、および両方存在する場合の衝突検出を SecretStore.backend_for と全く同じ形で重複して実装している(衝突時には自らエラーを送出せず backend_for を呼び出している)。backend_for(ref) を呼んで backend.name if backend.exists(ref) else MODE_ABSENT を返す形に共通化することで、backend 解決と存在確認の責務が 1 箇所に集約され重複が解消する。
手順: 1. SecretStore.mode を backend = self.backend_for(ref) を呼び出す形に変更する
2. backend.exists(ref) が真なら backend.name、偽なら MODE_ABSENT を返すように整理する
3. mode 内の重複していた _selected_backend() 判定および age.exists / plaintext.exists の個別判定を削除する
R2-002 — lib/devbase/commands/env_backend.py#_MigrationPlan.apply
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | major | agy | 検証中 | 1 |
なぜ: _MigrationPlan.apply は 1 つのメソッド内で、各保存単位への書き込み処理(ロールバック用ディクショナリの構築と dest.save)と、書き込み後の全件読み戻し検証処理(_read_back と期待値の突き合わせ・差分検出とエラー送出)の 2 つの異なる段階を通しで行っている。読み戻し検証ループを独立したメソッドへ抽出することで、書き込みと検証の関心が分離され保守性が向上する。
手順: 1. _MigrationPlan 内の読み戻し検証ループ(lines 754-763)を _verify_read_back(self) メソッドとして抽出する
2. apply の try ブロック内で、書き込みループ完了後に self._verify_read_back() を呼び出す
R2-003 — lib/devbase/commands/env_backend.py#cmd_env_backend_migrate
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | major | agy | 未着手 | 0 |
なぜ: cmd_env_backend_migrate は 115 行にわたり、CLI 引数の検証、backend 設定の検証、移行計画の作成・サマリ出力・確認プロンプト、計画適用、設定ファイルの永続化、ファイル退避および完了後レポート表示と、非常に多くの段階を 1 つの関数で担っている。特に移行完了後の結果レポート表示処理(lines 580-597: openbao 移行時の退避先案内、または age 移行時のサーバ残存機密一覧表示)を関数として抽出することで、主たる移行フローの可読性を向上させる。
手順: 1. cmd_env_backend_migrate 末尾の完了レポート表示部(lines 580-597)を _print_migration_completion(to: str, plan: _MigrationPlan, server: OpenBaoBackend, server_store: SecretStore) -> None として抽出する
2. cmd_env_backend_migrate から抽出した関数を呼び出す
R2-004 — lib/devbase/commands/env_backend.py#_MigrationPlan
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| conditional_chain | replace_conditional_with_polymorphism | major | kiro | 未着手 | 0 |
なぜ: 移行の向き (self.to == _bc.BACKEND_OPENBAO / self.to == 'age') の分岐が _source_side・_dest_side・_read_back・_units・_rollback・cmd_env_backend_migrate の完了処理と 6 箇所以上で繰り返される。向きを増やす・変えると全箇所を同時に直す必要があり、source/dest の取り違えが起きやすい。
手順: 1. 向きごとの差 (source 側/dest 側の backend と ref の選び方、read_back、rollback の消し方、完了時の後処理) を担う小さな向きオブジェクト (例: _ToOpenBao / _ToAge) を定義する
2. _MigrationPlan が to 文字列から向きオブジェクトを 1 度だけ生成して保持する
3. _source_side / _dest_side / _read_back / _rollback / _units 内の self.to 分岐を向きオブジェクトへの委譲に置き換える
4. cmd_env_backend_migrate 末尾の to による後処理分岐も向きオブジェクトへ寄せる
5. tests/commands の migrate 系テストで両方向の挙動 (計画・適用・rollback・退避) が不変であることを確認
見送った項目
| ラウンド | 対象 | 兆候・経路 | 理由 |
|---|---|---|---|
| 2 | lib/devbase/commands/env.py#_project_group_mismatch |
duplication | 重要度 minor がしきい値 major 未満 |
| 2 | lib/devbase/env/secret_store.py#PlaintextBackend.remove |
duplication | 重要度 minor がしきい値 major 未満 |
| 2 | lib/devbase/commands/env.py#cmd_env_list |
duplication | 重要度 minor がしきい値 major 未満 |
…ay_label Add characterization tests for the project heading in the migration plan listing and for project / personal-project references passed to SecretStore.display_label. Item-Id: R1-002 Item-Id: R1-005 Round: 1 Impl-Runtime: agy Impl-Model: default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Vu8hNTZKeDeXVLg8hYuK8
Fix the headings of the reverse direction (age -> openbao) of `env backend migrate --dry-run`, where the aliased group name and the destination path have to name the same group on one line. Item-Id: R1-003 Round: 1 Impl-Runtime: claude Impl-Model: claude-opus-5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Vu8hNTZKeDeXVLg8hYuK8
…ecretStore.mode SecretStore.mode が backend_for と同じ backend の選択・存在判定・衝突検出を 重複して持っていたため、backend_for の結果の exists で判定する形にまとめた。 あわせて extract_method — lib/devbase/commands/env_backend.py#_MigrationPlan.apply: 書き込み後の読み戻し検証ループを _verify_read_back へ抽出した (R2-002)。 Item-Id: R2-001 Round: 2 Impl-Runtime: claude Impl-Model: default Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ore.py#SecretStore.mode" This reverts commit cd75e0e. 構造改善の提案 R2-001 / R2-002 は、この Pull Request の差分の外 (`SecretStore.mode` と `_MigrationPlan.apply` の読み戻し検証) を指していた。 R2-001 は `mode` の分岐を `backend_for` へ寄せており、見出しの表示だけを変える この変更の範囲を越えて振る舞いに触れうる。範囲外として取り消す。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Vu8hNTZKeDeXVLg8hYuK8
構造改善(cross-refactoring)の記録
テスト整備ラウンド 1(提案 6 → 統合 5 → 採用 5 / 見送り 0)現状固定のテストだけを足し、
構造改善の提案ラウンド 1(提案 7 → 統合 7 → 採用 4 / 見送り 3)採用された 4 件はすべてこの Pull Request の差分の外を指していたため、進行側が却下した。
R2-001 / R2-002 は適用まで進んでいたため 結果構造改善として
|
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 1 | kiro | APPROVE
見出し用の表示を SecretStore.display_label の 1 か所に集約し、5 つの呼び出し(env list の節と件数・env backend test・migrate の計画一覧と --to age 完了表示)を寄せる変更は、設計 (#223) と整合し過不足がない。重点確認項目を検証した結果、修正を要する箇所は見つからなかった。
display_labelの分岐(storage_group is None/storage == ref.group)は「変わらないこと」1・2 を覆っている。version: 1・flat・エイリアス無しグループ(kkg/with)はref.label()をそのまま返し、backend 種別はstorage_group経由で判定されconfig.openbao is None判定を避けている。_group_suffixの前提(display_label(ref)の前方がreplace(ref, group=None).label()と一致)は、label()がtextに(グループ …)を後置する構造上、全 kind(global / project / user)で成立する。label()を残す 43 箇所(エラー文言・警告・ログ)とencrypt/decrypt/rekeyの一覧(ref.groupが常にNone)は見出し置換の対象外で、寄せ忘れは無い。display_labelはグループ検証済みの ref にのみ到達するため、新たなBackendConfigError送出面も増えていない。- 追加テストは「
(グループが 1 つも出ない」等を前方一致ではなく検証しており、受け入れ条件を満たす。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 2 | codex | APPROVE
修正が必要な指摘はありません(関連テスト 201 件通過)。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 3 | agy | APPROVE
重点確認項目(見出し 5 箇所の網羅性、SecretStore.display_label の分岐、_group_suffix の前提整合性、確定仕様の書き分け、非回帰テストの検証精度)を確認し、修正を要する問題はありません。
Summary
group_aliasesのある端末で、機密の参照の見出しに出るグループ名が読み替えの前(default)だけになり、隣に並ぶ置き場のパス(nyle)と食い違って見える件を直す。SecretRef.label()にキーワード引数group_displayを足す。文言の組み立て(括弧・個人のの接頭・チーム単位の文字列)はlabel()の中から出さないlabel()の既定の返り値は変えない。読み替えの解決はBackendConfigErrorを送出しうるため、エラー文言・警告・ログ(43 か所)を巻き込まないSecretStore.display_label(ref)の 1 つだけ。backend の種類の判定はSecretStore.storage_groupに任せる(config.openbaoのNone判定はしない)env listの 2 か所 /env backend test/env backend migrateの計画の一覧と--to ageの完了後の一覧)をdisplay_labelへ寄せるdocs/specifications/secret-backend.mdの相反する 2 つの記述を「文言の種類で分ける」形で解く設計:
issues/PLAN64_secret-label-group-design.md/ 要求:issues/PLAN64_secret-label-group.md設計 Pull Request: #223 / release Pull Request: #212
Closes #188
やらないこと
label()のまま)env backend statusの表示(すでにdisplay_groupを使っている)env encrypt/env decrypt/env rekeyの一覧(ref.groupが常にNone):<28)の変更実装したもの
env listの節の見出しcmd_env_list(lib/devbase/commands/env.py)env listの件数の行・プロジェクトの節_group_suffix(同。storeを受ける形にした)env backend testの参照ごとの行cmd_env_backend_test(lib/devbase/commands/env_backend.py)env backend migrateの計画の一覧_MigrationPlan._heading(同)env backend migrate --to ageの完了後の一覧cmd_env_backend_migrateの完了表示(同)既存のテストは 1 行も変えていない。
lib/の差分は 3 ファイル・53 行で、残りはテストと文書である。構造改善(cross-refactoring)で足した現状固定テスト
テスト整備ラウンドで 5 件を採用し、3 コミットで取り込んだ(
39a455d/b086bf0/3b4f743)。lib/は 1 行も触っていない。env backend testの 4 種の参照の見出しと、対象外のグループのプロジェクトを飛ばす行layout: flatでグループ付きの参照をdisplay_labelに渡したときの分岐display_label構造改善の提案ラウンドで採用された 4 件はいずれもこの Pull Request の差分の外を指していたため却下し、
適用済みだった 2 件は
34af220で取り消した(差し引きの差分は空)。内訳は#237 (comment) にある。
実装レビュー(cross-review)
3 ラウンド回し、
kiro/codex/agyの 3 者がいずれも指摘 0 件で APPROVE。未解決のレビュースレッドは 0 件。Test plan
release/v3.7.0を base にした Pull Request では CI が 1 件も動かないため(#216)、手元で実行した。合否は終了コードで判定している。
uv run --locked pytest tests/env/test_secret_store_label.py tests/commands/test_env_group_label.py tests/env/test_groups.py tests/env/test_runtime.py tests/commands/test_env_user_axis.py tests/commands/test_env_backend.py -quv run --locked pytest tests/ -quvx ruff check --select=E9,F63,F7,F82 lib testslibとtestsuv run --locked python -m compileall -q lib testslibとtestspython -m devbase.cli env list -k(隔離したDEVBASE_ROOT)version: 1とファイル backend=== グローバル (/=== プロジェクト: web (。(グループは出ない / exit=0python -m devbase.cli env backend status(隔離したDEVBASE_ROOT)layout: group+group_aliases: {default: nyle}グループ: default → nyleのまま / exit=0カバレッジは
pyproject.tomlにfail_underの記載が無いため、閾値による判定を行わない。受け入れ条件
env backend testの見出し)→test_backend_test_headings_show_both_names_next_to_the_pathenv listの見出しと件数の行)→test_list_headings_and_counts_show_both_namesmigrateの 2 つの一覧)→test_migration_plan_listing_shows_both_names/test_completion_listing_after_migrating_to_age_shows_both_names(逆向きはtest_migration_plan_listing_to_openbao_shows_both_names_next_to_the_path)test_a_group_without_an_alias_keeps_its_nameversion: 1)→test_version_one_shows_no_group_at_all。既存のtests/env/test_groups.py/tests/env/test_runtime.pyは変更なしで通る。隔離した
DEVBASE_ROOTで実際の CLI を打っても見出しにグループは出ないopenbao:節を残したbackend: age)→test_a_file_backend_with_a_leftover_openbao_section_shows_no_group。既存のtests/commands/test_env_user_axis.pyは変更なしで通る。隔離したDEVBASE_ROOTでconfig.openbao is Noneが偽になることと、見出しにグループが出ないことを実際の CLI で確かめたtest_the_flat_layout_refusal_names_the_group_before_the_alias/test_the_account_group_warning_names_the_group_before_the_aliasenv backend status)→ 既存のtests/commands/test_env_backend.pyが変更なしで通る。隔離した
DEVBASE_ROOTで実際の CLI を打ち、グループ: default → nyleのままであることを確かめたdocs/specifications/secret-backend.mdを「引数なしのlabel()で参照を表示する文言」と「
display_groupを直接呼ぶ文言」で重ならないように分けた。cross-review の 3 者が読んで指摘 0 件
listの見出しの例)→=== グローバル(グループ default → nyle) (...) ===を足したdocs/user/env-backend.mdとdocs/user/cli-reference/03-env.mdに足した未検証の項目
group_aliases: {default: nyle}、本番の OpenBao)での読み取りの手動確認。本番の系のため、この工程では隔離した
DEVBASE_ROOTと偽の設定だけで確かめた。配布の後に
devbase env backend test/devbase env list --keys-onlyを読み取りのみで打って見出しとパスが同じグループを指すことを見る(リリース後テストへ引き継ぐ)。
既存の失敗: なし
範囲外と判断したもの: なし