dsh-perm-gate 3.0.0 → 4.1.0-beta.1
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/INSTALL.ja.md +8 -14
- package/INSTALL.ko.md +9 -15
- package/INSTALL.md +8 -16
- package/INSTALL.zh.md +7 -12
- package/README.ja.md +2 -3
- package/README.ko.md +2 -3
- package/README.md +1 -2
- package/README.zh.md +2 -3
- package/lib/client.js +160 -51
- package/lib/client.js.map +1 -1
- package/lib/config.d.ts +3 -0
- package/lib/config.js +37 -1
- package/lib/events.d.ts +35 -0
- package/lib/events.js +128 -0
- package/lib/index.js +44 -86
- package/package.json +10 -9
- package/docs/plan-rules-to-settings.md +0 -217
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
# Plan: rules.yml → DSH Settings Namespace Migration
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
|
|
5
|
-
Eliminate the external `rules.yml` file dependency by migrating rules into the DSH settings namespace (`dsh-perm-gate.rules`). This solves the cold-start directory problem at the root cause: settings are managed by the framework and loaded before any plugin `apply()` runs.
|
|
6
|
-
|
|
7
|
-
## Current Architecture
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
User edits rules.yml (or allowlist card writes to it)
|
|
11
|
-
↓
|
|
12
|
-
resolveRulesFile() → path string
|
|
13
|
-
↓
|
|
14
|
-
readFileSafe() → raw YAML text
|
|
15
|
-
↓
|
|
16
|
-
parsePermissionsDocument() → structured doc
|
|
17
|
-
↓
|
|
18
|
-
compileDocument() → CompiledRuleset
|
|
19
|
-
↓
|
|
20
|
-
PermGateRuntime.decide() → verdict
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Read-only view:
|
|
24
|
-
```
|
|
25
|
-
GET /api/dsh-perm-gate/rules
|
|
26
|
-
→ readRulesView(rulesFile)
|
|
27
|
-
→ statSync + readFileSync + parse + compile
|
|
28
|
-
→ RulesView { path, exists, bytes, hash, raw, counts, error }
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Write path (allowlist card):
|
|
32
|
-
```
|
|
33
|
-
appendAllowCommand() / replaceAllowCommands()
|
|
34
|
-
→ readFileSync → parse → mutate → stringify → writeFileSync
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Target Architecture
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
User edits via settings card (or API)
|
|
41
|
-
↓
|
|
42
|
-
settings.get('dsh-perm-gate.rules') → structured object (already parsed)
|
|
43
|
-
↓
|
|
44
|
-
compileDocument(rules) → CompiledRuleset
|
|
45
|
-
↓
|
|
46
|
-
PermGateRuntime.decide() → verdict
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Read-only view:
|
|
50
|
-
```
|
|
51
|
-
GET /api/dsh-perm-gate/rules
|
|
52
|
-
→ settings.get('dsh-perm-gate.rules')
|
|
53
|
-
→ RulesView { source: 'settings', rules: {...}, counts, hash }
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Write path (allowlist card):
|
|
57
|
-
```
|
|
58
|
-
settings.update({ rules: { allow: [...] } })
|
|
59
|
-
→ framework persists to settings.yaml
|
|
60
|
-
→ settings.watch() triggers reload
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Schema (config.ts)
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
// New settings schema for rules
|
|
67
|
-
const RuleEntrySchema = z.object({
|
|
68
|
-
command: z.array(z.string()).optional(),
|
|
69
|
-
args: z.array(z.string()).optional(),
|
|
70
|
-
tools: z.array(z.string()).optional(),
|
|
71
|
-
network: z.object({
|
|
72
|
-
domains: z.array(z.string()),
|
|
73
|
-
}).optional(),
|
|
74
|
-
reason: z.string().default(''),
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
const RulesSchema = z.object({
|
|
78
|
-
defaultAction: z.enum(['allow', 'deny', 'ask']).default('ask'),
|
|
79
|
-
deny: z.array(RuleEntrySchema).default([]),
|
|
80
|
-
allow: z.array(RuleEntrySchema).default([]),
|
|
81
|
-
ask: z.array(RuleEntrySchema).default([]),
|
|
82
|
-
network: z.object({
|
|
83
|
-
domains: z.array(z.string()).default([]),
|
|
84
|
-
}).optional(),
|
|
85
|
-
}).default({ defaultAction: 'ask', deny: [], allow: [] })
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## Migration Steps
|
|
89
|
-
|
|
90
|
-
### Phase 1: Dual-Source (backward compatible)
|
|
91
|
-
|
|
92
|
-
1. **Add `rules` to settings schema** — new field in `dsh-perm-gate` namespace
|
|
93
|
-
2. **Runtime reads from settings first, falls back to file** — `settings.get('rules') ?? loadRulesFile()`
|
|
94
|
-
3. **Allowlist card writes to settings** — `settings.update({ rules: { allow: [...] } })`
|
|
95
|
-
4. **Rules view serves from settings** — `readRulesView()` reads from settings object, not file
|
|
96
|
-
5. **Module-level: still create dataDir** — belt-and-suspenders for the transition period
|
|
97
|
-
|
|
98
|
-
### Phase 2: File Deprecation
|
|
99
|
-
|
|
100
|
-
6. **Log deprecation warning** — when `rulesFile` is configured, warn that it's deprecated
|
|
101
|
-
7. **Import button** — UI can import existing `rules.yml` into settings
|
|
102
|
-
8. **Export button** — UI can export settings rules as YAML for version control
|
|
103
|
-
|
|
104
|
-
### Phase 3: File Removal
|
|
105
|
-
|
|
106
|
-
9. **Remove `rulesFile` config option** — no longer accepted
|
|
107
|
-
10. **Remove file I/O code** — `allowlist.ts`, `rules-view.ts` file operations
|
|
108
|
-
11. **Remove chokidar watcher** — settings has built-in `watch()`
|
|
109
|
-
12. **Remove dataDir bootstrap** — no longer needed
|
|
110
|
-
|
|
111
|
-
## How the Read-Only View Changes
|
|
112
|
-
|
|
113
|
-
### Before (file-based)
|
|
114
|
-
|
|
115
|
-
```json
|
|
116
|
-
{
|
|
117
|
-
"path": "C:\\Users\\joshua\\.dsh\\perm-gate\\rules.yml",
|
|
118
|
-
"exists": true,
|
|
119
|
-
"bytes": 1513,
|
|
120
|
-
"hash": "abc123...",
|
|
121
|
-
"lines": 61,
|
|
122
|
-
"raw": "permissions:\n defaultAction: ask\n deny:\n ...",
|
|
123
|
-
"truncated": false,
|
|
124
|
-
"defaultAction": "ask",
|
|
125
|
-
"counts": { "allow": 11, "deny": 1, "ask": 0 },
|
|
126
|
-
"error": null
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### After (settings-based)
|
|
131
|
-
|
|
132
|
-
```json
|
|
133
|
-
{
|
|
134
|
-
"source": "settings",
|
|
135
|
-
"defaultAction": "ask",
|
|
136
|
-
"counts": { "allow": 11, "deny": 1, "ask": 0 },
|
|
137
|
-
"hash": "abc123...",
|
|
138
|
-
"rules": {
|
|
139
|
-
"defaultAction": "ask",
|
|
140
|
-
"deny": [
|
|
141
|
-
{ "tools": ["shell", "pwsh"], "command": ["rm#recursive"], "reason": "destructive" }
|
|
142
|
-
],
|
|
143
|
-
"allow": [
|
|
144
|
-
{ "command": ["grep"], "reason": "permissive allowlist" }
|
|
145
|
-
],
|
|
146
|
-
"network": {
|
|
147
|
-
"domains": ["github.com", "registry.npmjs.org"]
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
**Key differences:**
|
|
154
|
-
- `path` / `exists` / `bytes` / `raw` / `truncated` → removed (no file)
|
|
155
|
-
- `source: 'settings'` → added (provenance)
|
|
156
|
-
- `rules` → the structured object (already parsed, not raw YAML)
|
|
157
|
-
- `counts` / `defaultAction` / `hash` → kept (useful for UI)
|
|
158
|
-
|
|
159
|
-
## How the Allowlist Write Path Changes
|
|
160
|
-
|
|
161
|
-
### Before (file-based)
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
// appendAllowCommand reads YAML, mutates, writes back
|
|
165
|
-
appendAllowCommand(rulesFile, 'git push', 'permissive allow-everywhere')
|
|
166
|
-
// → readFileSync → parse → push → stringify → writeFileSync
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### After (settings-based)
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
// Direct settings update
|
|
173
|
-
const current = settings.get('dsh-perm-gate.rules')
|
|
174
|
-
const allow = [...(current.allow ?? []), { command: ['git push'], reason: 'permissive allow-everywhere' }]
|
|
175
|
-
settings.update({ rules: { ...current, allow } })
|
|
176
|
-
// → framework persists → settings.watch() triggers reload
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## Files Changed
|
|
180
|
-
|
|
181
|
-
| File | Change |
|
|
182
|
-
|---|---|
|
|
183
|
-
| `src/config.ts` | Add `RulesSchema`, keep `resolveRulesFile` for backward compat |
|
|
184
|
-
| `src/index.ts` | Read from settings first, fall back to file; register settings schema |
|
|
185
|
-
| `src/runtime.ts` | Accept `rules` object directly (no file read) |
|
|
186
|
-
| `src/rules-view.ts` | Add `readRulesViewFromSettings()` |
|
|
187
|
-
| `src/allowlist.ts` | Add `appendAllowToSettings()`, `replaceAllowInSettings()` |
|
|
188
|
-
| `src/events.ts` | Update `registerRulesRoute` to use settings view |
|
|
189
|
-
| `src/watch.ts` | Remove chokidar watcher (settings has built-in watch) |
|
|
190
|
-
|
|
191
|
-
## Cold-Start Resilience (All Plugins)
|
|
192
|
-
|
|
193
|
-
The same pattern should be applied to other plugins that read files at startup:
|
|
194
|
-
|
|
195
|
-
| Plugin | Data | Risk | Fix |
|
|
196
|
-
|---|---|---|---|
|
|
197
|
-
| `dsh-perm-gate` | rules.yml | HIGH | Settings migration (this plan) |
|
|
198
|
-
| `dsh-search-index` | index files | MEDIUM | dataDir bootstrap + try/catch |
|
|
199
|
-
| `dsh-session-guard` | guard config | LOW | Already settings-backed |
|
|
200
|
-
| `dsh-prime-memory` | memory files | MEDIUM | dataDir bootstrap + try/catch |
|
|
201
|
-
| `dsh-context-compression` | config | LOW | Already settings-backed |
|
|
202
|
-
|
|
203
|
-
**Universal pattern:**
|
|
204
|
-
1. Module-level `ensureDataDir()` — belt
|
|
205
|
-
2. Read paths: `dataDirReady()` check → empty defaults if missing
|
|
206
|
-
3. Write paths: `ensureDataDir()` before first write
|
|
207
|
-
4. All file reads wrapped in try/catch with graceful fallback
|
|
208
|
-
|
|
209
|
-
## Testing
|
|
210
|
-
|
|
211
|
-
- [ ] Unit: `compileDocument` with settings-sourced rules (same as YAML-sourced)
|
|
212
|
-
- [ ] Unit: `readRulesViewFromSettings` returns correct `RulesView`
|
|
213
|
-
- [ ] Unit: `appendAllowToSettings` correctly mutates settings object
|
|
214
|
-
- [ ] Integration: settings card reads and displays rules
|
|
215
|
-
- [ ] Integration: allowlist card writes persist to settings
|
|
216
|
-
- [ ] Migration: existing `rules.yml` imports into settings correctly
|
|
217
|
-
- [ ] Cold start: plugin loads with empty settings (no rules configured)
|