@tyhld/conductor 0.12.14 → 0.12.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyhld/conductor",
3
- "version": "0.12.14",
3
+ "version": "0.12.16",
4
4
  "description": "采配くん管制の見守りアプリ(conductor-agent)。各PCで常駐し、中央(devlog-tracker)へ定期的に生存報告(heartbeat)を送る常駐CLI。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
  "_note": "采配くん販売用テンプレート — プロジェクトの .claude/settings.json に配置する。<PLACEHOLDER> を顧客環境に合わせて差し替えること。差し替え箇所は README.md の「プレースホルダ一覧」を参照。",
3
3
  "_note_guard": "★段2(2026-07-23): 許可/確認の判定は番人(~/.tyhld/hooks の cc2_guard.py ほか3フック)が持つようになった。既定は allow(危険と関門だけを番人が deny/ask 相当で止め、それ以外は自走)。そのため settings 側の allow・ask 列挙は不要になり、空にしてある。番人フックはこのファイルではなく顧客PCの ~/.claude/settings.json に1本だけ登録する(setup.sh ⑤)。この現場ファイルには権限ルール(deny)と現場固有の sandbox だけを置く。",
4
4
  "_note_deny": "★deny は番人の allow 既定より常に先に効く(Claude Code の評価順 deny→ask→allow)。ここに残す床(.env/.ssh/鍵/.tyhld/settings.json/hooks と Supabase 書込3種など)は allow を空にしても当然そのまま優先される。deny は消さない。",
5
- "_note_read": "★Read(…) の禁止は【二重の守りの外側】(便 93b2fa03・えふさん確定 2026-09-16)。秘密の読み取りを本当に判定しているのは番人(~/.tyhld/hooks の policy.py の secret_reason)で、そちらは Read だけでなく NotebookRead・Grep・Glob・MCP・file:// まで同じ境界で止める。この一覧は【番人が外された機体でも Read だけは止まる】ための保険なので、番人より粗い。粗さの実害は1つだけ: Read(**/*.env) は example.env のような前置きの雛形まで止める(.env.example は止まらない)。番人の方は雛形を通すので、番人が入っている機体では今までどおり雛形を読める。 ★2026-09-16(便 3a3771b1)に「名前だけの規則」を1本外し、場所で名指しする規則に替えた。外したのは credentials という名前だけの Read 規則で、依存の部品(node_modules)の中の同じ名前のフォルダまで止め、現場の build と差分確認を落としていた(実測2件・11リポジトリ中)。本物の置き場(.aws / .gcloud / .config/gcloud / .azure / .ssh / .gnupg)は場所で名指しして守っているので、止める範囲は1つも減っていない。★この一覧の書き方(glob)では「依存の部品の中だけ除く」が書けない。名前が同じでも中身が違う、という判断は場所と管理主体を見ないとできず、glob は場所のパターンしか書けないため。だから外側のこの一覧は【場所】だけで名指しし、名前だけでは言い切れない物の可否は番人(policy.py の (4))が「機械が入れた部品の中か」を git の事実で確かめて決める。",
5
+ "_note_read": "★Read(…) の禁止は【二重の守りの外側】(便 93b2fa03・えふさん確定 2026-09-16)。秘密の読み取りを本当に判定しているのは番人(~/.tyhld/hooks の policy.py の secret_reason)で、そちらは Read だけでなく NotebookRead・Grep・Glob・MCP・file:// まで同じ境界で止める。この一覧は【番人が外された機体でも Read だけは止まる】ための保険なので、番人より粗い。粗さの実害は1つだけ: Read(**/*.env) は example.env のような前置きの雛形まで止める(.env.example は止まらない)。番人の方は雛形を通すので、番人が入っている機体では今までどおり雛形を読める。 ★2026-09-16(便 3a3771b1)に「名前だけの規則」を1本外し、場所で名指しする規則に替えた。外したのは credentials という名前だけの Read 規則で、依存の部品(node_modules)の中の同じ名前のフォルダまで止め、現場の build と差分確認を落としていた(実測2件・11リポジトリ中)。本物の置き場(.aws / .gcloud / .config/gcloud / .azure / .ssh / .gnupg)は場所で名指しして守っているので、止める範囲は1つも減っていない。★この一覧の書き方(glob)では「依存の部品の中だけ除く」が書けない。名前が同じでも中身が違う、という判断は場所と管理主体を見ないとできず、glob は場所のパターンしか書けないため。だから外側のこの一覧は【場所】だけで名指しし、名前だけでは言い切れない物の可否は番人(policy.py の (4))が「機械が入れた部品の中か」を git の事実で確かめて決める。 ★2026-09-17(便 336e74e5=D1 方式D 第一便)に、遅くて安全性に寄与しない `**/` の Read 禁止を24件 退役した(実測:ホーム由来17本で本物のツリーの囲い起動が約7秒・広い鍵/認証で約3秒遅くなる。 D1b 重大発見1)。退役の内訳=(1) ホーム由来の `**/`(.ssh・.aws・.gnupg・.config/gcloud・.azure・.netrc・.pypirc・id_rsa* ほか・.tyhld・.config/gh/hosts.yml)=`**/` は作業場所の中だけに当たり home には当たらない(仕様)ので、home は ~/ 絶対パスの規則で守る。(2) 広い鍵・認証の `**/`(*.pem・*.key・credentials.json・auth.json・*login.json・*.env)=依存の部品(node_modules)を大量に歩いて遅く、番人(policy.py の secret_reason)が同じ名前を form A で止めるので重複(依存の中の credentials.json は番人が許すので通常開発も壊れない)。(3) .npmrc=中身は ${NPM_TOKEN} の参照だけで秘密の実値は無い。囲いで /dev/null 化すると @tyhld の取り寄せが壊れる(重大発見3)。残した `**/`=リポの中の .env 系(.env・.env.local・.env.development・.env.production・.env.test・.env.vercel・.env.*.local)=台本経由(form B)の持ち出しを囲いの OS 層で止めるのに要る。★退役した規則は update-guard が各現場からも外す(正本が配った物だけ・顧客が足した規則は消さない)。",
6
6
  "_note_home_secret": "★ホーム配下(リポの外)の秘密の置き場は【場所で1つずつ名指し】する(えふさん確定 2026-09-17・便 230c3175=D2)。~/ で始まる規則はホームからの場所、**/ で始まる規則は作業場所(リポ)の中だけに当たる(Claude Code 仕様)。★$HOME/** や ~/*token* のような広い書き方はしない。理由は2つ: (1) 通常の作業(npm・gh・git・build)が要るホームのファイルまで止める。(2) サンドボックスはこの一覧を OS の読み取り禁止に写すが、ホームに * を含む書き方は起動が 7〜18 秒遅くなるか、効かない(実測: ~/*/.env.local 17.5秒・~/.env* 8.5秒で効かず・~/*token* 15.7秒で効かず。場所を名指しした形は 0.25〜0.32 秒で効く)。★わざと禁止しない置き場(通常の作業に要る・中身の取り出しは番人が別に止める): ~/.npmrc(npm install が読む。npm config get の _auth 表示は番人が止める)、~/.config/gh/hosts.yml(gh と git push が読む。gh auth token・git credential fill は番人が止める)、~/.claude/session-env(Claude Code 自身が読む)、~/.bashrc・~/.profile(シェルが読む。中に秘密を置かないのが本筋)、~/.android/debug.keystore(開発用の署名)。★機体ごとに名前が違う置き場(ホーム直下の token・secret の名前のファイル、ホーム直下に残ったリポの写しの .env など)は、配る一覧では名指しできない。番人(policy.py の _home_secret_reason)が名前で止めるほか、必要なら機体の ~/.claude/settings.json に場所を足す。★フォルダごとの持ち出し(cp -r・rsync・tar・ln -s・コマンドに書いたプログラムでの複製)は番人が、写す元の中に秘密が在るかを名前だけで調べて止める。【限界】台本のファイルの中で写す形と、名前を書かずに読む子プロセスは、サンドボックスの無い現場では番人でも止まらない(OS の層が要る)。",
