Skip to content

secret_store の _validate_project_name と is_single_segment_name の 2 つの規則が並んでいる #245

Description

@takemi-ohama

何を見つけたか

プロジェクト名の形を見る規則が、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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions