何を見つけたか
プロジェクト名の形を見る規則が、env/secret_store.py の _validate_project_name と
utils/names.py の is_single_segment_name の 2 つ並んでいる。
PlaintextBackend.path() は参照の名前をそのままディレクトリ名に使う。
# lib/devbase/env/secret_store.py:207-210
def path(self, ref: SecretRef) -> Path:
if ref.kind == 'global':
return self._root / '.env'
return self._root / 'projects' / _validate_project_name(ref.name or '') / '.env'
通るのは _validate_project_name(同ファイル 73-85 行)だけで、弾くのはパス区切りと
. / .. と空だけである。
if name != Path(name).name or name in ('.', '..'):
raise SecretStoreError(...)
_foo / -x / .hidden / café / 末尾に改行を持つ名前はすべて通る。age backend
(AgeBackend.path()、同ファイル 305-310 行)も同じ規則を通し、secrets/projects/_foo.env.age
に書く。
ただし devbase env set --project / env edit --project / env project は名前を引数で取らない。
--project は store_true で、プロジェクト名は現在のディレクトリから
(env/runtime.py の current_project_name)取る。このため平文の backend で projects/_foo/.env
が書かれるときは、projects/_foo は既にあり、プラグインの同期(symlink)か実ディレクトリの
走査(plugin/syncer.py:204)で名前の形の知らせが出ている。書き込みはその中に .env を
足すだけで、知らせなしにディレクトリが増えることは無い。
一方、is_single_segment_name を使う箇所は次のとおりで、env/secret_store.py は一覧に無い
(main = 5edc75f、2026-09-26)。
$ grep -rn "is_single_segment_name\|NAME_FORM_HINT" lib/ | grep -v utils/names.py
lib/devbase/cli.py:961 ← 位置引数の解決(弾く)
lib/devbase/snapshot/manager.py:158,257 ← スナップショット名(弾く・一覧から外す)
lib/devbase/commands/container.py:594 ← プロジェクト名(弾く)
lib/devbase/commands/container.py:1756 ← イメージ名(弾く)
lib/devbase/plugin/syncer.py:121,129 ← 知らせる
lib/devbase/env/io_import.py:255 ← 知らせる
どこで見つけたか
v3.7.0 の振り返り(起票の取りこぼしを拾う工程)。設計はこの経路が残ることを認識して
先送りしていた。
| 場所 |
記述 |
/Users/takemi_ohama/devbase/issues/old/PLAN66_project-name-validation-design.md:427(未確認のまま残ること) |
「G4 の束(#188)が同じファイルを触るため、この設計では触らない」 |
同 :305(採らなかった案の表) |
「残る経路は『未確認のまま残ること』へ記録した」 |
/Users/takemi_ohama/devbase/docs/specifications/cli-argument-resolution.md:353-356 |
「寄せていない 2 つ」として _validate_project_name を挙げ、別の用途と互換性を持つと書く |
先送りの理由は #229 と同じ形である(「G5 の束が同じファイルを触るので次の束で寄せる」→ #229)。
なぜこの変更の範囲外なのか
PLAN66(#203)は plugin/syncer.py と env/io_import.py の 2 つの入口を対象と定め、
env/secret_store.py は G4(#188)の束と同じファイルを触るため対象から外していた。
直さないと何が起きるか
_validate_project_name と is_single_segment_name という 2 つの規則が並存し続ける
(cli-argument-resolution.md が「寄せていない 2 つ」と呼んでいるうちの 1 つ)。
機密の保存先が受け付ける名前と、名前を指定した操作が受け付ける名前が別々に決まる
- 片方の規則を変えたとき、もう片方との食い違いに気づく場所が無い
直し方の案
| 案 |
内容 |
| A |
PlaintextBackend.path() / AgeBackend.path() が書き込みに使われる経路で、plugin/syncer.py の _warn_unusable_name と同じ知らせを出す。#203 と同じ形で、弾かずに知らせるだけにする |
| B |
_validate_project_name を is_single_segment_name へ寄せる。挙動が変わる(いまは通っている名前が例外になる)ため、CHANGELOG の Changed と移行の案内が要る |
| C |
SecretStore の書き込みの入口を 1 つにまとめてから A を入れる |
#229(文言を utils/names.py の定数へ寄せる)と同じ束で扱うのが自然である。
どちらも container.py / secret_store.py という「#203 が触れなかったファイル」に残った
同じ形の宿題である。プロジェクトとして数える名前の述語を utils/names.py に 1 つ置く作業は、
親 issue #276 が持つ。
由来
issue #203(v3.7.0 の振り返り、PR #212)
何を見つけたか
プロジェクト名の形を見る規則が、
env/secret_store.pyの_validate_project_nameとutils/names.pyのis_single_segment_nameの 2 つ並んでいる。PlaintextBackend.path()は参照の名前をそのままディレクトリ名に使う。通るのは
_validate_project_name(同ファイル 73-85 行)だけで、弾くのはパス区切りと./..と空だけである。_foo/-x/.hidden/café/ 末尾に改行を持つ名前はすべて通る。age backend(
AgeBackend.path()、同ファイル 305-310 行)も同じ規則を通し、secrets/projects/_foo.env.ageに書く。
ただし
devbase env set --project/env edit --project/env projectは名前を引数で取らない。--projectはstore_trueで、プロジェクト名は現在のディレクトリから(
env/runtime.pyのcurrent_project_name)取る。このため平文の backend でprojects/_foo/.envが書かれるときは、
projects/_fooは既にあり、プラグインの同期(symlink)か実ディレクトリの走査(
plugin/syncer.py:204)で名前の形の知らせが出ている。書き込みはその中に.envを足すだけで、知らせなしにディレクトリが増えることは無い。
一方、
is_single_segment_nameを使う箇所は次のとおりで、env/secret_store.pyは一覧に無い(
main=5edc75f、2026-09-26)。どこで見つけたか
v3.7.0 の振り返り(起票の取りこぼしを拾う工程)。設計はこの経路が残ることを認識して
先送りしていた。
/Users/takemi_ohama/devbase/issues/old/PLAN66_project-name-validation-design.md:427(未確認のまま残ること):305(採らなかった案の表)/Users/takemi_ohama/devbase/docs/specifications/cli-argument-resolution.md:353-356_validate_project_nameを挙げ、別の用途と互換性を持つと書く先送りの理由は #229 と同じ形である(「G5 の束が同じファイルを触るので次の束で寄せる」→ #229)。
なぜこの変更の範囲外なのか
PLAN66(#203)は
plugin/syncer.pyとenv/io_import.pyの 2 つの入口を対象と定め、env/secret_store.pyは G4(#188)の束と同じファイルを触るため対象から外していた。直さないと何が起きるか
_validate_project_nameとis_single_segment_nameという 2 つの規則が並存し続ける(
cli-argument-resolution.mdが「寄せていない 2 つ」と呼んでいるうちの 1 つ)。機密の保存先が受け付ける名前と、名前を指定した操作が受け付ける名前が別々に決まる
直し方の案
PlaintextBackend.path()/AgeBackend.path()が書き込みに使われる経路で、plugin/syncer.pyの_warn_unusable_nameと同じ知らせを出す。#203 と同じ形で、弾かずに知らせるだけにする_validate_project_nameをis_single_segment_nameへ寄せる。挙動が変わる(いまは通っている名前が例外になる)ため、CHANGELOG の Changed と移行の案内が要るSecretStoreの書き込みの入口を 1 つにまとめてから A を入れる#229(文言を
utils/names.pyの定数へ寄せる)と同じ束で扱うのが自然である。どちらも
container.py/secret_store.pyという「#203 が触れなかったファイル」に残った同じ形の宿題である。プロジェクトとして数える名前の述語を
utils/names.pyに 1 つ置く作業は、親 issue #276 が持つ。
由来
issue #203(v3.7.0 の振り返り、PR #212)