feat: devbase up の機密の注入で SecretStore を持ち回り、サーバ backend の往復を設計の想定へ収める (PLAN55) - #177
Conversation
…照ごとに 1 回にする (PLAN55) runtime.store_for / release_store を足し、resolve / inject / child_env と _ensure_env_files が同じ SecretStore を使う。捨てる契機は _dispatch_lifecycle の finally、TUI の委譲の入口、env init の子プロセスから戻った直後の 3 つ。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9okWVz1S7VGUsCQWVMhb3
Add characterization coverage for stale secret caches after project dispatch errors, invalid UTF-8 project env files, missing DEVBASE_ROOT, and store release after project name resolution failure. Item-Id: R1-001 Round: 1 Impl-Runtime: codex Impl-Model: default
改修計画 — devbasex/devbase #177
ラウンド 1(実装 codex / レビュー agy / kiro)R1-001 —
|
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| error | unit | — | codex / agy | 採用 | 1 |
なぜ: test_up_roundtrips.py は正常起動と env init 成功後の再読込を固定し、test_runtime_store.py は release_store 単体を固定している。一方、cmd_project の異常終了を挟んだ次の解決で、操作前の機密キャッシュが残らないことは固定されていない。対象範囲の release_store/store_for 利用箇所も検索して確認した。
手順: 1. openbao_root/openbao を使い、team/global に TOKEN=old を保存して runtime.resolve(root) で読み込む。
2. サーバ側を TOKEN=new に更新する。未知の subcommand、存在しないプロジェクト名、公開ハンドラ cmd_ps のスタブが RuntimeError を送出する場合を独立したケースにする。
3. cmd_project を公開入口として実行し、早期終了では戻り値 1、例外では RuntimeError の伝播という現状の結果を記録する。
4. release_store を明示的に呼ばず runtime.resolve(root) を実行し、TOKEN=new が得られることを固定する。ストアの同一性・内部フィールド・release_store の呼出回数は検証しない。
5. 後片付けは検証後に行い、fixture の解除処理で本体の解除漏れが隠れないようにする。
R1-002 — lib/devbase/env/runtime.py#resolve
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| error | unit | — | codex / kiro | 採用 | 1 |
なぜ: 既存テストは正常な非機密 env による上書き、環境変数にないキーの除外、四層の優先順位を固定しているが、非機密 env が不正 UTF-8 の場合の継続動作は固定していない。暗号化機密の不正 UTF-8 を検証する test_secret_store.py とは読込対象と結果が異なる。実ファイルと EnvFile のデコードをつなぐ経路として固定する。
手順: 1. 一時プロジェクトの env に TOKEN=override の有効な行と不正 UTF-8 バイトを含むデータを実際に書き、os.environ の TOKEN にも上書き候補値を設定する。
2. 既存の四層ストアの疑似実装で共通機密 TOKEN=secret とプロジェクト固有キーを与え、resolve(root, web, store=...) を公開入口として実行する。ファイル読込と EnvFile のデコードは差し替えない。
3. 例外を送出せず、非機密 env の上書きを全体として無視し、機密の values と由来別のキー集合が保たれる現状の出力を記録して固定する。有効な先頭行だけを部分適用しない点も TOKEN の値で確認する。
4. 表示・警告の完全一致や private 関数の直接呼出しは検証しない。
R1-003 — lib/devbase/commands/container.py#_inject_secrets
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| branch | unit | — | kiro | 採用 | 1 |
なぜ: _inject_secrets の DEVBASE_ROOT 未設定分岐 (root is None のとき inject を呼ばず空の SecretEnv を返す) が固定されていない。既存テストは inject をスタブ化するか required=True の再送出だけを覆い、root 未設定で早期に空を返す経路は未固定。
手順: 1. DEVBASE_ROOT を unset する
2. container._inject_secrets(required=True) を呼ぶ
3. 戻り値が偽な (空の) SecretEnv で、例外も出ないことを assert する
4. required=False でも同じく空を返すことを assert する
R1-004 — lib/devbase/commands/container.py#_inject_secrets
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| error | unit | — | kiro | 採用 | 1 |
なぜ: _inject_secrets の required=False での DevbaseError 握り潰し分岐 (warning を出して空の SecretEnv を返し、down/ps/logs を止めない) が固定されていない。required=True の再送出は test_container_up_order で固定済みだが、required=False で続行する経路は未固定。
手順: 1. DEVBASE_ROOT を一時ディレクトリへ設定する
2. runtime.inject を DevbaseError を投げるスタブへ差し替える
3. container._inject_secrets(required=False) を呼ぶ
4. 例外が伝播せず、戻り値が偽な (空の) SecretEnv であることを assert する
5. 同じ入力で required=True のときは DevbaseError が送出されることを assert し、分岐差を固定する
R1-005 — lib/devbase/commands/container.py#cmd_project
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| branch | unit | — | agy | 採用 | 1 |
なぜ: プロジェクト名解決(_resolve_project_name)に失敗して早期リターンする分岐で、finally 節により持ち回りの SecretStore が解放される経路が固定されていない
手順: 1. 事前に runtime.store_for で控えを生成した状態にする
2. 存在しないプロジェクト名を指定して cmd_project を呼び出し、戻り値が 1 となることを確認する
3. 実行後に runtime.store_for を呼び、以前の控えとは異なる新しいインスタンスが返ることを検証する
ラウンド 2(実装 agy / レビュー codex / kiro)
R2-001 — lib/devbase/commands/container.py#_resolve_project_name
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | minor | agy / kiro | 採用 | 1 |
なぜ: 1 関数が projects_dir 解決・存在確認・already_there 判定・呼び出し元 env キーの記録と chdir・PWD 差し替え・切替先との差分 unset・env 反映・COMPOSE_PROJECT_NAME 上書きを通しで行う。切替時のクリーンアップ処理 (caller_env_keys の unset) が独立した段階として名前を持てる。
手順: 1. chdir 後に呼び出し元固有の env キーを落とす部分を _unset_caller_only_env_keys(caller_keys, target_dir) として抽出
2. _resolve_project_name は解決・chdir・抽出関数の呼び出し・env 反映の順に整理する
3. tests/cli/test_project_name_resolution.py を実行して chdir/PWD/unset の振る舞いが不変であることを確認
R2-002 — lib/devbase/commands/container.py#_ensure_images
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| duplication | consolidate_duplication | major | agy | 取り消し | 1 |
なぜ: _ensure_images (L1428-1444) 内で docker compose config --format json を実行し JSON をパースして dev サービス定義を取得する処理が、同ファイル内の _resolve_dev_service (L1221-1234) と完全に重複している。
手順: 1. _ensure_images 内の docker compose config 実行と json.loads、services.get(...) 処理を _resolve_dev_service() の呼び出しに置き換える
2. _resolve_dev_service() が None を返した場合のエラーハンドリングを _run_build() へのフォールバックとして整理する
R2-003 — lib/devbase/commands/container.py#_ensure_images
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | major | kiro | 採用 | 1 |
なぜ: 1 関数が compose config の取得・JSON 解析・dev サービスと image 名の取り出し・image inspect・4 分岐 (fetch/repull/build_with_expires) を通しで行い、全体を広い try/except Exception で包む。段階に名前が付けられ、部分だけをテストできない。
手順: 1. docker compose config --format json の実行と services 取得を _read_compose_services() として抽出
2. dev サービスから image 名・has_build を取り出す部分を _dev_image_spec(services) として抽出
3. image inspect と returncode 判定を通す残りの分岐は _ensure_images に残し、抽出した関数を呼ぶ形へ書き換える
4. tests/cli/test_base_image_staleness.py / test_rebuild.py を実行し挙動不変を確認
R2-004 — lib/devbase/commands/container.py#cmd_scale
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | major | agy | 取り消し | 1 |
なぜ: cmd_scale (L1030-1113, 84行) は引数検証、設定更新、ボリューム/ネットワーク準備、compose 生成、起動、待機、フック実行、ログ出力の全段階を1つの関数内で通しで行っており、cmd_up のような実行パイプラインの分離がなされていない。
手順: 1. ボリューム・ネットワーク確認、compose 生成、docker compose up、ready 待機を行う処理を _run_scale_pipeline として抽出する
2. cmd_scale 側を引数検証、設定更新、パイプライン呼び出し、後続処理(デプロイスクリプト実行、ログ出力)に整理する
R2-005 — lib/devbase/commands/container.py#_build_resolved
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| duplication | consolidate_duplication | minor | kiro | 採用 | 1 |
なぜ: ビルド実行後の終了コード変換 return 0 if _run_build(...) else 1 が同一関数内に 5 回並ぶ (no_cache 経路・expires None 経路・compose 読取り失敗・image 名なし・inspect 失敗)。bool → プロセス終了コードへの同じ変換が散り、片方だけ書き換わると挙動が食い違う。_build_with_expires への委譲行も同型。
手順: 1. bool を終了コードへ写す小ヘルパ (例: _exit_code(ok: bool) -> int) を module 内に追加する
2. _build_resolved 内の各 return 0 if <call> else 1 を return _exit_code(<call>) に置換する
3. 既存テスト (tests/cli/test_rebuild.py / test_base_image_staleness.py) を実行して終了コードが不変であることを確認する
ラウンド 3(実装 kiro / レビュー codex / agy)
R3-001 — lib/devbase/commands/container.py#_read_compose_services
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| duplication | consolidate_duplication | minor | agy / kiro | 取り消し | 1 |
なぜ: _read_compose_services と _resolve_dev_service がどちらも docker compose config --format json を実行し stdout を json.loads して services を取り出す。コマンド文字列・失敗時の扱い・JSON パースが 2 箇所に分かれており、compose config の呼び方を変えると両方を直す必要がある。変わるときは必ず一緒に変わる同一由来の重複。
手順: 1. docker compose config --format json を実行し (returncode, services_dict) を返す 1 つの内部関数へ寄せる (現 _read_compose_services をこの形に保つ)
2. _resolve_dev_service をその関数の呼び出しへ書き換え、returncode!=0 / JSONDecodeError 時は None、成功時は services.get(get_dev_service_name(), {}) を返す薄い包みにする
3. json.JSONDecodeError の握り方を共通関数側に寄せ、両呼び出し元の挙動 (失敗時 None / (rc,{})) を現状のまま保つ
4. tests/cli/test_rebuild.py と tests/cli/test_base_image_staleness.py の該当経路を実行して現状の戻り値・分岐が不変であることを確認
R3-002 — lib/devbase/commands/container.py#cmd_login
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| duplication | consolidate_duplication | minor | agy / kiro | 採用 | 1 |
なぜ: cmd_login は _apply_context→_inject_secrets(required=False) の前処理と、.docker-compose.scale.yml の有無で -f を足す docker compose コマンド構築を _compose_run と重複して持つ。login は exec の index 指定だけが異なり、compose ファイルの付与規則と前処理は同一由来で一緒に変わる。
手順: 1. _compose_run に scale override の -f 付与規則があるので、compose ベース引数 (['docker','compose'] + 必要なら ['-f', scale_file]) を返す小さなヘルパへ切り出す
2. cmd_login はそのヘルパでベースを得た上で exec / --index の差分だけを足す形に書き換える (_apply_context + _inject_secrets の前処理もヘルパ側へ寄せられる範囲で共有)
3. scale override 有り (exec service-index) / 無し (exec --index=) の 2 経路の生成コマンドが現状と一致することを tests/cli の login 経路で確認する
R3-003 — lib/devbase/commands/container.py#_image_max_age_days
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| duplication | consolidate_duplication | minor | agy / kiro | 検証中 | 1 |
なぜ: _image_max_age_days と _snapshot_min_interval_minutes が同型の処理構造を持つ。環境変数の取得、未設定時の既定値返却、整数変換、負値の ValueError 送出による除外、不正値時の warning ログ出力と既定値フォールバックという一連のバリデーション・フォールバックのロジックが 2 箇所に重複している。
手順: 1. _env_non_negative_int(env_name, default) を導入し、read→未設定既定→int→負値 ValueError→warning+既定 の流れを 1 箇所へ寄せる
2. warning 文言を関数内で env 名・raw・default から組み立て、現行 2 種の文言と実質同等にする
3. _image_max_age_days / _snapshot_min_interval_minutes を新ヘルパへの委譲 (定数を渡すだけ) に置き換える
4. tests/cli の期限系テストを実行し、既定値・不正値フォールバック・負値拒否の現状挙動が不変か確認する (snapshot 側の挙動確認のため tests/cli に共有ヘルパの現状固定テストを 1 本足す)
見送った項目
| ラウンド | 対象 | 兆候・経路 | 理由 |
|---|---|---|---|
| 1 | lib/devbase/env/runtime.py#inject |
error | 1 ラウンドの採用上限 5 件を超えた |
| 1 | lib/devbase/env/runtime.py#resolve |
branch | 1 ラウンドの採用上限 5 件を超えた |
| 1 | lib/devbase/env/runtime.py#store_for |
boundary | 1 ラウンドの採用上限 5 件を超えた |
| 1 | lib/devbase/tui/dispatch.py#dispatch_group |
error | 1 ラウンドの採用上限 5 件を超えた |
| 1 | lib/devbase/tui/dispatch.py#dispatch_lifecycle |
normal | 1 ラウンドの採用上限 5 件を超えた |
| 2 | lib/devbase/commands/container.py#_ensure_env_files |
long_method | 1 ラウンドの採用上限 5 件を超えた |
| 2 | lib/devbase/commands/container.py#_ensure_images |
duplication | コミット 7add6cf にトレーラーが欠けています: Item-Id, Round, Impl-Runtime, Impl-Model |
| 2 | lib/devbase/commands/container.py#cmd_scale |
long_method | 実差分 222 行が差分予算 165 行(見積 55 行 × 3)を超えました(範囲の逸脱) |
| 3 | lib/devbase/commands/container.py#_read_compose_services |
duplication | コミット 3ad3fb9 にトレーラーが欠けています: Item-Id, Round, Impl-Runtime, Impl-Model |
Add characterization test verifying that DevbaseError is swallowed and returns an empty SecretEnv when required=False, while DevbaseError is re-raised when required=True. Item-Id: R1-004 Round: 1 Impl-Runtime: agy Impl-Model: default
…_project_name project 切替時に呼び出し元固有の env キーを unset する段階を _unset_caller_only_env_keys(caller_keys, target_dir) として抽出した。 _resolve_project_name は 解決 → chdir/PWD 差し替え → 抽出関数呼び出し → env 反映 の順に整理され、クリーンアップ処理が独立した名前を持つ。 振る舞いは不変 (tests/cli/test_project_name_resolution.py を含む全テストが通過)。 Item-Id: R2-001 Round: 2 Impl-Runtime: kiro Impl-Model: default
…#_ensure_images _ensure_images 内で docker compose config を実行し JSON を解釈して dev サービス定義を取り出す処理を、同ファイルの _resolve_dev_service() の 呼び出しに置き換える。取得できない (None) ときは従来どおり _run_build() へフォールバックする。 現状固定テストとして _ensure_images の入口を subprocess.run の応答だけで 固定する 10 件を tests/cli/test_base_image_staleness.py に追加した。 Item-Id: R2-002 Round: 2 Impl-Runtime: claude Impl-Model: default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ainer.py#_ensure_images" This reverts commit 7add6cf.
…images Extract Compose services reading and dev image specification while preserving image checks and fallback behavior. Item-Id: R2-003 Round: 2 Impl-Runtime: codex Impl-Model: default
cmd_scale 内のボリューム・ネットワーク確認、compose 生成、起動、待機処理を _run_scale_pipeline として抽出し、cmd_scale を引数検証・設定更新・パイプライン呼び出し・後続処理に整理。 Item-Id: R2-004 Round: 2 Impl-Runtime: agy Impl-Model: default
…cmd_scale" This reverts commit 30065e3.
…#_build_resolved _build_resolved 内で 5 回並んでいた `return 0 if <call> else 1` の bool→終了コード 変換を、module ヘルパ `_exit_code(ok: bool) -> int` に集約。委譲行 (_build_with_expires) も同型のため同じヘルパへ統一した。振る舞いは不変。 Item-Id: R2-005 Round: 2 Impl-Runtime: kiro Impl-Model: default
…#_read_compose_services
_resolve_dev_service が独自に `docker compose config --format json` を実行して
JSON を読んでいたのを、_read_compose_services の呼び出しへ寄せる。compose config
の呼び方は 1 箇所になり、_resolve_dev_service は returncode!=0 / JSONDecodeError で
None、成功時に services.get(dev_service_name, {}) を返す薄い包みになる。
JSONDecodeError の握りは _resolve_dev_service 側に残す。共通関数側で握って
(rc, {}) を返すと _ensure_images の不正 JSON 経路 (except → _run_build) が
「イメージ未指定 → True」へ変わるため。
両関数の本体はテストで通っていなかったので、書き換え前に現状固定テストを
tests/cli/test_rebuild.py へ追加した (失敗時 None / (rc,{}) / 不正 JSON の伝播)。
Item-Id: R3-001
Round: 3
Impl-Runtime: claude
Impl-Model: default
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ainer.py#_read_compose_services" This reverts commit 3ad3fb9.
…#cmd_login Share Compose context and secret preparation and override file argument construction. Preserve login command variants and execution order with CLI regression tests. Item-Id: R3-002 Round: 3 Impl-Runtime: codex Impl-Model: default
…#_image_max_age_days Consolidate duplicate non-negative integer environment variable parsing in _image_max_age_days and _snapshot_min_interval_minutes into a shared helper _env_non_negative_int. Item-Id: R3-003 Round: 3 Impl-Runtime: agy Impl-Model: default
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 1 | kiro | APPROVE
SecretStore を操作 1 回持ち回る設計 (store_for/release_store) は、注入の 3 経路と _ensure_env_files を同一インスタンスへ寄せ、往復削減の意図と一致している。env init 後の release_store()(決定 5)、_dispatch_lifecycle finally / TUI 入口での解放、conftest の autouse fixture によるテスト間漏洩対策まで揃っており、モジュールグローバル状態のリスクは制御されている。抽出系リファクタ(_env_non_negative_int / _compose_base_args / _read_compose_services / _dev_image_spec / _unset_caller_only_env_keys / _exit_code)はいずれも既存挙動を保った忠実な抽出で、現状固定テストが伴っている。対象テスト 149 件を実行し全て pass。修正を要する指摘は無し。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 1 | agy | APPROVE
PLAN55(SecretStore のライフサイクル管理による往復削減)および各ラウンドの採用リファクタリング(R2-001, R2-003, R2-005, R3-002, R3-003)と現状固定テストが要件通り実装され、全テストの通過を確認しました。修正を要する問題はありません。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 2 | codex | APPROVE
新規の修正指摘はありません。関連テスト 189 件が成功しました。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 2 | kiro | APPROVE
PLAN55(SecretStore を lifecycle 1 回だけ持ち回り、サーバ backend の往復を認証 1 回+参照ごと 1 回へ収める)の実装として一貫している。store_for/release_store の寿命管理(_dispatch_lifecycle の finally / TUI 入口 / env init 後の release / conftest autouse)が 3 経路 + env init 後読み直しの受け入れ条件と偽サーバ実測で固定されており、_exit_code / _env_non_negative_int / _read_compose_services / _dev_image_spec / _compose_base_args の抽出はいずれも挙動保存で対応テストが付いている。修正を要する設計・正確性・セキュリティ上の指摘は無い。
概要
devbase up1 回のサーバ backend(OpenBao)への往復を、仕様「OpenBao との契約」の想定「認証 1 回 + 参照ごとに取得 1 回」に収める。注入の 3 か所(dispatch 前 / 切替後 / 起動直前)と
_ensure_env_filesがSecretStoreを作り直していたのを、ライフサイクル操作 1 回の間持ち回る形にした。要求と設計は
issues/PLAN55_up-single-injection.md/issues/PLAN55_up-single-injection-design.md(設計 PR #176 でマージ済み)。関連 Issue
変更点
env/runtime.py:store_for(root)/release_store()を追加。resolve/inject/child_envはstore=Noneのときこれを使う(明示的に渡した store は控えない)commands/container.py:_ensure_env_filesが持ち回った store で判定(GET 0)。env initの子プロセスから戻ったらrelease_store()(決定 5)。_dispatch_lifecycleのfinallyでrelease_store()(決定 2)tui/dispatch.py: 委譲の入口_preserve_cwd_envでrelease_store()(決定 3)tests/env/test_runtime_store.py(規則の表)、tests/cli/test_up_roundtrips.py(3 経路の往復 + env init 後の読み直し)、tests/cli/tui/test_dispatch.py(入口で捨てる /env edit→up)、tests/conftest.py(autouse で捨てる)cross-refactoring、3 ラウンド): 現状固定テスト 5 件(cmd_project/resolve/_inject_secretsの失敗系)、_resolve_project_name/_ensure_imagesの extract method、_build_resolved/cmd_login/_image_max_age_daysの重複の統合。見送り 9 件の内訳は改修計画 feat: devbase up の機密の注入で SecretStore を持ち回り、サーバ backend の往復を設計の想定へ収める (PLAN55) #177 (comment)往復の実測(偽サーバ):
webの中でup= 認証 1 / GET 4、apiの中でup web= 認証 1 / GET 6、projects/の外でup web= 認証 1 / GET 4(変更前は認証 4 / GET 12〜14)。検証結果
head
15a6baa。合否は終了コードで判定。uv run pytest -q tests/cli/test_up_roundtrips.py tests/env/test_runtime_store.py tests/cli/tui/test_dispatch.py tests/cli/test_project_name_resolution.py tests/commands/test_container_up_order.py tests/commands/test_container_context.pyuv run pytest -q tests/uvx ruff check --select=E9,F63,F7,F82 lib(CI と同じ選択)libpython3 -m compileall -q lib binlibbincross-review(2 ラウンド)カバレッジツールの設定は無い(
pyproject.tomlに[tool.coverage]なし)ため閾値の判定は行わない。受け入れ条件(PLAN55): 9/9 のうち 8 を満たす。
webの中でup: 認証 1 / GET 4 →test_up_roundtrips.py::test_up_in_projectapiの中でup web: 認証 1 / GET ≤ 6、api 固有キーが残らない →::test_up_other_projectprojects/の外でup web: 認証 1 / GET 4 →::test_up_from_outside_ensure_env_filesが GET を出さない →::test_ensure_env_files_reads_seenageで 3 経路の結果が同じ → 既存test_project_name_resolution.py/test_container_up_order.py/test_container_context.pyが変更なしで通るtest_project_name_resolution.pyを変更していないpytest/ruff/compileall→ 上の表env initが書いた値で起動する →::test_up_after_env_init_reads_written_valuesenv edit→upが新しい値で起動する →tui/test_dispatch.py::test_lifecycle_after_env_edit_reads_written_values未検証の項目: 実機(backend
openbaoの端末)でdevbase --verbose upの認証の行が 1 回であること — リリース後テスト(PLAN53 の切り替え後)で行う既存の失敗: なし
範囲外と判断したもの: なし(見送った構造改善 9 件は改修計画に残り、この PR の受け入れ条件の外)
補足
docs/specifications/secret-backend.md「OpenBao との契約」への追記)はまとまり(v3.4.0)の実装 PR がすべて入った後にplan-to-specで行う_push_bao_tokenはstore_for(root)から token を取れる🤖 Generated with Claude Code
https://claude.ai/code/session_01S9okWVz1S7VGUsCQWVMhb3