dsh-perm-gate 4.2.0-beta.5 → 4.2.0-beta.6

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/dsh.plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dsh-perm-gate",
3
3
  "name": "dsh-perm-gate",
4
- "version": "4.2.0-beta.5",
4
+ "version": "4.2.0-beta.6",
5
5
  "description": {
6
6
  "en": "Deterministic-first, fail-closed permission gate for DeepSeek Harness 0.1.2+: a P0 hard-deny -> P1 precise session grant -> P2 static allow/deny rule chain -> P3 optional LLM classifier -> P4 ask pipeline over the tools/pre-execute waterfall, with a standalone CLI dry-run evaluator and an auditable decision log. It only vetoes with deny/ask and always delegates otherwise, and unknown or malformed rules fail loud instead of silently standing the gate down.",
7
7
  "zh": "面向 DeepSeek Harness 0.1.2+ 的确定性优先、失败即拒的权限闸门:在 tools/pre-execute 瀑布上串起 P0 硬拒绝 → P1 精确会话授权 → P2 静态 allow/deny 规则链 → P3 可选 LLM 语义分类 → P4 ask,并附带独立 CLI 干跑评估器与可审计的决策日志。只以 deny/ask 否决、其余一律放行,规则未知或非法时直接报错,绝不静默失效。"
@@ -46,8 +46,10 @@ export declare function listAllowCommands(rulesFile: string): string[];
46
46
  export declare function replaceAllowCommands(rulesFile: string, patterns: readonly string[], reason?: string): boolean;
47
47
  /**
48
48
  * Parse the rules file into a plain document — the seed for the first
49
- * settings-backed allowlist write, so migrating a file-defined document into
50
- * the entry's `rules` config field cannot drop its deny/ask sections.
51
- * Returns `{}` when the file is absent or unreadable.
49
+ * settings-backed allowlist write and the activation-time rules seed, so
50
+ * migrating a file-defined document into the entry's `rules` config field
51
+ * cannot drop its deny/ask sections. The file's optional `permissions:`
52
+ * wrapper is unwrapped (the config field stores the bare form). Returns `{}`
53
+ * when the file is absent or unreadable.
52
54
  */
53
55
  export declare function readRulesFileDoc(rulesFile: string): unknown;
package/lib/allowlist.js CHANGED
@@ -199,16 +199,23 @@ function readFileSafe(p) {
199
199
  }
200
200
  /**
201
201
  * Parse the rules file into a plain document — the seed for the first
202
- * settings-backed allowlist write, so migrating a file-defined document into
203
- * the entry's `rules` config field cannot drop its deny/ask sections.
204
- * Returns `{}` when the file is absent or unreadable.
202
+ * settings-backed allowlist write and the activation-time rules seed, so
203
+ * migrating a file-defined document into the entry's `rules` config field
204
+ * cannot drop its deny/ask sections. The file's optional `permissions:`
205
+ * wrapper is unwrapped (the config field stores the bare form). Returns `{}`
206
+ * when the file is absent or unreadable.
205
207
  */
206
208
  export function readRulesFileDoc(rulesFile) {
207
209
  const text = readFileSafe(rulesFile);
208
210
  if (text === '')
209
211
  return {};
210
212
  try {
211
- return parse(text) ?? {};
213
+ const root = parse(text);
214
+ if (root !== null && typeof root === 'object' && !Array.isArray(root)
215
+ && root['permissions'] !== null && typeof root['permissions'] === 'object') {
216
+ return root['permissions'];
217
+ }
218
+ return root ?? {};
212
219
  }
213
220
  catch {
214
221
  return {};
package/lib/config.d.ts CHANGED
@@ -277,14 +277,17 @@ export interface RulesConfig {
277
277
  readonly deny?: unknown[];
278
278
  readonly allow?: unknown[];
279
279
  readonly ask?: unknown[];
280
+ /** Activation-time seed marker (see RulesSchema). */
281
+ readonly initialized?: boolean;
280
282
  }
281
283
  /** Settings namespace for rules (separate from the main perm-gate namespace). */
282
284
  export declare const RULES_NAMESPACE = "dsh-perm-gate-rules";
283
285
  /**
284
286
  * Whether a settings-sourced rules document carries a REAL configuration —
285
- * entries or a non-default `defaultAction`. A namespace still holding bare
286
- * schema defaults is "not configured" and must not shadow the rules file
287
- * (the dual-source contract: settings first, file fallback).
287
+ * entries, a non-default `defaultAction`, or the `initialized` marker the
288
+ * activation-time seed writes after populating an empty document. A namespace
289
+ * still holding bare schema defaults is "not configured" and must not shadow
290
+ * the rules file (the dual-source contract: settings first, file fallback).
288
291
  */
289
292
  export declare function isRulesConfigured(value: unknown): boolean;
290
293
  /**
package/lib/config.js CHANGED
@@ -113,19 +113,26 @@ export const RulesSchema = z.object({
113
113
  deny: z.array(RuleEntrySchema).default([]),
114
114
  allow: z.array(RuleEntrySchema).default([]),
115
115
  ask: z.array(RuleEntrySchema).default([]),
116
+ // Activation-time seed marker: the gate writes `true` the first time it
117
+ // populates an empty document (file migration or bare starter), so the
118
+ // seeded namespace becomes the rules source instead of shadowing nothing.
119
+ initialized: z.boolean().default(false),
116
120
  });
117
121
  /** Settings namespace for rules (separate from the main perm-gate namespace). */
118
122
  export const RULES_NAMESPACE = 'dsh-perm-gate-rules';
119
123
  /**
120
124
  * Whether a settings-sourced rules document carries a REAL configuration —
121
- * entries or a non-default `defaultAction`. A namespace still holding bare
122
- * schema defaults is "not configured" and must not shadow the rules file
123
- * (the dual-source contract: settings first, file fallback).
125
+ * entries, a non-default `defaultAction`, or the `initialized` marker the
126
+ * activation-time seed writes after populating an empty document. A namespace
127
+ * still holding bare schema defaults is "not configured" and must not shadow
128
+ * the rules file (the dual-source contract: settings first, file fallback).
124
129
  */
125
130
  export function isRulesConfigured(value) {
126
131
  if (typeof value !== 'object' || value === null || Array.isArray(value))
127
132
  return false;
128
133
  const doc = value;
134
+ if (doc.initialized === true)
135
+ return true;
129
136
  if (doc.defaultAction !== undefined && doc.defaultAction !== 'ask')
130
137
  return true;
131
138
  for (const key of ['deny', 'allow', 'ask']) {
package/lib/index.js CHANGED
@@ -188,7 +188,19 @@ export function apply(ctx, config = {}) {
188
188
  const ENTRY_ID = 'dsh-perm-gate';
189
189
  const settingsRef = {};
190
190
  ctx.inject(['settings'], (child) => {
191
- settingsRef.svc = child.settings;
191
+ const svc = child.settings;
192
+ settingsRef.svc = svc;
193
+ // Activation-time seed: a never-configured `rules` field is populated once
194
+ // — from the rules file when one exists (implicit migration), otherwise a
195
+ // bare starter document — and marked `initialized`, so the panel shows a
196
+ // live rules document from the first boot instead of a missing-file
197
+ // fallback. The field watcher below picks the seed up via reload.
198
+ if (svc !== undefined && readRulesDocument() === undefined) {
199
+ const seed = { ...readRulesFileDoc(rulesFilePath), initialized: true };
200
+ void svc.update(ENTRY_ID, { rules: seed }).catch((e) => {
201
+ console.warn('[dsh-perm-gate] rules seed write failed:', e);
202
+ });
203
+ }
192
204
  });
193
205
  const settingsWrite = (write) => {
194
206
  const svc = settingsRef.svc;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-perm-gate",
3
- "version": "4.2.0-beta.5",
3
+ "version": "4.2.0-beta.6",
4
4
  "publishConfig": {
5
5
  "tag": "dsh-0.1.7"
6
6
  },