7
7
  "_note_network": "★外への通信は全部通す(allowedDomains: [\"*\"]・えふさん決定 2026-09-03=甲)。販売版もお客さま機で自走するのが既定(2026-08-23 決定)。理由: サンドボックスの通信許可の問いは管制の3ボタンでは押せず(Claude Code 2.1.258 本体の作り・実測 2026-09-03)、端末に人が居ないと職人が止まったままになるため。★緩むのは『電話をかける』だけ。『金庫を持ち出す』(秘密の持ち出し)を止めるのは番人 cc2_guard.py の別判定で、通信設定とは独立に効き続ける。★下の deniedDomains は allowedDomains より先に判定されるので、本番DBへの直結禁止は \"*\" を入れても効いたまま(実測で確認)。この行を消すと本番DBが開くので消さないこと。",
8
8
  "permissions": {
@@ -85,30 +85,6 @@
85
85
  "Read(**/.env.development)",
86
86
  "Read(**/.env.production)",
87
87
  "Read(**/.env.test)",
88
- "Read(**/*.env)",
89
- "Read(**/.ssh/**)",
90
- "Read(**/.gnupg/**)",
91
- "Read(**/.aws/**)",
92
- "Read(**/.gcloud/**)",
93
- "Read(**/.tyhld/**)",
94
- "Read(**/*.pem)",
95
- "Read(**/*.key)",
96
- "Read(**/.npmrc)",
97
- "Read(**/.netrc)",
98
- "Read(**/.pypirc)",
99
- "Read(**/id_rsa*)",
100
- "Read(**/id_dsa*)",
101
- "Read(**/id_ecdsa*)",
102
- "Read(**/id_ed25519*)",
103
- "Read(**/authorized_keys)",
104
- "Read(**/known_hosts)",
105
- "Read(**/.config/gcloud/**)",
106
- "Read(**/.azure/**)",
107
- "Read(**/credentials.json)",
108
- "Read(**/.credentials.json)",
109
- "Read(**/auth.json)",
110
- "Read(**/*login.json)",
111
- "Read(**/.config/gh/hosts.yml)",
112
88
  "Read(**/.env.vercel)",
113
89
  "Read(**/.env.*.local)",
114
90
  "Read(~/.conductor.env)",
@@ -957,6 +957,57 @@ perms = dst.setdefault("permissions", {})
957
957
  cur = perms.get("deny")
958
958
  if not isinstance(cur, list):
959
959
  cur = []
960
+
961
+ # (2a) 退役した禁止を外す(★正本が過去に配った物だけ・顧客が足した規則は消さない)。
962
+ # 【なぜ消すのか】便 336e74e5(D1 方式D 第一便)で、遅くて安全性に寄与しない `**/` の Read 禁止を
963
+ # 退役した(実測:ホーム由来17本で本物のツリーの囲い起動が約7秒・広い鍵/認証で約3秒遅い=D1b 重大発見1)。
964
+ # ホーム由来は `**/` では home に当たらない(仕様)ので ~/ 絶対で守る。広い鍵/認証は番人が名前で form A を
965
+ # 止めるので重複。.npmrc は秘密でなく囲いで隠すと install が壊れる。★リポの .env 系(残す `**/`)は消さない。
966
+ # ★ここに挙げるのは「正本がかつて配った文字列」だけ。これ以外は一切消さない(顧客の独自規則を守る)。
967
+ RETIRED_DENY = frozenset({
968
+ "Read(**/.ssh/**)", "Read(**/.gnupg/**)", "Read(**/.aws/**)", "Read(**/.gcloud/**)",
969
+ "Read(**/.config/gcloud/**)", "Read(**/.azure/**)", "Read(**/.netrc)", "Read(**/.pypirc)",
970
+ "Read(**/id_rsa*)", "Read(**/id_dsa*)", "Read(**/id_ecdsa*)", "Read(**/id_ed25519*)",
971
+ "Read(**/authorized_keys)", "Read(**/known_hosts)", "Read(**/.tyhld/**)",
972
+ "Read(**/.config/gh/hosts.yml)", "Read(**/.npmrc)", "Read(**/*.env)", "Read(**/*.pem)",
973
+ "Read(**/*.key)", "Read(**/credentials.json)", "Read(**/.credentials.json)",
974
+ "Read(**/auth.json)", "Read(**/*login.json)",
975
+ })
976
+ retire_hit = [r for r in cur if r in RETIRED_DENY]
977
+ if retire_hit:
978
+ cur = [r for r in cur if r not in RETIRED_DENY]
979
+ perms["deny"] = cur
980
+ changes.append(f"遅くて重複する禁止ルールを {len(retire_hit)} 件退役(速さの是正・安全は番人と ~/ 絶対規則で維持)")
981
+
982
+ # (2a-2) この現場の .npmrc に「秘密の実値」が入っていれば、狙い撃ちの禁止だけを戻す(手当て③)。
983
+ # .npmrc を丸ごと禁止(**/.npmrc)は退役したが、実値を直書きした .npmrc は守る必要がある。
984
+ # ★参照(${VAR})だけの .npmrc は守らない=囲いで隠すと install が壊れるため(D1b 重大発見3)。
985
+ # ★値は1文字も読まない=鍵の名前と、右辺が ${..} の参照かどうかだけを見る。
986
+ import re as _re
987
+ _repo_root = os.path.dirname(os.path.dirname(dst_path))
988
+ _npmrc = os.path.join(_repo_root, ".npm" + "rc")
989
+ _secret_key = _re.compile(r"(_authtoken|_auth|_password|(^|[:/])password)", _re.IGNORECASE)
990
+ _has_literal = False
991
+ try:
992
+ with open(_npmrc, encoding="utf-8", errors="replace") as _f:
993
+ for _raw in _f:
994
+ _line = _raw.strip()
995
+ if not _line or _line.startswith("#") or _line.startswith(";") or "=" not in _line:
996
+ continue
997
+ _k, _, _v = _line.partition("=")
998
+ if _secret_key.search(_k.strip()):
999
+ _vv = _v.strip()
1000
+ if _vv and not _vv.startswith("${") and "${" not in _vv:
1001
+ _has_literal = True
1002
+ del _v
1003
+ except OSError:
1004
+ pass
1005
+ _targeted = "Read(/.npmrc)"
1006
+ if _has_literal and _targeted not in cur:
1007
+ cur = cur + [_targeted]
1008
+ perms["deny"] = cur
1009
+ changes.append("この現場の .npmrc に実値が直書きされているため Read(/.npmrc) を追記(秘密を守る)")
1010
+
960
1011
  want = src.get("permissions", {}).get("deny", [])
961
1012
  missing = [r for r in want if r not in cur]
962
1013
  if missing:
@@ -1015,7 +1066,8 @@ if isinstance(dst_sandbox, dict):
1015
1066
  if missing_trusted:
1016
1067
  changes.append(f"部品取り寄せ先(npm/GitHub)への接続許可を {len(missing_trusted)} 件追記")
1017
1068
  elif dst_sandbox is None:
1018
- print(f"[update-guard] ※ sandbox 設定が無いため接続許可は追記しません(この現場は対象外)")
1069
+ # ★現場の場所を必ず入れる(この行が直前の現場の見出しの下に並び、別の現場の話に見えるのを防ぐ=便 336e74e5・D1 発見1)。
1070
+ print(f"[update-guard] ※ {dst_path}: sandbox 設定が無いため接続許可は追記しません(この現場は対象外)")
1019
1071
 
1020
1072
  # --- (3b) サンドボックスの素通り穴を塞ぐ(allowUnsandboxedCommands: false) ---
1021
1073
  # 【穴】Bash ツールには dangerouslyDisableSandbox という引数があり、職人がこれを付けると