dsh-perm-gate 2.1.0 → 3.0.0
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/CHANGELOG.ja.md +82 -0
- package/CHANGELOG.ko.md +80 -0
- package/CHANGELOG.md +321 -0
- package/INSTALL.ja.md +70 -8
- package/INSTALL.ko.md +68 -9
- package/INSTALL.md +68 -8
- package/INSTALL.zh.md +59 -7
- package/README.ja.md +103 -16
- package/README.ko.md +101 -17
- package/README.md +152 -25
- package/README.zh.md +99 -12
- package/cordis.patch.yml +34 -3
- package/docs/baseline-guard.md +56 -0
- package/docs/plan-rules-to-settings.md +217 -0
- package/docs/rules-format.en.md +294 -0
- package/docs/rules-format.md +279 -0
- package/docs/ui-gap-analysis-and-branch-permissions.md +338 -0
- package/dsh.plugin.json +22 -0
- package/examples/permissions.example.yaml +16 -1
- package/lib/agent-identity.d.ts +43 -0
- package/lib/agent-identity.js +95 -0
- package/lib/allowlist.d.ts +27 -0
- package/lib/allowlist.js +103 -1
- package/lib/cli.js +11 -14
- package/lib/client.js +598 -6
- package/lib/client.js.map +1 -1
- package/lib/command-dispatcher.d.ts +22 -0
- package/lib/command-dispatcher.js +97 -0
- package/lib/command-semantics.d.ts +62 -0
- package/lib/command-semantics.js +6 -0
- package/lib/compiler.d.ts +50 -0
- package/lib/compiler.js +122 -0
- package/lib/config.d.ts +161 -7
- package/lib/config.js +78 -11
- package/lib/dry-run.d.ts +87 -0
- package/lib/dry-run.js +110 -0
- package/lib/engine.js +61 -4
- package/lib/evaluate.d.ts +9 -0
- package/lib/evaluate.js +264 -1
- package/lib/events.d.ts +69 -2
- package/lib/events.js +123 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +320 -11
- package/lib/network-lifecycle.d.ts +107 -0
- package/lib/network-lifecycle.js +180 -0
- package/lib/network.d.ts +109 -0
- package/lib/network.js +196 -0
- package/lib/parsers/git.d.ts +43 -0
- package/lib/parsers/git.js +404 -0
- package/lib/parsers/shell-cmds.d.ts +32 -0
- package/lib/parsers/shell-cmds.js +293 -0
- package/lib/preset.d.ts +13 -0
- package/lib/preset.js +17 -0
- package/lib/proxy.d.ts +160 -0
- package/lib/proxy.js +529 -0
- package/lib/risk.d.ts +17 -3
- package/lib/risk.js +19 -6
- package/lib/rule-chain.d.ts +39 -0
- package/lib/rule-chain.js +213 -0
- package/lib/rule-dims.d.ts +174 -0
- package/lib/rule-dims.js +230 -0
- package/lib/rule.d.ts +60 -0
- package/lib/rule.js +69 -7
- package/lib/rules-view.d.ts +52 -0
- package/lib/rules-view.js +126 -0
- package/lib/runtime.d.ts +208 -7
- package/lib/runtime.js +433 -27
- package/lib/session-sweep.d.ts +45 -0
- package/lib/session-sweep.js +153 -0
- package/lib/shadow.d.ts +34 -0
- package/lib/shadow.js +143 -0
- package/lib/shell.d.ts +14 -0
- package/lib/shell.js +17 -0
- package/lib/watch.d.ts +42 -0
- package/lib/watch.js +170 -0
- package/package.json +15 -2
- package/scripts/patch-permission-glyph.mjs +209 -0
package/CHANGELOG.ja.md
CHANGED
|
@@ -5,6 +5,88 @@
|
|
|
5
5
|
形式は [Keep a Changelog](https://keepachangelog.com/ja/1.1.0/) に従い、
|
|
6
6
|
このプロジェクトは [Semantic Versioning](https://semver.org/spec/v2.0.0.html) に準拠します。
|
|
7
7
|
|
|
8
|
+
## [2.4.1] - 2026-09-17
|
|
9
|
+
|
|
10
|
+
### 修正
|
|
11
|
+
|
|
12
|
+
- 承認履歴が、フラグ付きの呼び出しがなぜレビューされずに実行されたのかを説明する唯一のイベントについて、生の `preset-passthrough` 文字列を表示していました(ティアは `approval: ask` を宣言しているのに、セッションが `never` で上書きされ、ゲートの ask がパススルーに降格したためです)。他の判定と同じく読みやすいラベルで表示されるようになりました。
|
|
13
|
+
|
|
14
|
+
## [2.4.0] - 2026-09-17
|
|
15
|
+
|
|
16
|
+
### 変更
|
|
17
|
+
|
|
18
|
+
- **P3 LLM 分類器はエスカレート専用になり、拒否できなくなりました。** ハードカテゴリ
|
|
19
|
+
(`deletion` / `credential` / `remote` / `system` / `bulk`) の `risky` 判定はこれまでパネルなしで
|
|
20
|
+
**自動拒否**していましたが、現在は**人間への確認(ask)を維持**します。拒否は決定論的レイヤー
|
|
21
|
+
(P0 ハード拒否、拒否キーワードのブラックリスト、明示的な `deny:` ルール)だけに属します。
|
|
22
|
+
確率的な判定が、異議を唱えられないブロックを下してはならないからです。実測: グレーダーは無害な
|
|
23
|
+
`git commit -F …` を **`remote`** と判定し、自動拒否は承認するパネルも再試行する grant も残しませんでした
|
|
24
|
+
(たまたま `safe` と判定された手動リトライだけが通りました)。これは「高リスク操作は**決定論的に**
|
|
25
|
+
遮断し、LLM の判断には委ねない」というプロジェクト自身のルールとも整合します。
|
|
26
|
+
|
|
27
|
+
### 追加
|
|
28
|
+
|
|
29
|
+
- ハードリスクカテゴリは `neutral` との重要な違いを一つだけ保ちます: **決して学習されません**。
|
|
30
|
+
人間の承認を繰り返しても `deletion`/`credential`/`remote`/`system`/`bulk` の判定が自動許可に
|
|
31
|
+
沈殿することはありません(従来は自動拒否の副作用としてのみ成立していましたが、現在は明示的な性質です)。
|
|
32
|
+
|
|
33
|
+
### 動作上の注意
|
|
34
|
+
|
|
35
|
+
- `approval: never` では、ゲートが配信できない ask は従来どおりパススルーに降格するため、分類器が
|
|
36
|
+
フラグを立てた呼び出しは**実行されます**(以前は自動拒否でした)。これは「決定論的に危険なものだけを
|
|
37
|
+
拒否し、それ以外は交渉する」の直接の帰結です —— 交渉には人間が必要で、`never` は人間がいないことを
|
|
38
|
+
意味します。フラグを実際に受け取りたい場合は `approval` が `ask` のティアで運用してください。
|
|
39
|
+
|
|
40
|
+
## [2.3.0] - 2026-09-17
|
|
41
|
+
|
|
42
|
+
### 追加
|
|
43
|
+
|
|
44
|
+
- **ゲートの停止はもはや沈黙しません。** セッションの権限プリセットが `gatePresets` の外にあるとき、
|
|
45
|
+
ゲートは停止して何も記録しませんでした —— そのためツール呼び出しは、ゲートが検査して許可した
|
|
46
|
+
呼び出しと見分けがつきませんでした。現在は (session, preset) の遷移ごとに **1 件だけ**
|
|
47
|
+
`stand-down` 通知を記録し(呼び出しごとではありません)、プリセット・スコープ・P0 ハード拒否が
|
|
48
|
+
無効である事実を明示します。ブラウザは入力欄の上に常駐の **GATE OFF** ストリップとして表示し、
|
|
49
|
+
承認履歴には `ゲート停止` タグが出ます。
|
|
50
|
+
- 自动审查 設定カードがゲート自身のプリセットスコープ(`gatePresets`、既定は
|
|
51
|
+
`permissive` / `permissive-full`)とその外側で何が起きるかを明示するため、スコープはティアを
|
|
52
|
+
設定する場所で確認できます。
|
|
53
|
+
|
|
54
|
+
### 変更
|
|
55
|
+
|
|
56
|
+
- **P0 の文書上の位置づけはグローバルではなくスコープ付きです。** P0 ハード拒否は*ゲートのプリセット
|
|
57
|
+
スコープ内*で単調かつ交渉不能です。プリセットをまたぐとゲートは P0 を含めて完全に停止します ——
|
|
58
|
+
選択されたティア自身のポリシーがそのセッションを所有するためです。コードは常にこう動作していましたが、
|
|
59
|
+
`AGENTS.md` と 4 つの README は逆を主張しており、`danger-full-access` が「P0 は依然適用される」と
|
|
60
|
+
読めてしまいました。`gatePresets: ['*']` を設定すると P0 は再びグローバルになります。
|
|
61
|
+
|
|
62
|
+
### 修正
|
|
63
|
+
|
|
64
|
+
- `DEFAULT_GATE_PRESETS` / `resolveGatePresets` を `config.ts`(schemastery を import しています)から
|
|
65
|
+
依存のない `preset.ts` へ移し、ブラウザ側が node 専用依存をクライアントバンドルに引き込まずに
|
|
66
|
+
スコープを描画できるようにしました。`config.ts` は両方を再エクスポートするため、既存の import は
|
|
67
|
+
変更ありません。
|
|
68
|
+
|
|
69
|
+
## [2.2.0] - 2026-09-17
|
|
70
|
+
|
|
71
|
+
### 修正
|
|
72
|
+
|
|
73
|
+
- **P0 ハード拒否が、4 つのシェルツール以外をすべて素通りさせていました。** `hardDenyReason` はシェル検査をローカルの正規表現 `/^(?:bash|pwsh|sh|cmd)$/` で門番していましたが、これは `shell` / `terminal` / `powershell` に**一致しません**。`shell` は DSH の主要シェルツールであるため、P0 のシェル検査(保護パスへのリダイレクト)は**最も一般的な呼び出し形に対して無効**でした。実測、同一コマンド `echo x > /etc/passwd`:`bash` 経由は遮断、`shell` 経由は**通過**。`engine.ts` は `evaluate.ts` から `SHELL_TOOLS` を取り込むよう変更しました——同じ事実の複製が 3 か所にあり、完全だったのは 1 つだけでした。
|
|
74
|
+
- **`git push --delete` が通常の push として解析されていました。** `hasDelete` は `analyzeSubcommand` で計算されながら `branch` ケースでしか読まれず、`git push --delete origin main` は通常 push 分岐(`destructiveness: 4`)に落ちていました。`DESTRUCTIVENESS_MAP` の `'push-delete': 5` は死んだデータでした。
|
|
75
|
+
- **保護ブランチ判定が、スラッシュを含む名前に誤反応していました。** 述語は最後の `/` より前を落としていたため、`backup/main` や `feat/release` が保護扱いになっていました。既知の ref 接頭辞(`refs/heads/`、`refs/tags/`、`refs/remotes/<remote>/`)のみを剥がすよう変更しました。方向が重要です——この述語は**取り消し不能な** P0 に供給され、見逃しにはキーワード/ルール/LLM 層という受け皿がありますが、誤検出は正当なワークフローを出口なく塞いでしまいます。
|
|
76
|
+
|
|
77
|
+
### 追加
|
|
78
|
+
|
|
79
|
+
- **保護ブランチに対するリモート履歴改変の P0 ハード拒否。** `main` / `master` / `production` / `release` / `stable` を強制上書きまたは削除する `git push` を、LLM 呼び出しより前に決定的に拒否します。従来の保護は、フラットでブランチを見ず、名前空間で上書き可能なキーワード `'push --force'` でした。これはその下に敷く交渉不能な床です。
|
|
80
|
+
- **「エスカレーションのみ」は慣習ではなく構造です**: ヘルパーは `string | undefined` を返し、呼び出し側はそれを deny / 未決定として読みます。allow を表現する手段がないため、P0 に配線してもゲートが許可する範囲を広げることはできません。
|
|
81
|
+
- 意図的に**対象外**(引き続きキーワード/ルール/LLM 層が担当): 非保護ブランチへの force push、ブランチ名を書かない `git push --force`、通常の push。
|
|
82
|
+
- コマンドパーサ(`command-dispatcher` / `command-semantics` / `parsers/git` / `parsers/shell-cmds`)の初の本番利用です。これまで自身のテストファイルからしか参照されていませんでした。
|
|
83
|
+
|
|
84
|
+
### テスト
|
|
85
|
+
|
|
86
|
+
- 新規 `test/git-protected-push.spec.ts`(13 件)。
|
|
87
|
+
- **反証**: 3 つの修正をそれぞれ戻すと、それを守るケースだけが赤くなります(計 7 件)。意図的に許可するケースは緑のままです。
|
|
88
|
+
- 全量 **40 ファイル / 459 件**、`typecheck` クリーン。
|
|
89
|
+
|
|
8
90
|
## [2.0.0] - 2026-09-11
|
|
9
91
|
|
|
10
92
|
### 修正
|
package/CHANGELOG.ko.md
CHANGED
|
@@ -5,6 +5,86 @@
|
|
|
5
5
|
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)을 따르며,
|
|
6
6
|
이 프로젝트는 [Semantic Versioning](https://semver.org/spec/v2.0.0.html)을 준수합니다.
|
|
7
7
|
|
|
8
|
+
## [2.4.1] - 2026-09-17
|
|
9
|
+
|
|
10
|
+
### 수정됨
|
|
11
|
+
|
|
12
|
+
- 승인 이력이, 플래그된 호출이 왜 검토 없이 실행되었는지 설명하는 유일한 이벤트에 대해 원시 `preset-passthrough` 문자열을 표시했습니다(티어는 `approval: ask`를 선언했지만 세션이 `never`로 재정의되어 게이트의 ask가 패스스루로 강등된 경우입니다). 이제 다른 판정과 마찬가지로 읽기 쉬운 라벨로 표시됩니다.
|
|
13
|
+
|
|
14
|
+
## [2.4.0] - 2026-09-17
|
|
15
|
+
|
|
16
|
+
### 변경됨
|
|
17
|
+
|
|
18
|
+
- **P3 LLM 분류기는 에스컬레이션 전용이 되어 더 이상 거부할 수 없습니다.** 하드 카테고리
|
|
19
|
+
(`deletion` / `credential` / `remote` / `system` / `bulk`)의 `risky` 판정은 이전에는 패널 없이
|
|
20
|
+
**자동 거부**했지만, 이제는 **사람 확인(ask)을 유지**합니다. 거부는 결정론적 계층(P0 하드 거부,
|
|
21
|
+
거부 키워드 블랙리스트, 명시적 `deny:` 규칙)에만 속합니다. 확률적 판정이 이의를 제기할 수 없는
|
|
22
|
+
차단을 내려서는 안 되기 때문입니다. 실측: 그레이더가 무해한 `git commit -F …`를 **`remote`**로
|
|
23
|
+
판정했고, 자동 거부는 승인할 패널도 재시도할 grant도 남기지 않았습니다(우연히 `safe`로 판정된
|
|
24
|
+
수동 재시도만 통과했습니다). 이는 "고위험 작업은 **결정론적으로** 차단하고 LLM 판단에 맡기지
|
|
25
|
+
않는다"는 프로젝트 자체 규칙과도 일치합니다.
|
|
26
|
+
|
|
27
|
+
### 추가됨
|
|
28
|
+
|
|
29
|
+
- 하드 위험 카테고리는 `neutral`과의 중요한 차이를 하나만 유지합니다: **결코 학습되지 않습니다**.
|
|
30
|
+
사람의 승인을 반복해도 `deletion`/`credential`/`remote`/`system`/`bulk` 판정이 자동 허용으로
|
|
31
|
+
침전되지 않습니다(이전에는 자동 거부의 부수 효과로만 성립했지만, 이제는 명시적 속성입니다).
|
|
32
|
+
|
|
33
|
+
### 동작 주의
|
|
34
|
+
|
|
35
|
+
- `approval: never`에서는 게이트가 전달할 수 없는 ask가 여전히 패스스루로 강등되므로, 분류기가
|
|
36
|
+
플래그한 호출은 이제 **실행됩니다**(이전에는 자동 거부였습니다). 이는 "결정론적으로 위험한 것만
|
|
37
|
+
거부하고 나머지는 협상한다"의 직접적 귀결입니다 —— 협상에는 사람이 필요하고, `never`는 사람이
|
|
38
|
+
없다는 뜻입니다. 플래그를 실제로 받으려면 `approval`이 `ask`인 티어에서 운용하십시오.
|
|
39
|
+
|
|
40
|
+
## [2.3.0] - 2026-09-17
|
|
41
|
+
|
|
42
|
+
### 추가됨
|
|
43
|
+
|
|
44
|
+
- **게이트 정지는 더 이상 침묵하지 않습니다.** 세션 권한 프리셋이 `gatePresets` 밖에 있을 때 게이트는
|
|
45
|
+
정지하고 아무것도 기록하지 않았습니다 —— 그래서 그 도구 호출은 게이트가 검사하고 허용한 호출과
|
|
46
|
+
구별되지 않았습니다. 이제 (session, preset) 전환마다 **1건만** `stand-down` 알림을 기록하고
|
|
47
|
+
(호출마다가 아닙니다), 프리셋·범위·P0 하드 거부가 비활성이라는 사실을 명시합니다. 브라우저는
|
|
48
|
+
입력창 위에 상시 **GATE OFF** 표시줄로 렌더링하고, 승인 이력에는 `게이트 정지` 태그가 표시됩니다.
|
|
49
|
+
- 自动审查 설정 카드가 게이트 자체의 프리셋 범위(`gatePresets`, 기본값
|
|
50
|
+
`permissive` / `permissive-full`)와 그 밖에서 벌어지는 일을 명시하므로, 범위를 티어를 설정하는
|
|
51
|
+
곳에서 확인할 수 있습니다.
|
|
52
|
+
|
|
53
|
+
### 변경됨
|
|
54
|
+
|
|
55
|
+
- **P0의 문서상 위치는 전역이 아니라 범위 한정입니다.** P0 하드 거부는 *게이트의 프리셋 범위 안에서*
|
|
56
|
+
단조롭고 협상 불가능합니다. 프리셋을 넘어가면 게이트는 P0를 포함해 완전히 정지합니다 —— 선택된
|
|
57
|
+
티어 자체의 정책이 그 세션을 소유하기 때문입니다. 코드는 항상 이렇게 동작했지만 `AGENTS.md`와
|
|
58
|
+
4개 README는 반대로 주장해, `danger-full-access`가 "P0는 여전히 적용된다"로 읽혔습니다.
|
|
59
|
+
`gatePresets: ['*']`로 설정하면 P0는 다시 전역이 됩니다.
|
|
60
|
+
|
|
61
|
+
### 수정됨
|
|
62
|
+
|
|
63
|
+
- `DEFAULT_GATE_PRESETS` / `resolveGatePresets`를 `config.ts`(schemastery를 import합니다)에서
|
|
64
|
+
의존성 없는 `preset.ts`로 옮겨, 브라우저 쪽이 node 전용 의존성을 클라이언트 번들로 끌어오지 않고
|
|
65
|
+
범위를 렌더링할 수 있게 했습니다. `config.ts`는 둘 다 재수출하므로 기존 import는 변경이 없습니다.
|
|
66
|
+
|
|
67
|
+
## [2.2.0] - 2026-09-17
|
|
68
|
+
|
|
69
|
+
### 수정
|
|
70
|
+
|
|
71
|
+
- **P0 하드 거부가 4개의 셸 도구를 제외한 나머지를 모두 통과시키고 있었습니다.** `hardDenyReason`은 셸 검사를 로컬 정규식 `/^(?:bash|pwsh|sh|cmd)$/`로 막고 있었는데, 이는 `shell` / `terminal` / `powershell`에 **일치하지 않습니다**. `shell`은 DSH의 주 셸 도구이므로, P0의 셸 검사(보호 경로로의 리다이렉트)는 **가장 흔한 호출 형태에 대해 무력**했습니다. 실측, 동일 명령 `echo x > /etc/passwd`: `bash` 경유는 차단, `shell` 경유는 **통과**. `engine.ts`가 이제 `evaluate.ts`에서 `SHELL_TOOLS`를 가져옵니다——같은 사실의 사본이 세 곳에 있었고, 완전한 것은 하나뿐이었습니다.
|
|
72
|
+
- **`git push --delete`가 일반 push로 파싱되고 있었습니다.** `hasDelete`는 `analyzeSubcommand`에서 계산되지만 `branch` 케이스에서만 읽혔고, 그 결과 `git push --delete origin main`은 일반 push 분기(`destructiveness: 4`)로 떨어졌습니다. `DESTRUCTIVENESS_MAP`의 `'push-delete': 5`는 죽은 데이터였습니다.
|
|
73
|
+
- **보호 브랜치 판정이 슬래시를 포함한 이름에 오작동하고 있었습니다.** 술어가 마지막 `/` 앞부분을 잘라내어 `backup/main`과 `feat/release`가 보호 대상으로 읽혔습니다. 이제 알려진 ref 접두사(`refs/heads/`, `refs/tags/`, `refs/remotes/<remote>/`)만 제거합니다. 방향이 중요합니다——이 술어는 **되돌릴 수 없는** P0에 공급되며, 놓침에는 키워드/규칙/LLM 계층이라는 안전망이 있지만 오탐은 정당한 워크플로를 출구 없이 막습니다.
|
|
74
|
+
|
|
75
|
+
### 추가
|
|
76
|
+
|
|
77
|
+
- **보호 브랜치에 대한 원격 이력 재작성의 P0 하드 거부.** `main` / `master` / `production` / `release` / `stable`을 강제 덮어쓰기하거나 삭제하는 `git push`를 LLM 호출 이전에 결정론적으로 거부합니다. 기존 보호는 평면적이고 브랜치를 보지 않으며 네임스페이스로 덮어쓸 수 있는 키워드 `'push --force'`였습니다. 이것은 그 아래에 깔리는 협상 불가능한 바닥입니다.
|
|
78
|
+
- **"에스컬레이션 전용"은 관례가 아니라 구조입니다**: 헬퍼는 `string | undefined`를 반환하고 호출자는 이를 deny / 미결로 읽습니다. allow를 표현할 수단이 없으므로 P0에 배선해도 게이트가 허용하는 범위를 넓힐 수 없습니다.
|
|
79
|
+
- 의도적으로 **대상에서 제외**(키워드/규칙/LLM 계층이 계속 담당): 비보호 브랜치로의 force push, 브랜치명을 쓰지 않은 `git push --force`, 일반 push.
|
|
80
|
+
- 커맨드 파서(`command-dispatcher` / `command-semantics` / `parsers/git` / `parsers/shell-cmds`)의 첫 프로덕션 사용입니다. 지금까지는 자체 테스트 파일에서만 참조되고 있었습니다.
|
|
81
|
+
|
|
82
|
+
### 테스트
|
|
83
|
+
|
|
84
|
+
- 신규 `test/git-protected-push.spec.ts`(13건).
|
|
85
|
+
- **반증**: 세 수정을 각각 되돌리면 그것을 지키는 케이스만 빨개집니다(총 7건). 의도적으로 허용하는 케이스는 초록으로 남습니다.
|
|
86
|
+
- 전체 **40 파일 / 459건**, `typecheck` 클린.
|
|
87
|
+
|
|
8
88
|
## [2.0.0] - 2026-09-11
|
|
9
89
|
|
|
10
90
|
### 수정
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,327 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.6.0] - 2026-09-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Rule test (dry-run) in the settings card.** A new panel evaluates one would-be tool call
|
|
13
|
+
against the ruleset the gate currently has loaded and shows the verdict, the matched rule (index
|
|
14
|
+
and action), the dimensions that rule constrains, and the reason. Written for the question the
|
|
15
|
+
YAML cannot answer by inspection: *what will this actually do?* — `args` is an OR over tokens, an
|
|
16
|
+
empty dimension constrains nothing, and `command` matches the decomposed command **word** only, so
|
|
17
|
+
a plausible-looking rule can be dead on arrival.
|
|
18
|
+
|
|
19
|
+
The report keeps two answers apart on purpose. **`verdict`** is the policy result of the whole
|
|
20
|
+
chain (P0 hard-deny → deny-keyword → P1 grant → P2 rules → P4 ask → permissive).
|
|
21
|
+
**`ruleLayer`** is what the `permissions` chain decides on its own, and only that layer can name a
|
|
22
|
+
rule index: a P0 hard-deny or a preset deny-keyword fires *before* it and leaves no rule behind, so
|
|
23
|
+
the panel says so instead of attributing an unrelated rule. `matchedDimensions` likewise lists the
|
|
24
|
+
dimensions the matched rule *constrains* rather than the one that "caused" the match — dimensions
|
|
25
|
+
are ANDed, so a single cause cannot honestly be named. A `ruleCount` of 0 is surfaced as "0 rules
|
|
26
|
+
loaded, check the rulesFile path" rather than as "nothing matched".
|
|
27
|
+
|
|
28
|
+
- **`POST /api/dsh-perm-gate/dry-run`** — the panel's endpoint. **Read-only by construction**: it
|
|
29
|
+
has no write form, unknown body keys are dropped rather than forwarded, and it never touches
|
|
30
|
+
rules, grants, learning state or the settings namespace. Testing a rule must not be able to change
|
|
31
|
+
it. It evaluates against the **live** runtime rather than a fresh one, so the panel tests the rules
|
|
32
|
+
in force — a fresh runtime would re-resolve the chain from a different root and could silently
|
|
33
|
+
answer about a different file.
|
|
34
|
+
|
|
35
|
+
- **`src/dry-run.ts`** — `runDryRun` / `createDryRunRuntime`, the shared evaluator for the CLI and
|
|
36
|
+
the route, plus `PermGateRuntime.explainRules` (read-only rule-layer report) and
|
|
37
|
+
`PermGateRuntime.explainCall` (pure policy evaluation). `src/cli.ts` is now a thin wrapper over it;
|
|
38
|
+
its output is unchanged across nine replayed invocations, verified byte for byte (including the
|
|
39
|
+
`--list`, no-tool, bad-JSON and write-path cases).
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **The rule-test panel reported `allow` for calls the rules send to a human.** The first version
|
|
44
|
+
evaluated through the host-facing path, which applies the session layers — the preset stand-down
|
|
45
|
+
and the `approval: never` ask degradation. A session-less dry-run has no session, so the policy
|
|
46
|
+
reader answered "never", every `ask` degraded to a passthrough, and the panel showed **allow** for
|
|
47
|
+
exactly the rules someone opens it to review. Measured on the live host: `shell ls -la` showed
|
|
48
|
+
`allow` while the rule layer on the same card said `ask`.
|
|
49
|
+
|
|
50
|
+
`runDryRun` now reports the **pure policy** verdict from `explainCall`: the same
|
|
51
|
+
P0 → deny-keyword → P1 → P2 → P4 chain with the session-state layers excluded rather than guessed,
|
|
52
|
+
and with `reason` never collapsed to a bare `(default/passthrough)`. The host-facing view remains
|
|
53
|
+
available under `input.hostView`; the CLI asks for it, so its historic output stays byte for byte
|
|
54
|
+
identical (re-verified: 9/9 cases, SHA256 per case).
|
|
55
|
+
|
|
56
|
+
- **The "read-only" route was not read-only.** It ran the host-facing decision path against the live
|
|
57
|
+
runtime, which appends audit entries and records decision events — opening the rule-test panel
|
|
58
|
+
wrote to the live decision feed. The pure path appends nothing, and `test/dry-run.spec.ts` now
|
|
59
|
+
asserts that a call the host path *would* record leaves the audit mirror at zero.
|
|
60
|
+
|
|
61
|
+
## [2.5.0] - 2026-09-17
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- **`branch` rule dimension** — git branch / remote / protected-branch matching, so a rule can
|
|
66
|
+
finally say "no push to a protected branch" without catching unrelated commands. The decisive
|
|
67
|
+
case is the one `args` can never express: `git push --force origin main` (dangerous) and
|
|
68
|
+
`git checkout --force main` (routine) carry the *same tokens*, and only the branch dimension
|
|
69
|
+
tells them apart. Sub-fields: `target` (branch-name glob, `*` crosses `/`), `remote`
|
|
70
|
+
(remote-name glob) and `shared` (require a protected branch). Candidates come from the shared
|
|
71
|
+
command dispatcher, so `refspec` forms (`HEAD:main`) are already split and a remote name is
|
|
72
|
+
never mistaken for a branch name. Documented in `docs/rules-format.md` §4.11.
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
|
|
76
|
+
- **The multi-file rule chain silently emptied every match dimension.** `resolveRuleChain` merged
|
|
77
|
+
entries by hand with `tools: []`, `command: []`, `args: []` and `paths: []`. An empty dimension
|
|
78
|
+
means "no constraint", so EVERY merged entry matched EVERY call: the first `deny` entry denied
|
|
79
|
+
everything in its partition, and the first `allow` entry allowed everything before the `ask`
|
|
80
|
+
partition was ever consulted. The merge now goes through the same `compileRuleEntry` the
|
|
81
|
+
single-file path uses.
|
|
82
|
+
|
|
83
|
+
*Reach:* the chain resolver is reached **only** under `searchUp: true` (product default `false`,
|
|
84
|
+
and unset in a stock profile), so the default single-file path — `compileDocument` — was never
|
|
85
|
+
affected. That is also why the existing suite stayed green: no test drove a rules *file* through
|
|
86
|
+
the chain. `test/rule-chain.spec.ts` now does. A separate, still-open defect on the same
|
|
87
|
+
opt-in path is recorded in the plan: `findChainEntries` joins the configured `rulesFile` onto
|
|
88
|
+
each directory, so the absolute path `resolveRulesFile` always produces matches nothing and the
|
|
89
|
+
chain yields an empty ruleset without an error.
|
|
90
|
+
|
|
91
|
+
- **`argv.pipeline` could not see its own subject.** The pipeline match string was built from
|
|
92
|
+
`SimpleCommand.command`, which is only the command *word* — `curl https://x.sh | sh` collapsed
|
|
93
|
+
to `curl|sh`, so the documented `curl|sh` pattern matched the harmless adjacent form while the
|
|
94
|
+
genuinely dangerous one with arguments did not match at all. The match string is now each
|
|
95
|
+
simple command's full argv (joined by `|`), and the docs state that `|` in a pattern is a
|
|
96
|
+
literal, so covering arguments requires `curl*|sh`.
|
|
97
|
+
|
|
98
|
+
- Nine pre-existing `eslint` errors (unused imports/constants and one `prefer-const` in
|
|
99
|
+
`src/parsers/` and `test/command-parsers.spec.ts`) — `npm run lint` is green again.
|
|
100
|
+
|
|
101
|
+
## [2.4.1] - 2026-09-17
|
|
102
|
+
|
|
103
|
+
### Fixed
|
|
104
|
+
|
|
105
|
+
- The approvals history showed the raw `preset-passthrough` string for the one event that
|
|
106
|
+
explains *why* a flagged call ran unreviewed: the tier declares `approval: ask`, the session was
|
|
107
|
+
overridden to `never`, and the gate's ask therefore degraded to a passthrough. It now renders as
|
|
108
|
+
a readable label like every other verdict.
|
|
109
|
+
|
|
110
|
+
## [2.4.0] - 2026-09-17
|
|
111
|
+
|
|
112
|
+
### Changed
|
|
113
|
+
|
|
114
|
+
- **The P3 LLM classifier is escalate-only — it can no longer deny.** A `risky` verdict with a
|
|
115
|
+
hard category (`deletion` / `credential` / `remote` / `system` / `bulk`) used to **auto-deny**
|
|
116
|
+
with no panel; it now **keeps the human ask**. Denying is reserved for the deterministic layers
|
|
117
|
+
alone — P0 hard-deny, the deny-keyword blacklist, explicit `deny:` rules — because a
|
|
118
|
+
probabilistic verdict must not be able to hand down an unappealable block. Measured live: the
|
|
119
|
+
grader called a benign `git commit -F …` **`remote`** and the auto-deny left no panel to approve
|
|
120
|
+
and no grant to retry with; only a manual retry (which happened to grade `safe`) got past it.
|
|
121
|
+
This also aligns the code with the project's own rule that high-risk operations are intercepted
|
|
122
|
+
**deterministically, never on the LLM's judgement**.
|
|
123
|
+
|
|
124
|
+
### Added
|
|
125
|
+
|
|
126
|
+
- Hard risk categories keep one meaningful distinction from `neutral`: they are **never
|
|
127
|
+
learnable**. Repeated human approvals cannot sediment a `deletion`/`credential`/`remote`/
|
|
128
|
+
`system`/`bulk` verdict into an auto-allow (previously this was true only as a side effect of
|
|
129
|
+
the auto-deny; it is now an explicit property).
|
|
130
|
+
|
|
131
|
+
### Behaviour notes
|
|
132
|
+
|
|
133
|
+
- Under `approval: never` an ask the gate cannot deliver still degrades to passthrough, so a
|
|
134
|
+
classifier-flagged call now **runs** where it used to be auto-denied. That is the direct
|
|
135
|
+
consequence of "deny only what is deterministically dangerous, negotiate everything else": a
|
|
136
|
+
negotiation needs a human, and `never` means there is none. Run the gate in a tier whose
|
|
137
|
+
`approval` is `ask` for the flags to reach you.
|
|
138
|
+
|
|
139
|
+
## [2.3.0] - 2026-09-17
|
|
140
|
+
|
|
141
|
+
### Added
|
|
142
|
+
|
|
143
|
+
- **A stand-down is no longer silent.** When the session's permission preset is outside
|
|
144
|
+
`gatePresets`, the gate used to stand down and record nothing — so the tool call looked
|
|
145
|
+
exactly like one the gate had inspected and allowed. It now records **one** `stand-down`
|
|
146
|
+
notice per (session, preset) transition (never per call), naming the preset, the scope and
|
|
147
|
+
the fact that P0 hard-deny is inactive. The browser renders it as a sticky **GATE OFF**
|
|
148
|
+
strip above the input, and the approvals history shows a `Gate off` tag.
|
|
149
|
+
- The 自动审查 settings card states the gate's own preset scope (`gatePresets`, default
|
|
150
|
+
`permissive` / `permissive-full`) and what happens outside it, so the scope is visible
|
|
151
|
+
where the tier is configured.
|
|
152
|
+
|
|
153
|
+
### Changed
|
|
154
|
+
|
|
155
|
+
- **P0's documented positioning is scoped, not global.** P0 hard-deny is monotonic and
|
|
156
|
+
non-negotiable *within the gate's preset scope*; across presets the gate stands down
|
|
157
|
+
entirely — P0 included — because the selected tier's own policy owns that session. The
|
|
158
|
+
code always behaved this way; `AGENTS.md` and the four READMEs claimed otherwise, which
|
|
159
|
+
made `danger-full-access` read as "P0 still applies". Set `gatePresets: ['*']` to make P0
|
|
160
|
+
global again.
|
|
161
|
+
|
|
162
|
+
### Fixed
|
|
163
|
+
|
|
164
|
+
- `DEFAULT_GATE_PRESETS` / `resolveGatePresets` moved from `config.ts` (which imports
|
|
165
|
+
schemastery) into the dependency-free `preset.ts`, so the browser half can render the scope
|
|
166
|
+
without pulling a node-only dependency into the client bundle. `config.ts` re-exports both,
|
|
167
|
+
so existing imports are unchanged.
|
|
168
|
+
|
|
169
|
+
## [2.2.0] - 2026-09-17
|
|
170
|
+
|
|
171
|
+
### Fixed
|
|
172
|
+
|
|
173
|
+
- **P0 hard-deny skipped every shell tool but four.** `hardDenyReason` gated its shell
|
|
174
|
+
inspection on a locally defined regex, `/^(?:bash|pwsh|sh|cmd)$/`, which does **not**
|
|
175
|
+
match `shell`, `terminal` or `powershell`. `shell` is DSH's primary shell tool, so the
|
|
176
|
+
whole P0 shell check — the protected-path redirect guard — was **inert for the most
|
|
177
|
+
common call shape**. Measured, same command `echo x > /etc/passwd`: blocked via `bash`,
|
|
178
|
+
**allowed** via `shell`. `engine.ts` now imports `SHELL_TOOLS` from `evaluate.ts`; the
|
|
179
|
+
gate held three copies of that one fact and only one of them was complete.
|
|
180
|
+
- **`git push --delete` was parsed as an ordinary push.** `hasDelete` was computed in
|
|
181
|
+
`analyzeSubcommand` but only ever read in the `branch` case, so `git push --delete
|
|
182
|
+
origin main` fell through to the plain-push branch (`destructiveness: 4`) and the
|
|
183
|
+
`DESTRUCTIVENESS_MAP` entry `'push-delete': 5` was dead data.
|
|
184
|
+
- **Protected-branch detection mis-fired on any name containing a slash.** The predicate
|
|
185
|
+
stripped everything before the last `/`, so `backup/main` and `feat/release` were read
|
|
186
|
+
as protected. It now strips only known ref prefixes (`refs/heads/`, `refs/tags/`,
|
|
187
|
+
`refs/remotes/<remote>/`). The direction matters: this predicate feeds an **unremovable**
|
|
188
|
+
P0, where a miss still has the keyword / rule / LLM layers behind it, while a false
|
|
189
|
+
positive blocks a legitimate workflow with no recourse.
|
|
190
|
+
|
|
191
|
+
### Added
|
|
192
|
+
|
|
193
|
+
- **P0 hard-deny for remote-history rewrite on a protected branch.** A `git push` that
|
|
194
|
+
force-overwrites or deletes `main` / `master` / `production` / `release` / `stable` is
|
|
195
|
+
rejected deterministically, before any LLM call. The pre-existing protection was the
|
|
196
|
+
flat, branch-blind, namespace-overridable keyword `'push --force'`; this is the
|
|
197
|
+
non-negotiable floor beneath it.
|
|
198
|
+
- **Escalate-only by construction, not by convention**: the helper returns
|
|
199
|
+
`string | undefined`, which the caller reads as deny / undecided. It has no way to
|
|
200
|
+
express allow, so wiring it into P0 cannot widen what the gate permits.
|
|
201
|
+
- Deliberately **not** covered (still left to the keyword / rule / LLM layers):
|
|
202
|
+
force-push to a non-protected branch, `git push --force` with no branch named, and
|
|
203
|
+
ordinary pushes.
|
|
204
|
+
- First production use of the command parser (`command-dispatcher`, `command-semantics`,
|
|
205
|
+
`parsers/git`, `parsers/shell-cmds`), which until now was referenced only by its own
|
|
206
|
+
test file.
|
|
207
|
+
|
|
208
|
+
### Tests
|
|
209
|
+
|
|
210
|
+
- New suite `test/git-protected-push.spec.ts` (13 cases): parser variants, the
|
|
211
|
+
protected-branch predicate in both directions, full `SHELL_TOOLS` coverage, the four
|
|
212
|
+
deliberately-allowed shapes, and compound-command segmentation
|
|
213
|
+
(`git status && git push --force origin main`).
|
|
214
|
+
- **Falsified**: reverting each of the three fixes turns exactly the cases guarding it red
|
|
215
|
+
(7 failures in total), while the deliberate-allow cases stay green.
|
|
216
|
+
- Full suite **40 files / 459 cases**; `typecheck` clean.
|
|
217
|
+
|
|
218
|
+
## [2.1.2] - 2026-09-15
|
|
219
|
+
|
|
220
|
+
### Added
|
|
221
|
+
|
|
222
|
+
- **The composer glyph patch now ships with the package.** `scripts/patch-permission-glyph.mjs`
|
|
223
|
+
is whitelisted in `files` and exposed as the **`dsh-perm-gate-patch-glyph`** bin, so re-applying
|
|
224
|
+
the host-bundle patch after a DSH upgrade no longer needs a source checkout:
|
|
225
|
+
|
|
226
|
+
```sh
|
|
227
|
+
npx dsh-perm-gate-patch-glyph # apply
|
|
228
|
+
npx dsh-perm-gate-patch-glyph --check # report only; exits 1 if the glyph is missing
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
In a checkout, `npm run patch:glyph` and `npm run patch:glyph:check` do the same.
|
|
232
|
+
|
|
233
|
+
It is deliberately **not** a `postinstall`. The script edits a **host** package, and a plugin
|
|
234
|
+
must not rewrite the harness it is installed into without being asked. (pnpm 10+ blocks
|
|
235
|
+
install scripts by default unless they are allowlisted, so a `postinstall` would also have
|
|
236
|
+
been a promise that silently never runs — worse than an explicit command.)
|
|
237
|
+
|
|
238
|
+
### Changed
|
|
239
|
+
|
|
240
|
+
- The script is now testable: its pure parts are exported (`sliceEntry`, `applyGlyphPatch`,
|
|
241
|
+
`candidateBundles`, `findBundle`) and pinned by `test/glyph-patch.spec.ts` (11 cases). The
|
|
242
|
+
bracket-balance invariant is the one that matters — the first version of the slicer scanned
|
|
243
|
+
from the wrong bracket and produced an unparseable bundle, which only the script's own
|
|
244
|
+
`node --check` guard caught.
|
|
245
|
+
- `candidateBundles` additionally probes the hoisted per-profile scope under `$DSH_HOME`, and the
|
|
246
|
+
CLI is a no-op when imported (the main block runs only when the file is the entry point).
|
|
247
|
+
|
|
248
|
+
## [2.1.1] - 2026-09-15
|
|
249
|
+
|
|
250
|
+
### Added
|
|
251
|
+
|
|
252
|
+
- **Network execution surface (opt-in, off by default).** A local HTTP/CONNECT proxy adjudicates
|
|
253
|
+
the outbound traffic of *shell subprocesses* against the same rules file, plus an interactive
|
|
254
|
+
approval path for targets no rule covers. A `deny` rule is never escalated — approval widens
|
|
255
|
+
reach for an unlisted target, but can never override a rule that says no. New config:
|
|
256
|
+
`networkEnabled` (default `false`), `networkMode`, `networkUnlisted`, `networkUnattributed`,
|
|
257
|
+
`networkBind`, `networkPort`, `networkNoProxy`, `networkInjectEnv`, `networkAskTimeoutMs`,
|
|
258
|
+
`networkGrantTtlMs`. Diagnostics at `GET /api/dsh-perm-gate/network`. Covered by
|
|
259
|
+
`test/network.spec.ts`, `test/proxy-errors.spec.ts`, `test/network-lifecycle.spec.ts` and
|
|
260
|
+
`test/network-approval.spec.ts`.
|
|
261
|
+
- **Two 自动审查 tiers.** `permissive` keeps the built-in file sandbox; `permissive-full`
|
|
262
|
+
(label 自动审查(高权限)) pairs the same approval behaviour with `danger-full-access`. A preset's
|
|
263
|
+
`sandbox` and `approval` are independent knobs, and coupling them forced a trade: the
|
|
264
|
+
`workspace-write` sandbox also denies the named pipes a child process needs to start, so
|
|
265
|
+
`git clone`, MSYS2/Cygwin `sh.exe` and ConPTY failed under the only tier the gate was active in.
|
|
266
|
+
Both tiers are in the default `gatePresets`.
|
|
267
|
+
- **Rule model expansion.** Six new match dimensions (`params` / `absent` / `agents` / `when` /
|
|
268
|
+
`argv` / `network`), a multi-file rule chain with `searchUp` and a fallback path, and shadow
|
|
269
|
+
detection for unreachable rules. New config: `searchUp`, `fallbackPath`, `badFilePolicy`,
|
|
270
|
+
`maxChainLength`.
|
|
271
|
+
- **Rule hot reload.** A chokidar watcher reloads the effective rule files, with debounce, LRU
|
|
272
|
+
eviction across workspaces, and candidate-file watching through the deepest existing ancestor so
|
|
273
|
+
a rules file created mid-session is adopted. New config: `watch` (default `true`),
|
|
274
|
+
`watchDebounceMs`.
|
|
275
|
+
|
|
276
|
+
### Fixed
|
|
277
|
+
|
|
278
|
+
- **A blocked connection could kill the host.** When the proxy answered a CONNECT with 403, the
|
|
279
|
+
client's RST produced an `ECONNRESET` with no handler attached yet, which escalated to an
|
|
280
|
+
unhandled `'error'` event and terminated the DSH process. Socket error handlers are now attached
|
|
281
|
+
at connection time, with `clientError` and handler-rejection fallbacks behind them. A policy
|
|
282
|
+
proxy must never take down its host.
|
|
283
|
+
- **Built-in tools prompted for approval.** The auto-allow roster was hand-maintained, so tools
|
|
284
|
+
added to DSH since drifted out of it and a read-only call could raise an approval prompt:
|
|
285
|
+
`advanced_search`, `platform_search`, `free_search_test`, `context_compression_retrieve`,
|
|
286
|
+
`memory_search_graph`, `memory_expand_graph_node`, `memory_import`, `memory_ruminate`,
|
|
287
|
+
`memory_ruminate_cancel`, `memory_ruminate_status`.
|
|
288
|
+
- **Proxy lifecycle races.** `close()` now awaits an in-flight bind instead of racing it, is
|
|
289
|
+
bounded, and concurrent `start()` calls share one bind. DNS lookups are time-bounded, the
|
|
290
|
+
connection-setup phase is bounded, concurrent connections are capped, and a throwing logger can
|
|
291
|
+
no longer escalate into a crash. Proxy teardown is registered before the bind is awaited, so an
|
|
292
|
+
early dispose cannot leak a bound port or a rewritten `process.env`.
|
|
293
|
+
- **`permissive-full` had no icon in the composer picker.** The picker's glyph map is closed and,
|
|
294
|
+
by its own comment, gives host-configured names none, so 自动审查(高权限)rendered text-only
|
|
295
|
+
while 自动审查 showed shield+eye. `scripts/patch-permission-glyph.mjs` adds the missing entry to
|
|
296
|
+
that **host** map: idempotent, bracket-depth slicing, and it `node --check`s its own output and
|
|
297
|
+
restores the backup on failure. It patches a host package, so it must be re-run after every DSH
|
|
298
|
+
upgrade — reinstalling the plugin does **not** restore it, because `dsh plugin … add` only writes
|
|
299
|
+
the profile's own `node_modules`. Documented in all four INSTALL guides.
|
|
300
|
+
|
|
301
|
+
### Changed
|
|
302
|
+
|
|
303
|
+
- `networkUnattributed` defaults to `allow`: traffic with no shell attribution is DSH's own client
|
|
304
|
+
(a built-in network tool, the LLM transport), and reviewing it would let the host block itself.
|
|
305
|
+
Set `deny` for the stricter behaviour.
|
|
306
|
+
- `networkUnlisted` defaults to `ask`.
|
|
307
|
+
- `DEFAULT_GATE_PRESETS` is now `['permissive', 'permissive-full']`.
|
|
308
|
+
|
|
309
|
+
### Documented
|
|
310
|
+
|
|
311
|
+
- **The network proxy is a cooperative policy layer, not an enforcement boundary.** It only sees
|
|
312
|
+
clients that read the proxy environment. Measured, not assumed: `curl` routes through it, while
|
|
313
|
+
`node` `http`/`https`/`fetch` connect directly, as do raw sockets, DNS, QUIC, literal-IP targets
|
|
314
|
+
and Java/.NET default clients. Stated in all four READMEs.
|
|
315
|
+
|
|
316
|
+
- **Session sweep — the authorization chain now follows the session lifecycle.** On plugin startup
|
|
317
|
+
and every hour, the gate reads DSH's workspace store (`$DSH_HOME/storages/workspace.json`,
|
|
318
|
+
read-only) and classifies every session it holds gate data for. Sessions DSH has archived
|
|
319
|
+
(`global.archivedSessionIds`) or no longer tracks at all have their decision events dropped from
|
|
320
|
+
`$DSH_HOME/perm-gate/events.jsonl` (atomic tmp+rename rewrite, only when something is removed)
|
|
321
|
+
and their pre-change snapshot files deleted from `$DSH_HOME/perm-gate/snapshots/`. Live sessions
|
|
322
|
+
are untouched; unattributable rows (empty session id, unparsable line/file) are never deleted;
|
|
323
|
+
every I/O failure is fail-open (the round is skipped and retried an hour later); the hourly
|
|
324
|
+
timer is `unref`'d and disposed with the plugin. New config: `sessionSweep` (default `true`),
|
|
325
|
+
`workspaceStoreFile` (default `<dshHome>/storages/workspace.json`). Note that restoring an
|
|
326
|
+
archived session does not restore its swept history. Covered by `test/session-sweep.spec.ts`
|
|
327
|
+
(13 unit cases) and `test/session-sweep-apply.spec.ts` (end-to-end wiring).
|
|
328
|
+
|
|
8
329
|
## [2.0.0] - 2026-09-11
|
|
9
330
|
|
|
10
331
|
### Fixed
|
package/INSTALL.ja.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- [日本語 changelog](./CHANGELOG.ja.md)
|
|
13
13
|
- [한국어 changelog](./CHANGELOG.ko.md)
|
|
14
14
|
|
|
15
|
-
`dsh-perm-gate` バージョン **2.
|
|
15
|
+
`dsh-perm-gate` バージョン **2.6.0**。判定チェーン、ルールファイル形式、自动审查
|
|
16
16
|
ティアについては [日本語 README](./README.ja.md) を参照してください。
|
|
17
17
|
|
|
18
18
|
## 必要条件
|
|
@@ -23,19 +23,20 @@
|
|
|
23
23
|
- インストール先の profile。ブラウザ側は `web` profile 向けにのみビルドされます
|
|
24
24
|
(`package.json` の `dsh.client.platform = "web"`)。
|
|
25
25
|
|
|
26
|
-
## DSH
|
|
26
|
+
## DSH のバージョンに合うラインを選ぶ
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
2 つの長期ブランチが 2 つの DSH ラインを担当します:`main`(2.x)と
|
|
29
|
+
`sync/0.1.5-from-main`(3.x、DSH 0.1.5 のパッケージング変体 —— `compat/0.1.5` は
|
|
30
|
+
同じコードツリーを指します)。
|
|
31
31
|
|
|
32
32
|
| DSH のバージョン | インストール方法 |
|
|
33
33
|
|-----------------|-----------------|
|
|
34
|
-
| `0.1.2-alpha.1`
|
|
34
|
+
| `0.1.2-alpha.1` – `0.1.5-rc.0` | `dsh plugin --profile web add dsh-perm-gate`(tag `latest`、2.x ライン) |
|
|
35
|
+
| `0.1.5-rc.1` 以上 | `dsh plugin --profile web add github:drscrewdriver/dsh-perm-gate#sync/0.1.5-from-main`(3.x ライン;npm の dist-tag は未公開 —— `@latest` は同じコードの 2.x パッケージングをインストールします) |
|
|
35
36
|
| `0.1.1-rc.2` まで | `dsh plugin --profile web add dsh-perm-gate@legacy` |
|
|
36
37
|
|
|
37
|
-
DSH は `engines.dsh` を強制しないため、tag
|
|
38
|
-
|
|
38
|
+
DSH は `engines.dsh` を強制しないため、tag と ref は選択メカニズムであり、互換性
|
|
39
|
+
ゲートそのものではありません。ラインと tag の公開レイアウトについては
|
|
39
40
|
[RELEASING.md](./RELEASING.md) を参照してください。
|
|
40
41
|
|
|
41
42
|
## 公式 CLI でインストール
|
|
@@ -72,6 +73,11 @@ profile の `cordis.yml` にプラグインを追加します:
|
|
|
72
73
|
defaultAction: ask # allow | ask | deny
|
|
73
74
|
```
|
|
74
75
|
|
|
76
|
+
2.6.0 から、ルール本体は `dsh-perm-gate-rules` settings 名前空間に保存され、変更の
|
|
77
|
+
たびにゲートがその場で再コンパイルされます。上記の YAML ファイルは任意になりました。
|
|
78
|
+
名前空間が未設定の間だけ読み込まれ、プラグインが最初にルールを書き込む際(許可リスト
|
|
79
|
+
の承認、または設定 UI での編集)にファイルのルールが自動的に取り込まれます。
|
|
80
|
+
|
|
75
81
|
まずは [examples/permissions.example.yaml](./examples/permissions.example.yaml)
|
|
76
82
|
から始め、profile を再読込してください。
|
|
77
83
|
|
|
@@ -87,6 +93,56 @@ dsh plugin --profile web update dsh-perm-gate
|
|
|
87
93
|
dsh profile reload --profile web
|
|
88
94
|
```
|
|
89
95
|
|
|
96
|
+
## **DSH** アップグレード後:入力欄アイコンのパッチを再適用
|
|
97
|
+
|
|
98
|
+
「自动审查(高权限)」が「自动审查」と同じ盾+目のアイコンを表示するのは、
|
|
99
|
+
`scripts/patch-permission-glyph.mjs` が **DSH ホストパッケージ内の閉じた Map** に
|
|
100
|
+
その項目を追加したからにすぎません。DSH はプラグイン提供ティアに**設計上アイコンを
|
|
101
|
+
与えません** —— その Map 自身のコメントが *"host-configured names outside the design
|
|
102
|
+
set get none"* と述べています。プラグインが影響できる option オブジェクトは
|
|
103
|
+
`{value, name, description}` しか運ばないため、代わりに使えるプラグイン側のシームは
|
|
104
|
+
存在しません。
|
|
105
|
+
|
|
106
|
+
このパッチは**ホスト**のファイルを書き換えるので、**DSH のアップグレードや再インストールで
|
|
107
|
+
失われます**。*本プラグイン*のアップグレードでは失われません。アイコンは元々プラグインの
|
|
108
|
+
ものではなく、プラグイン自身の寄与(`cordis.patch.yml` の `name:` / `description:`)は
|
|
109
|
+
パッケージに同梱されています。
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
npx dsh-perm-gate-patch-glyph # パッチを適用
|
|
113
|
+
npx dsh-perm-gate-patch-glyph --check # 確認のみ。グリフが失われていれば終了コード 1
|
|
114
|
+
dsh profile reload --profile web
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
スクリプトは**このパッケージに同梱**されています —— `scripts/patch-permission-glyph.mjs`
|
|
118
|
+
としても、`dsh-perm-gate-patch-glyph` bin としても —— ソースの checkout は不要です。
|
|
119
|
+
**意図的に `postinstall` には接続していません**:これはホストパッケージを書き換えるもので、
|
|
120
|
+
プラグインが許可なく自分の harness を書き換えてよい理由はありません。適用の有無にかかわらず
|
|
121
|
+
DSH の動作に影響はなく、ティア自体はそのまま機能します。
|
|
122
|
+
|
|
123
|
+
このスクリプトは冪等(2 回目は no-op)で、バックアップは 1 度だけ取り、壊れたスライスを
|
|
124
|
+
書き込もうとしません —— 結果に対して `node --check` を実行し、失敗すればバックアップを
|
|
125
|
+
復元します —— したがって DSH アップグレードのたびに無条件で再実行して安全です。
|
|
126
|
+
実行中の `node` バイナリからパッケージを逆算するため、nvm のバージョン変更や
|
|
127
|
+
インストール symlink の貼り替えでも壊れません。
|
|
128
|
+
|
|
129
|
+
**プラグインを再インストールしてもアイコンは戻りません。**
|
|
130
|
+
`dsh plugin --profile web add …` は profile ディレクトリ内の pnpm に引数を転送し、
|
|
131
|
+
profile 自身の `node_modules` しか書き換えません(`-w` = `--workspace-root` ですが、
|
|
132
|
+
この profile の workspace は `packages: ['.']` そのものなので関係ありません)。
|
|
133
|
+
アイコンは DSH インストール側にあります。標準的なインストールでは、以下の 3 つの
|
|
134
|
+
パスは 3 つのコピーではなく **1 つの物理ファイル**です:
|
|
135
|
+
|
|
136
|
+
| パス | 実体 |
|
|
137
|
+
|------|------|
|
|
138
|
+
| `dirname(node)/node_modules/@deepseek-ai/dsh` | DSH インストール先(symlink のことがある) |
|
|
139
|
+
| `<profile>/node_modules/@deepseek-ai/dsh-client-ui-conversation` | そこへの**ジャンクション** |
|
|
140
|
+
| `<dsh>/node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/client.js` | パッチ対象のファイル |
|
|
141
|
+
|
|
142
|
+
見るべき症状:ティアは動作しゲートも効いたままですが、入力欄のドロップダウンの行に
|
|
143
|
+
アイコンがなく、折りたたまれたトリガーは他のティアがアイコン+文字なのに対して
|
|
144
|
+
文字だけになります。
|
|
145
|
+
|
|
90
146
|
## 分割プラグインからの移行
|
|
91
147
|
|
|
92
148
|
`dsh-perm-gate` は、これまで `dsh-permission-rules` / `dsh-auto-mode` /
|
|
@@ -144,6 +200,12 @@ DSH の bundle patch は `permission.config.presets` を key 単位でマージ
|
|
|
144
200
|
します。profile を再読込して `cordis.patch.yml` を再適用し、後から読み込まれる
|
|
145
201
|
プラグインが `presets` を上書きしていないか確認してください。
|
|
146
202
|
|
|
203
|
+
**「自动审查(高权限)」の入力欄アイコンが消えた。**
|
|
204
|
+
DSH のアップグレードまたは再インストールが、パッチを当てたホスト bundle を置き換え
|
|
205
|
+
ました。プラグインを再インストールしても戻りません。
|
|
206
|
+
`npx dsh-perm-gate-patch-glyph` を実行して再読込してください。
|
|
207
|
+
ティア自体は影響を受けません —— パッチなしでもラベルとゲートは動作します。
|
|
208
|
+
|
|
147
209
|
**ルールファイルがあるのに `--list` が `ruleCount: 0` を返す。**
|
|
148
210
|
`rulesFile` はプラグイン ディレクトリではなく Harness プロセスの CWD から解決されます。
|
|
149
211
|
絶対パスを使うか、シェルの CWD を確認してください。不正なドキュメントは読み込み時に
|