dsh-permission-rules 0.4.2 → 0.5.2
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.md +34 -0
- package/README.es.md +160 -117
- package/README.hi.md +162 -119
- package/README.md +133 -90
- package/README.pt.md +161 -118
- package/README.zh.md +162 -119
- package/docs/rule-test-vectors/README.md +28 -0
- package/docs/rule-test-vectors/corpus.json +109 -0
- package/lib/client.js +4693 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +1530 -179
- package/lib/typert.host.js +4305 -0
- package/lib/types/client/index.d.ts +28 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/locales.d.ts +9 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/remote.d.ts +181 -0
- package/lib/types/client/remote.d.ts.map +1 -0
- package/lib/types/client/section.d.ts +35 -0
- package/lib/types/client/section.d.ts.map +1 -0
- package/lib/types/config.d.ts +54 -2
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/events.d.ts +45 -0
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/index.d.ts +8 -6
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/network.d.ts +90 -0
- package/lib/types/network.d.ts.map +1 -0
- package/lib/types/prose.d.ts +15 -0
- package/lib/types/prose.d.ts.map +1 -1
- package/lib/types/proxy.d.ts +128 -0
- package/lib/types/proxy.d.ts.map +1 -0
- package/lib/types/remote-service.d.ts +45 -0
- package/lib/types/remote-service.d.ts.map +1 -0
- package/lib/types/rules.d.ts +131 -0
- package/lib/types/rules.d.ts.map +1 -1
- package/lib/types/runtime.d.ts +153 -21
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/settings.d.ts +28 -0
- package/lib/types/settings.d.ts.map +1 -0
- package/lib/types/typert.host.d.ts +166 -0
- package/lib/types/typert.host.d.ts.map +1 -0
- package/lib/types/wire.d.ts +421 -0
- package/lib/types/wire.d.ts.map +1 -0
- package/package.json +182 -144
- package/scripts/repair-session-logs.mjs +7 -2
- package/src/client/index.ts +111 -0
- package/src/client/locales.ts +94 -0
- package/src/client/remote.ts +41 -0
- package/src/client/section.ts +201 -0
- package/src/config.ts +111 -0
- package/src/events.ts +46 -0
- package/src/index.ts +2 -0
- package/src/network.ts +177 -0
- package/src/prose.ts +85 -10
- package/src/proxy.ts +327 -0
- package/src/remote-service.ts +109 -0
- package/src/rules.ts +424 -5
- package/src/runtime.ts +464 -53
- package/src/settings.ts +74 -0
- package/src/typert.host.ts +25 -0
- package/src/wire.ts +239 -0
- package/lib/types/config.js +0 -99
- package/lib/types/config.js.map +0 -1
- package/lib/types/events.js +0 -44
- package/lib/types/events.js.map +0 -1
- package/lib/types/glob.js +0 -307
- package/lib/types/glob.js.map +0 -1
- package/lib/types/index.js +0 -27
- package/lib/types/index.js.map +0 -1
- package/lib/types/prose.js +0 -173
- package/lib/types/prose.js.map +0 -1
- package/lib/types/rules.js +0 -610
- package/lib/types/rules.js.map +0 -1
- package/lib/types/runtime.js +0 -818
- package/lib/types/runtime.js.map +0 -1
package/src/settings.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side settings-namespace wiring: registers the whole plugin config
|
|
3
|
+
* as the `permission-rules` settings section (editable from the Web
|
|
4
|
+
* settings surface, loopback-only), keeps the runtime's config source
|
|
5
|
+
* pointed at the resolved section, and rebinds the network proxy when a
|
|
6
|
+
* bind/env-relevant knob changes live. On hosts without a settings
|
|
7
|
+
* service (the rc.6 line) nothing registers and the composition entry
|
|
8
|
+
* keeps serving the config — the plugin stays fully functional.
|
|
9
|
+
*
|
|
10
|
+
* The registration uses only structural interfaces: `@deepseek-ai/
|
|
11
|
+
* dsh-settings` is not an rc.6 peer, so importing it would hard-fail
|
|
12
|
+
* older hosts; the real service satisfies these shapes.
|
|
13
|
+
* @module dsh-permission-rules/settings
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
17
|
+
import type z from '@deepseek-ai/schemastery'
|
|
18
|
+
import { Config, resolveConfig } from './config.ts'
|
|
19
|
+
import type { Config as PluginConfig, ResolvedConfig } from './config.ts'
|
|
20
|
+
import type { PermissionRulesRuntime } from './runtime.ts'
|
|
21
|
+
|
|
22
|
+
/** The settings namespace this plugin owns (kebab-case, per the provider contract). */
|
|
23
|
+
export const SETTINGS_NAMESPACE = 'permission-rules'
|
|
24
|
+
|
|
25
|
+
/** The owner-facing scope the real settings service hands back. */
|
|
26
|
+
interface SettingsScopeLike<T> {
|
|
27
|
+
get(): T
|
|
28
|
+
watch(callback: (next: T, prev: T) => void | Promise<void>): () => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The structural settings-provider shape the real `ctx.settings` satisfies. */
|
|
32
|
+
interface SettingsProviderLike {
|
|
33
|
+
register<T>(ns: string, schema: z<T>, options?: {
|
|
34
|
+
base?: Partial<T>
|
|
35
|
+
expose?: boolean
|
|
36
|
+
applies?: string
|
|
37
|
+
validate?: (value: T) => void
|
|
38
|
+
}): SettingsScopeLike<T>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Register the settings section and bind the runtime's live config
|
|
43
|
+
* source. Runs only where a settings service is composed.
|
|
44
|
+
* @param ctx - the plugin context.
|
|
45
|
+
* @param runtime - the runtime whose config source follows the section.
|
|
46
|
+
* @param entry - the composition entry config (the section's `base` layer).
|
|
47
|
+
*/
|
|
48
|
+
export function attachSettingsSection(ctx: Context, runtime: PermissionRulesRuntime, entry: PluginConfig): void {
|
|
49
|
+
ctx.inject(['settings'], (scope) => {
|
|
50
|
+
const settings = scope.get('settings') as SettingsProviderLike | undefined
|
|
51
|
+
if (settings === undefined) return
|
|
52
|
+
const registration = settings.register<PluginConfig>(SETTINGS_NAMESPACE, Config as z<PluginConfig>, {
|
|
53
|
+
base: entry,
|
|
54
|
+
expose: true,
|
|
55
|
+
applies: 'live',
|
|
56
|
+
validate: (value: PluginConfig) => {
|
|
57
|
+
// Schemastery already enforced the closed enums; this re-runs the
|
|
58
|
+
// numeric-bounds checks so an out-of-range stored value is refused
|
|
59
|
+
// at write time instead of disabling the owner later.
|
|
60
|
+
resolveConfig(value)
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
runtime.setConfigSource(() => resolveConfig(registration.get()))
|
|
64
|
+
registration.watch((next, prev) => {
|
|
65
|
+
const nextConfig = resolveConfig(next) as ResolvedConfig
|
|
66
|
+
const prevConfig = resolveConfig(prev) as ResolvedConfig
|
|
67
|
+
const rebind = nextConfig.network.proxyPort !== prevConfig.network.proxyPort
|
|
68
|
+
|| nextConfig.network.proxyBind !== prevConfig.network.proxyBind
|
|
69
|
+
|| nextConfig.network.injectEnv !== prevConfig.network.injectEnv
|
|
70
|
+
|| nextConfig.network.noProxy !== prevConfig.network.noProxy
|
|
71
|
+
if (rebind) void runtime.onNetworkConfigChanged()
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hand-written Typert HOST manifest for `dsh-permission-rules`,
|
|
3
|
+
* exported as `./typert` so the harness's typert-loader registers the
|
|
4
|
+
* `permissionRules` settings-page invocations automatically when this
|
|
5
|
+
* plugin mounts. Same shape as a generator output (validated by the
|
|
6
|
+
* loader): package face, empty model and schemas, and the canonical
|
|
7
|
+
* invocation list shared with the client Remote contribution
|
|
8
|
+
* (`src/wire.ts`).
|
|
9
|
+
* @module dsh-permission-rules/typert
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { PERMISSION_RULES_INVOCATIONS } from './wire.ts'
|
|
13
|
+
|
|
14
|
+
/** Host Typert manifest (validated by `@deepseek-ai/dsh-typert-loader`). */
|
|
15
|
+
export const TYPERT = Object.freeze({
|
|
16
|
+
package: 'dsh-permission-rules',
|
|
17
|
+
face: 'host',
|
|
18
|
+
schemas: Object.freeze([]),
|
|
19
|
+
invocations: PERMISSION_RULES_INVOCATIONS,
|
|
20
|
+
model: Object.freeze({
|
|
21
|
+
services: Object.freeze([]),
|
|
22
|
+
events: Object.freeze([]),
|
|
23
|
+
objects: Object.freeze([]),
|
|
24
|
+
}),
|
|
25
|
+
})
|
package/src/wire.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settings-page wire vocabulary: the snapshot/rule-editor types served
|
|
3
|
+
* over the `permissionRules` Typert Remote namespace, their zod v4
|
|
4
|
+
* validation schemas (the strict codec both Typert faces carry), and the
|
|
5
|
+
* invocation descriptors shared verbatim by the host `./typert` manifest
|
|
6
|
+
* and the client Remote contribution. One canonical source for both faces
|
|
7
|
+
* keeps the host and client codecs from ever drifting apart.
|
|
8
|
+
* @module dsh-permission-rules/wire
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { z } from 'zod'
|
|
12
|
+
import type { InvocationDescriptor } from '@deepseek-ai/dsh-typert-protocol'
|
|
13
|
+
|
|
14
|
+
/** The three network policy modes (wire vocabulary). */
|
|
15
|
+
export type NetworkModeWire = 'deny-all' | 'whitelist' | 'allow-all'
|
|
16
|
+
|
|
17
|
+
/** One recent proxy-layer block, sanitized for display. */
|
|
18
|
+
export interface NetworkBlockView {
|
|
19
|
+
/** Epoch ms of the block. */
|
|
20
|
+
time: number
|
|
21
|
+
/** Attributed shell tool (`bash`/`pwsh`) or `subprocess`. */
|
|
22
|
+
tool: string
|
|
23
|
+
/** Whether an in-flight shell execution could be named. */
|
|
24
|
+
attributed: boolean
|
|
25
|
+
/** Blocked target host. */
|
|
26
|
+
domain: string
|
|
27
|
+
scheme: 'http' | 'https' | null
|
|
28
|
+
port: number | null
|
|
29
|
+
action: 'deny' | 'ask'
|
|
30
|
+
mode: NetworkModeWire
|
|
31
|
+
/** Whether a rule (true) or the mode default (false) blocked the target. */
|
|
32
|
+
matched: boolean
|
|
33
|
+
source: string
|
|
34
|
+
ruleIndex: number | null
|
|
35
|
+
reason: string | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** One rule file the editor may read/write (a known rule source only). */
|
|
39
|
+
export interface RuleSourceView {
|
|
40
|
+
path: string
|
|
41
|
+
exists: boolean
|
|
42
|
+
/** The workspace the source belongs to, or null for the deployment fallback. */
|
|
43
|
+
cwd: string | null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The complete settings-page snapshot served by `permissionRules/networkStatus`. */
|
|
47
|
+
export interface PermissionRulesSnapshot {
|
|
48
|
+
enabled: boolean
|
|
49
|
+
mode: NetworkModeWire
|
|
50
|
+
/** The configured mode knob (`auto` or an explicit mode). */
|
|
51
|
+
configuredMode: 'auto' | NetworkModeWire
|
|
52
|
+
/** The sandbox preset `auto` mapped from, or null. */
|
|
53
|
+
sandboxMode: string | null
|
|
54
|
+
proxyPort: number
|
|
55
|
+
proxyActive: boolean
|
|
56
|
+
denied: number
|
|
57
|
+
askBlocked: number
|
|
58
|
+
recent: readonly NetworkBlockView[]
|
|
59
|
+
sources: readonly RuleSourceView[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Strict wire schema for {@link PermissionRulesSnapshot}. */
|
|
63
|
+
export const PERMISSION_RULES_SNAPSHOT_SCHEMA = z.object({
|
|
64
|
+
enabled: z.boolean(),
|
|
65
|
+
mode: z.union([z.literal('deny-all'), z.literal('whitelist'), z.literal('allow-all')]),
|
|
66
|
+
configuredMode: z.union([z.literal('auto'), z.literal('deny-all'), z.literal('whitelist'), z.literal('allow-all')]),
|
|
67
|
+
sandboxMode: z.string().nullable(),
|
|
68
|
+
proxyPort: z.number().int(),
|
|
69
|
+
proxyActive: z.boolean(),
|
|
70
|
+
denied: z.number().int(),
|
|
71
|
+
askBlocked: z.number().int(),
|
|
72
|
+
recent: z.array(z.object({
|
|
73
|
+
time: z.number().int(),
|
|
74
|
+
tool: z.string(),
|
|
75
|
+
attributed: z.boolean(),
|
|
76
|
+
domain: z.string(),
|
|
77
|
+
scheme: z.union([z.literal('http'), z.literal('https'), z.null()]),
|
|
78
|
+
port: z.number().int().nullable(),
|
|
79
|
+
action: z.union([z.literal('deny'), z.literal('ask')]),
|
|
80
|
+
mode: z.union([z.literal('deny-all'), z.literal('whitelist'), z.literal('allow-all')]),
|
|
81
|
+
matched: z.boolean(),
|
|
82
|
+
source: z.string(),
|
|
83
|
+
ruleIndex: z.number().int().nullable(),
|
|
84
|
+
reason: z.string().nullable(),
|
|
85
|
+
})),
|
|
86
|
+
sources: z.array(z.object({
|
|
87
|
+
path: z.string(),
|
|
88
|
+
exists: z.boolean(),
|
|
89
|
+
cwd: z.string().nullable(),
|
|
90
|
+
})),
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
/** Result of `permissionRules/rulesRead`. */
|
|
94
|
+
export interface RulesReadResult {
|
|
95
|
+
path: string
|
|
96
|
+
exists: boolean
|
|
97
|
+
text: string
|
|
98
|
+
error: string | null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Strict wire schema for {@link RulesReadResult}. */
|
|
102
|
+
export const RULES_READ_SCHEMA = z.object({
|
|
103
|
+
path: z.string(),
|
|
104
|
+
exists: z.boolean(),
|
|
105
|
+
text: z.string(),
|
|
106
|
+
error: z.string().nullable(),
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
/** Result of `permissionRules/rulesSave`. */
|
|
110
|
+
export interface RulesSaveResult {
|
|
111
|
+
ok: boolean
|
|
112
|
+
error: string | null
|
|
113
|
+
reloaded: number | null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Strict wire schema for {@link RulesSaveResult}. */
|
|
117
|
+
export const RULES_SAVE_SCHEMA = z.object({
|
|
118
|
+
ok: z.boolean(),
|
|
119
|
+
error: z.string().nullable(),
|
|
120
|
+
reloaded: z.number().int().nullable(),
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
/** Result of `permissionRules/reload`. */
|
|
124
|
+
export interface RulesReloadResult {
|
|
125
|
+
ok: boolean
|
|
126
|
+
error: string | null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Strict wire schema for {@link RulesReloadResult}. */
|
|
130
|
+
export const RULES_RELOAD_SCHEMA = z.object({
|
|
131
|
+
ok: z.boolean(),
|
|
132
|
+
error: z.string().nullable(),
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
/** The `permissionRules/networkStatus` invocation descriptor (no parameters). */
|
|
136
|
+
export const NETWORK_STATUS_DESCRIPTOR = Object.freeze({
|
|
137
|
+
id: 'dsh-permission-rules#permissionRules/networkStatus',
|
|
138
|
+
service: 'permissionRules',
|
|
139
|
+
namespace: 'permissionRules',
|
|
140
|
+
method: 'networkStatus',
|
|
141
|
+
invocation: Object.freeze({ kind: 'direct' }),
|
|
142
|
+
parameters: Object.freeze([]),
|
|
143
|
+
result: Object.freeze({
|
|
144
|
+
mode: 'strict',
|
|
145
|
+
typeSymbol: 'dsh-permission-rules/types#PermissionRulesSnapshot',
|
|
146
|
+
schema: PERMISSION_RULES_SNAPSHOT_SCHEMA,
|
|
147
|
+
}),
|
|
148
|
+
sourceLocation: Object.freeze({ file: 'src/wire.ts', line: 1, column: 1 }),
|
|
149
|
+
} as const) satisfies InvocationDescriptor
|
|
150
|
+
|
|
151
|
+
/** The `permissionRules/rulesRead` invocation descriptor. */
|
|
152
|
+
export const RULES_READ_DESCRIPTOR = Object.freeze({
|
|
153
|
+
id: 'dsh-permission-rules#permissionRules/rulesRead',
|
|
154
|
+
service: 'permissionRules',
|
|
155
|
+
namespace: 'permissionRules',
|
|
156
|
+
method: 'rulesRead',
|
|
157
|
+
invocation: Object.freeze({ kind: 'direct' }),
|
|
158
|
+
parameters: Object.freeze([Object.freeze({
|
|
159
|
+
name: 'path',
|
|
160
|
+
wire: 'path',
|
|
161
|
+
source: 'json',
|
|
162
|
+
codec: Object.freeze({
|
|
163
|
+
mode: 'strict',
|
|
164
|
+
typeSymbol: 'dsh-permission-rules/types#RulesReadRequestPath',
|
|
165
|
+
schema: z.string(),
|
|
166
|
+
}),
|
|
167
|
+
} satisfies InvocationDescriptor['parameters'][number])]),
|
|
168
|
+
result: Object.freeze({
|
|
169
|
+
mode: 'strict',
|
|
170
|
+
typeSymbol: 'dsh-permission-rules/types#RulesReadResult',
|
|
171
|
+
schema: RULES_READ_SCHEMA,
|
|
172
|
+
}),
|
|
173
|
+
sourceLocation: Object.freeze({ file: 'src/wire.ts', line: 1, column: 1 }),
|
|
174
|
+
} as const) satisfies InvocationDescriptor
|
|
175
|
+
|
|
176
|
+
/** The `permissionRules/rulesSave` invocation descriptor. */
|
|
177
|
+
export const RULES_SAVE_DESCRIPTOR = Object.freeze({
|
|
178
|
+
id: 'dsh-permission-rules#permissionRules/rulesSave',
|
|
179
|
+
service: 'permissionRules',
|
|
180
|
+
namespace: 'permissionRules',
|
|
181
|
+
method: 'rulesSave',
|
|
182
|
+
invocation: Object.freeze({ kind: 'direct' }),
|
|
183
|
+
parameters: Object.freeze([
|
|
184
|
+
Object.freeze({
|
|
185
|
+
name: 'path',
|
|
186
|
+
wire: 'path',
|
|
187
|
+
source: 'json',
|
|
188
|
+
codec: Object.freeze({
|
|
189
|
+
mode: 'strict',
|
|
190
|
+
typeSymbol: 'dsh-permission-rules/types#RulesSaveRequestPath',
|
|
191
|
+
schema: z.string(),
|
|
192
|
+
}),
|
|
193
|
+
} satisfies InvocationDescriptor['parameters'][number]),
|
|
194
|
+
Object.freeze({
|
|
195
|
+
name: 'text',
|
|
196
|
+
wire: 'text',
|
|
197
|
+
source: 'json',
|
|
198
|
+
codec: Object.freeze({
|
|
199
|
+
mode: 'strict',
|
|
200
|
+
typeSymbol: 'dsh-permission-rules/types#RulesSaveRequestText',
|
|
201
|
+
schema: z.string(),
|
|
202
|
+
}),
|
|
203
|
+
} satisfies InvocationDescriptor['parameters'][number]),
|
|
204
|
+
]),
|
|
205
|
+
result: Object.freeze({
|
|
206
|
+
mode: 'strict',
|
|
207
|
+
typeSymbol: 'dsh-permission-rules/types#RulesSaveResult',
|
|
208
|
+
schema: RULES_SAVE_SCHEMA,
|
|
209
|
+
}),
|
|
210
|
+
sourceLocation: Object.freeze({ file: 'src/wire.ts', line: 1, column: 1 }),
|
|
211
|
+
} as const) satisfies InvocationDescriptor
|
|
212
|
+
|
|
213
|
+
/** The `permissionRules/reload` invocation descriptor (no parameters). */
|
|
214
|
+
export const RULES_RELOAD_DESCRIPTOR = Object.freeze({
|
|
215
|
+
id: 'dsh-permission-rules#permissionRules/reload',
|
|
216
|
+
service: 'permissionRules',
|
|
217
|
+
namespace: 'permissionRules',
|
|
218
|
+
method: 'reload',
|
|
219
|
+
invocation: Object.freeze({ kind: 'direct' }),
|
|
220
|
+
parameters: Object.freeze([]),
|
|
221
|
+
result: Object.freeze({
|
|
222
|
+
mode: 'strict',
|
|
223
|
+
typeSymbol: 'dsh-permission-rules/types#RulesReloadResult',
|
|
224
|
+
schema: RULES_RELOAD_SCHEMA,
|
|
225
|
+
}),
|
|
226
|
+
sourceLocation: Object.freeze({ file: 'src/wire.ts', line: 1, column: 1 }),
|
|
227
|
+
} as const) satisfies InvocationDescriptor
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* The canonical invocation list both Typert faces register — the host
|
|
231
|
+
* manifest and the client contribution share these exact descriptor
|
|
232
|
+
* objects, so the two wire codecs can never drift apart.
|
|
233
|
+
*/
|
|
234
|
+
export const PERMISSION_RULES_INVOCATIONS = Object.freeze([
|
|
235
|
+
NETWORK_STATUS_DESCRIPTOR,
|
|
236
|
+
RULES_READ_DESCRIPTOR,
|
|
237
|
+
RULES_SAVE_DESCRIPTOR,
|
|
238
|
+
RULES_RELOAD_DESCRIPTOR,
|
|
239
|
+
])
|
package/lib/types/config.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Config schema and resolution for `dsh-permission-rules`. Every tunable is
|
|
3
|
-
* a validated {@link Config} field changeable from cordis.yml; the
|
|
4
|
-
* resolution step validates the numeric bounds and compiles nothing — rule
|
|
5
|
-
* files are external documents resolved per session cwd at load time.
|
|
6
|
-
* @module dsh-permission-rules/config
|
|
7
|
-
*/
|
|
8
|
-
import { isAbsolute } from 'node:path';
|
|
9
|
-
import z from '@deepseek-ai/schemastery';
|
|
10
|
-
/** Schemastery schema: the loader validates and fills defaults before `apply`. */
|
|
11
|
-
export const Config = z.object({
|
|
12
|
-
rulesFile: z.string().default('.dsh/rules.yaml'),
|
|
13
|
-
fallbackPath: z.string(),
|
|
14
|
-
badFilePolicy: z.union(['fail', 'ignore-with-warning']).default('fail'),
|
|
15
|
-
maxRules: z.number().default(256),
|
|
16
|
-
maxCachedWorkspaces: z.number().default(512),
|
|
17
|
-
patternMode: z.union(['glob', 'regex']).default('glob'),
|
|
18
|
-
watch: z.boolean().default(true),
|
|
19
|
-
watchStabilityThresholdMs: z.number().default(200),
|
|
20
|
-
language: z.union(['en', 'zh', 'es', 'pt', 'hi']).default('en'),
|
|
21
|
-
caseInsensitivePaths: z.boolean().default(process.platform === 'win32'),
|
|
22
|
-
audit: z.union(['all', 'hits']).default('all'),
|
|
23
|
-
searchUp: z.boolean().default(false),
|
|
24
|
-
maxGlobStars: z.number().default(2),
|
|
25
|
-
enforce: z.boolean().default(true),
|
|
26
|
-
allowUnmarkedAudit: z.boolean().default(false),
|
|
27
|
-
});
|
|
28
|
-
/**
|
|
29
|
-
* Validate raw values and fill explicit defaults. A `maxRules`,
|
|
30
|
-
* `maxCachedWorkspaces`, or `maxGlobStars` that is not a positive safe
|
|
31
|
-
* integer, a non-positive stability window, `searchUp` combined with an
|
|
32
|
-
* absolute `rulesFile`, a value outside a closed enum, or a non-boolean
|
|
33
|
-
* flag throws here — misconfiguration fails loud at mount even when the
|
|
34
|
-
* plugin is mounted without the Schemastery loader.
|
|
35
|
-
* @param config - raw (possibly partial) plugin config.
|
|
36
|
-
* @returns the fully resolved config.
|
|
37
|
-
*/
|
|
38
|
-
export function resolveConfig(config = {}) {
|
|
39
|
-
const rulesFile = config.rulesFile ?? '.dsh/rules.yaml';
|
|
40
|
-
const searchUp = config.searchUp ?? false;
|
|
41
|
-
if (searchUp && isAbsolute(rulesFile)) {
|
|
42
|
-
throw new TypeError(`searchUp cannot be combined with an absolute rulesFile (${JSON.stringify(rulesFile)}): parent-directory walking needs a relative file name`);
|
|
43
|
-
}
|
|
44
|
-
const maxRules = config.maxRules ?? 256;
|
|
45
|
-
if (!Number.isSafeInteger(maxRules) || maxRules <= 0) {
|
|
46
|
-
throw new TypeError(`maxRules must be a positive safe integer, got ${String(config.maxRules)}`);
|
|
47
|
-
}
|
|
48
|
-
const watchStabilityThresholdMs = config.watchStabilityThresholdMs ?? 200;
|
|
49
|
-
if (!Number.isSafeInteger(watchStabilityThresholdMs) || watchStabilityThresholdMs < 0) {
|
|
50
|
-
throw new TypeError(`watchStabilityThresholdMs must be a non-negative safe integer, got ${String(config.watchStabilityThresholdMs)}`);
|
|
51
|
-
}
|
|
52
|
-
const maxCachedWorkspaces = config.maxCachedWorkspaces ?? 512;
|
|
53
|
-
if (!Number.isSafeInteger(maxCachedWorkspaces) || maxCachedWorkspaces <= 0) {
|
|
54
|
-
throw new TypeError(`maxCachedWorkspaces must be a positive safe integer, got ${String(config.maxCachedWorkspaces)}`);
|
|
55
|
-
}
|
|
56
|
-
const maxGlobStars = config.maxGlobStars ?? 2;
|
|
57
|
-
if (!Number.isSafeInteger(maxGlobStars) || maxGlobStars <= 0) {
|
|
58
|
-
throw new TypeError(`maxGlobStars must be a positive safe integer, got ${String(config.maxGlobStars)}`);
|
|
59
|
-
}
|
|
60
|
-
assertEnum('badFilePolicy', config.badFilePolicy ?? 'fail', ['fail', 'ignore-with-warning']);
|
|
61
|
-
assertEnum('patternMode', config.patternMode ?? 'glob', ['glob', 'regex']);
|
|
62
|
-
assertEnum('language', config.language ?? 'en', ['en', 'zh', 'es', 'pt', 'hi']);
|
|
63
|
-
assertEnum('audit', config.audit ?? 'all', ['all', 'hits']);
|
|
64
|
-
assertBoolean('watch', config.watch ?? true);
|
|
65
|
-
assertBoolean('searchUp', searchUp);
|
|
66
|
-
assertBoolean('caseInsensitivePaths', config.caseInsensitivePaths ?? process.platform === 'win32');
|
|
67
|
-
assertBoolean('enforce', config.enforce ?? true);
|
|
68
|
-
assertBoolean('allowUnmarkedAudit', config.allowUnmarkedAudit ?? false);
|
|
69
|
-
return {
|
|
70
|
-
rulesFile,
|
|
71
|
-
fallbackPath: config.fallbackPath,
|
|
72
|
-
badFilePolicy: config.badFilePolicy ?? 'fail',
|
|
73
|
-
maxRules,
|
|
74
|
-
maxCachedWorkspaces,
|
|
75
|
-
patternMode: config.patternMode ?? 'glob',
|
|
76
|
-
watch: config.watch ?? true,
|
|
77
|
-
watchStabilityThresholdMs,
|
|
78
|
-
language: config.language ?? 'en',
|
|
79
|
-
caseInsensitivePaths: config.caseInsensitivePaths ?? process.platform === 'win32',
|
|
80
|
-
audit: config.audit ?? 'all',
|
|
81
|
-
searchUp,
|
|
82
|
-
maxGlobStars,
|
|
83
|
-
enforce: config.enforce ?? true,
|
|
84
|
-
allowUnmarkedAudit: config.allowUnmarkedAudit ?? false,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
/** Throw unless `key` is one of `allowed` (TypeScript's closed enums are not runtime checks). */
|
|
88
|
-
function assertEnum(key, value, allowed) {
|
|
89
|
-
if (!allowed.includes(value)) {
|
|
90
|
-
throw new TypeError(`${key} must be one of ${allowed.map(item => JSON.stringify(item)).join(' | ')}, got ${JSON.stringify(value)}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
/** Throw unless `key` is a boolean (plain-JS mounts bypass the Schemastery coercion). */
|
|
94
|
-
function assertBoolean(key, value) {
|
|
95
|
-
if (typeof value !== 'boolean') {
|
|
96
|
-
throw new TypeError(`${key} must be a boolean, got ${typeof value}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
//# sourceMappingURL=config.js.map
|
package/lib/types/config.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAyFxC,kFAAkF;AAClF,MAAM,CAAC,MAAM,MAAM,GAAc,CAAC,CAAC,MAAM,CAAC;IACxC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAC;IAChD,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,qBAAqB,CAAU,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IAChF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;IACjC,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;IAC5C,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,OAAO,CAAU,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IAChE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAChC,yBAAyB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;IAClD,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IACxE,oBAAoB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;IACvE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,MAAM,CAAU,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IACvD,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACpC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACnC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAClC,kBAAkB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAC/C,CAAC,CAAA;AAEF;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE;IAC/C,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,iBAAiB,CAAA;IACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,KAAK,CAAA;IACzC,IAAI,QAAQ,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,SAAS,CAAC,2DAA2D,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,wDAAwD,CAAC,CAAA;IACnK,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,GAAG,CAAA;IACvC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,SAAS,CAAC,iDAAiD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IACjG,CAAC;IACD,MAAM,yBAAyB,GAAG,MAAM,CAAC,yBAAyB,IAAI,GAAG,CAAA;IACzE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,yBAAyB,CAAC,IAAI,yBAAyB,GAAG,CAAC,EAAE,CAAC;QACtF,MAAM,IAAI,SAAS,CAAC,sEAAsE,MAAM,CAAC,MAAM,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAA;IACvI,CAAC;IACD,MAAM,mBAAmB,GAAG,MAAM,CAAC,mBAAmB,IAAI,GAAG,CAAA;IAC7D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,mBAAmB,CAAC,IAAI,mBAAmB,IAAI,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,SAAS,CAAC,4DAA4D,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAA;IACvH,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,CAAC,CAAA;IAC7C,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,SAAS,CAAC,qDAAqD,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAA;IACzG,CAAC;IACD,UAAU,CAAC,eAAe,EAAE,MAAM,CAAC,aAAa,IAAI,MAAM,EAAE,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAA;IAC5F,UAAU,CAAC,aAAa,EAAE,MAAM,CAAC,WAAW,IAAI,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAC1E,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;IAC/E,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IAC3D,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,CAAA;IAC5C,aAAa,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IACnC,aAAa,CAAC,sBAAsB,EAAE,MAAM,CAAC,oBAAoB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAA;IAClG,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC,CAAA;IAChD,aAAa,CAAC,oBAAoB,EAAE,MAAM,CAAC,kBAAkB,IAAI,KAAK,CAAC,CAAA;IACvE,OAAO;QACL,SAAS;QACT,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,MAAM;QAC7C,QAAQ;QACR,mBAAmB;QACnB,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,MAAM;QACzC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;QAC3B,yBAAyB;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;QACjC,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QACjF,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,KAAK;QAC5B,QAAQ;QACR,YAAY;QACZ,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;QAC/B,kBAAkB,EAAE,MAAM,CAAC,kBAAkB,IAAI,KAAK;KACvD,CAAA;AACH,CAAC;AAED,iGAAiG;AACjG,SAAS,UAAU,CAAmB,GAAW,EAAE,KAAa,EAAE,OAAqB;IACrF,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAU,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,SAAS,CAAC,GAAG,GAAG,mBAAmB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACrI,CAAC;AACH,CAAC;AAED,yFAAyF;AACzF,SAAS,aAAa,CAAC,GAAW,EAAE,KAAc;IAChD,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,IAAI,SAAS,CAAC,GAAG,GAAG,2BAA2B,OAAO,KAAK,EAAE,CAAC,CAAA;IACtE,CAAC;AACH,CAAC"}
|
package/lib/types/events.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Durable session vocabulary for `dsh-permission-rules`: the
|
|
3
|
-
* `permissionRules/decision` audit event, appended for every hit AND every
|
|
4
|
-
* passthrough. Log-only — it never enters the model transcript; the only
|
|
5
|
-
* model-visible plugin content is the deny/ask reason the tools registry
|
|
6
|
-
* materializes into the denied tool result, which carries the same
|
|
7
|
-
* `callId`, keeping model-visible ⟺ logged reconstructable.
|
|
8
|
-
*
|
|
9
|
-
* `outcome` records the FINAL pre-execute decision: on a deny/ask hit it
|
|
10
|
-
* equals `action` (the plugin short-circuits); on an allow hit or a
|
|
11
|
-
* passthrough it carries the downstream listeners' decision, so the audit
|
|
12
|
-
* never claims a call was allowed when a later listener denied it.
|
|
13
|
-
*
|
|
14
|
-
* Under `enforce: false` (dry-run mode) the plugin matches but NEVER
|
|
15
|
-
* short-circuits: the record keeps the WOULD-BE `action` and marks
|
|
16
|
-
* `dryRun: true`, while `outcome` carries what actually happened
|
|
17
|
-
* downstream — an auditable "what would the policy have done" trail.
|
|
18
|
-
*
|
|
19
|
-
* The event is appended with the envelope's `ignorable: true` marker (see
|
|
20
|
-
* {@link AuditAppend}). Harness builds that honor the marker (post-rc.6)
|
|
21
|
-
* stamp it on the envelope and skip unknown ignorable records when loading,
|
|
22
|
-
* so the audit can never refuse a session. Builds whose `Session.append`
|
|
23
|
-
* predates the marker — the `0.1.0-rc.6` line — silently DROP the options
|
|
24
|
-
* bag: the event then lands UNMARKED and makes the session unresumable on
|
|
25
|
-
* hosts with required-on-read semantics (`SessionFormatUnsupportedError`).
|
|
26
|
-
* The runtime detects this at first use (peer version pre-check plus a
|
|
27
|
-
* probe of the appended envelope) and disables session-log audit on such
|
|
28
|
-
* hosts with a one-time warning; `allowUnmarkedAudit: true` opts back in,
|
|
29
|
-
* and `scripts/repair-session-logs.mjs` repairs already-polluted logs.
|
|
30
|
-
* @module dsh-permission-rules/events
|
|
31
|
-
*/
|
|
32
|
-
/**
|
|
33
|
-
* Whether an `append` call actually honored the `ignorable` marker: the
|
|
34
|
-
* logged event returned by the host carries `ignorable === true` on
|
|
35
|
-
* marker-aware builds and nothing on pre-marker builds. `false` (or any
|
|
36
|
-
* non-event return) means the host dropped the marker and the event landed
|
|
37
|
-
* unmarked — the runtime then degrades instead of polluting further logs.
|
|
38
|
-
* @param result - the return value of the audit append.
|
|
39
|
-
* @returns true only when the marker is present on the returned envelope.
|
|
40
|
-
*/
|
|
41
|
-
export function isMarkedAuditEvent(result) {
|
|
42
|
-
return typeof result === 'object' && result !== null && result.ignorable === true;
|
|
43
|
-
}
|
|
44
|
-
//# sourceMappingURL=events.js.map
|
package/lib/types/events.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAoEH;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAe;IAChD,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAK,MAAkC,CAAC,SAAS,KAAK,IAAI,CAAA;AAChH,CAAC"}
|