dsh-perm-gate 5.2.0 → 5.3.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.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Unattended tiers — which human-free implementations exist, and which permission preset
3
+ * each one owns.
4
+ *
5
+ * The shape, and why it is shaped this way: a mode is a property of the preset the user
6
+ * picks in the composer, never a background flow the gate adds to a session it was not
7
+ * asked to change. So each implementation has exactly one preset and exactly one switch
8
+ * that says whether it exists at all — one switch, one implementation, one tier. They do
9
+ * not cross: turning `safenetPass` off cannot make 自动审查 behave differently, and
10
+ * selecting `permissive-mech` does nothing unless that implementation is switched on.
11
+ *
12
+ * | switch | preset | who settles an ask | model |
13
+ * | ------------ | ----------------- | ---------------------------------------------------- | ----- |
14
+ * | `permissive` | permissive(-full) | machine screen + grader refine, then a PERSON | yes |
15
+ * | `safenetAsk` | permissive-snet | machine screen, then a PERSON — no grader at all | no |
16
+ * | `safenetPass`| permissive-mech | the machine screen alone: named danger blocked+fed | no |
17
+ * | `yolo` | permissive-yolo | the grader answers in place of the person, always | yes |
18
+ *
19
+ * `yolo` is the model-backed tier and says so: with no reviewer in seat it is NOT silently
20
+ * a pass-through. The caller still has to decide what a missing grader means (see
21
+ * {@link YOLO_WITHOUT_REVIEWER}) but it must record that the tier could not do the thing it
22
+ * advertises, and the settings face must say so out loud.
23
+ *
24
+ * Everything here is pure string/enum work so the browser half can reuse the same truth.
25
+ */
26
+ /** The three human-free implementations, named by what they do with an ask. */
27
+ export type UnattendedMode = 'safety-net-ask' | 'safety-net-passthrough' | 'yolo';
28
+ /** The preset each implementation owns. One switch, one preset — they never cross. */
29
+ export declare const MODE_PRESETS: Readonly<Record<UnattendedMode, string>>;
30
+ /** Every accepted mode, in the order the settings face should offer them. */
31
+ export declare const UNATTENDED_MODES: readonly UnattendedMode[];
32
+ /** The switches that decide whether each implementation exists. */
33
+ export interface UnattendedSwitches {
34
+ readonly safenetAsk?: boolean;
35
+ readonly safenetPass?: boolean;
36
+ readonly yolo?: boolean;
37
+ }
38
+ /**
39
+ * The mode for one call, from the session's current permission preset plus which
40
+ * implementations are switched on.
41
+ *
42
+ * `null` means no human-free implementation owns this preset: the ordinary flow runs, and
43
+ * an ask goes to a person. A preset whose switch is off also returns `null` — the tier may
44
+ * still exist in the picker (profiles are not rewritten here), it simply has no
45
+ * implementation behind it, which is the honest reading of "the switch controls whether the
46
+ * implementation exists".
47
+ */
48
+ export declare function resolveTierMode(preset: string | undefined, switches: UnattendedSwitches): UnattendedMode | null;
49
+ /**
50
+ * Whether a mode takes the human out of the loop. `safety-net-ask` keeps them in — it
51
+ * exists to grade without a model, not to stop asking — so the decision seam only branches
52
+ * on the other two, and `safety-net-ask` instead suppresses the grader refine pass.
53
+ */
54
+ export declare function isHumanFree(mode: UnattendedMode): boolean;
55
+ /**
56
+ * What YOLO does when its grader is not in seat. It stays human-free (that is the tier's
57
+ * promise), settles on the mechanical screen alone, and the caller records that the model
58
+ * never answered — a tier that quietly becomes a different tier is the failure mode this
59
+ * whole module exists to avoid.
60
+ */
61
+ export declare const YOLO_WITHOUT_REVIEWER: "settle-mechanically-and-label";
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Unattended tiers — which human-free implementations exist, and which permission preset
3
+ * each one owns.
4
+ *
5
+ * The shape, and why it is shaped this way: a mode is a property of the preset the user
6
+ * picks in the composer, never a background flow the gate adds to a session it was not
7
+ * asked to change. So each implementation has exactly one preset and exactly one switch
8
+ * that says whether it exists at all — one switch, one implementation, one tier. They do
9
+ * not cross: turning `safenetPass` off cannot make 自动审查 behave differently, and
10
+ * selecting `permissive-mech` does nothing unless that implementation is switched on.
11
+ *
12
+ * | switch | preset | who settles an ask | model |
13
+ * | ------------ | ----------------- | ---------------------------------------------------- | ----- |
14
+ * | `permissive` | permissive(-full) | machine screen + grader refine, then a PERSON | yes |
15
+ * | `safenetAsk` | permissive-snet | machine screen, then a PERSON — no grader at all | no |
16
+ * | `safenetPass`| permissive-mech | the machine screen alone: named danger blocked+fed | no |
17
+ * | `yolo` | permissive-yolo | the grader answers in place of the person, always | yes |
18
+ *
19
+ * `yolo` is the model-backed tier and says so: with no reviewer in seat it is NOT silently
20
+ * a pass-through. The caller still has to decide what a missing grader means (see
21
+ * {@link YOLO_WITHOUT_REVIEWER}) but it must record that the tier could not do the thing it
22
+ * advertises, and the settings face must say so out loud.
23
+ *
24
+ * Everything here is pure string/enum work so the browser half can reuse the same truth.
25
+ */
26
+ /** The preset each implementation owns. One switch, one preset — they never cross. */
27
+ export const MODE_PRESETS = {
28
+ 'safety-net-ask': 'permissive-snet',
29
+ 'safety-net-passthrough': 'permissive-mech',
30
+ yolo: 'permissive-yolo',
31
+ };
32
+ /** Every accepted mode, in the order the settings face should offer them. */
33
+ export const UNATTENDED_MODES = [
34
+ 'safety-net-ask',
35
+ 'safety-net-passthrough',
36
+ 'yolo',
37
+ ];
38
+ /**
39
+ * The mode for one call, from the session's current permission preset plus which
40
+ * implementations are switched on.
41
+ *
42
+ * `null` means no human-free implementation owns this preset: the ordinary flow runs, and
43
+ * an ask goes to a person. A preset whose switch is off also returns `null` — the tier may
44
+ * still exist in the picker (profiles are not rewritten here), it simply has no
45
+ * implementation behind it, which is the honest reading of "the switch controls whether the
46
+ * implementation exists".
47
+ */
48
+ export function resolveTierMode(preset, switches) {
49
+ if (preset === undefined)
50
+ return null;
51
+ for (const mode of UNATTENDED_MODES) {
52
+ if (MODE_PRESETS[mode] !== preset)
53
+ continue;
54
+ const on = mode === 'safety-net-ask'
55
+ ? switches.safenetAsk === true
56
+ : mode === 'safety-net-passthrough'
57
+ ? switches.safenetPass === true
58
+ : switches.yolo === true;
59
+ return on ? mode : null;
60
+ }
61
+ return null;
62
+ }
63
+ /**
64
+ * Whether a mode takes the human out of the loop. `safety-net-ask` keeps them in — it
65
+ * exists to grade without a model, not to stop asking — so the decision seam only branches
66
+ * on the other two, and `safety-net-ask` instead suppresses the grader refine pass.
67
+ */
68
+ export function isHumanFree(mode) {
69
+ return mode === 'safety-net-passthrough' || mode === 'yolo';
70
+ }
71
+ /**
72
+ * What YOLO does when its grader is not in seat. It stays human-free (that is the tier's
73
+ * promise), settles on the mechanical screen alone, and the caller records that the model
74
+ * never answered — a tier that quietly becomes a different tier is the failure mode this
75
+ * whole module exists to avoid.
76
+ */
77
+ export const YOLO_WITHOUT_REVIEWER = 'settle-mechanically-and-label';
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "dsh-perm-gate",
3
- "version": "5.2.0",
3
+ "version": "5.3.1",
4
4
  "publishConfig": {
5
+ "registry": "https://registry.npmjs.org",
5
6
  "tag": "latest"
6
7
  },
7
8
  "description": "DSH permission-gate for the DeepSeek Harness 0.1.5 line: a single self-sufficient, deterministic-first, fail-closed gate covering P0 hard-deny -> P1 session grant -> P2 static rule (allow/deny) chain -> P3 optional LLM semantic classifier -> P4 ask, with command whitelist/blacklist.",
@@ -68,15 +69,15 @@
68
69
  },
69
70
  "engines": {
70
71
  "node": ">=24",
71
- "dsh": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2"
72
+ "dsh": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1"
72
73
  },
73
74
  "peerDependencies": {
74
75
  "@deepseek-ai/cordis": "^4.0.1",
75
- "@deepseek-ai/dsh-client-locale": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
76
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
77
- "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
78
- "@deepseek-ai/dsh-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
79
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2"
76
+ "@deepseek-ai/dsh-client-locale": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
77
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
78
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
79
+ "@deepseek-ai/dsh-settings": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
80
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1"
80
81
  },
81
82
  "dependencies": {
82
83
  "@deepseek-ai/schemastery": "^3.18.4",
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Add `permissive-full` to the DSH composer's permission-glyph map.
3
+ * Add the plugin's own permission tiers to the DSH composer's permission-glyph map.
4
4
  *
5
5
  * WHY THIS EXISTS
6
6
  * ---------------
@@ -52,9 +52,11 @@ import { homedir } from 'node:os'
52
52
  import { dirname, join, resolve } from 'node:path'
53
53
  import { pathToFileURL } from 'node:url'
54
54
 
55
- /** The map key whose entry is cloned, and the key it is cloned onto. */
55
+ /** The map key whose entry is cloned, and every key it must be cloned onto. */
56
56
  export const SOURCE_KEY = 'permissive'
57
- export const TARGET_KEY = 'permissive-full'
57
+ export const TARGET_KEYS = ['permissive-full', 'permissive-snet', 'permissive-mech', 'permissive-yolo']
58
+ /** The first target, kept as a named export because the spec and the docs quote it. */
59
+ export const TARGET_KEY = TARGET_KEYS[0]
58
60
 
59
61
  /** Relative path from a package root to the bundle holding the map. */
60
62
  const BUNDLE = join('@deepseek-ai', 'dsh-client-ui-conversation', 'lib', 'client.js')
@@ -117,20 +119,31 @@ export function sliceEntry(source, key) {
117
119
  }
118
120
 
119
121
  /**
120
- * Clone the `permissive` glyph entry onto the `permissive-full` key.
122
+ * Clone the `permissive` glyph entry onto every plugin-contributed tier that lacks one.
123
+ *
124
+ * The composer renders "outside the design set get no icon" for any key the closed map does
125
+ * not hold, so all four of this plugin's tiers would otherwise read as bare text next to the
126
+ * built-in shields. Each missing key gets a byte-identical clone under its own name; keys
127
+ * already present are left alone, so re-running is a no-op.
121
128
  *
122
129
  * @param {string} source the bundle's text
123
- * @returns {{ changed: boolean, source: string, entry: string | null }}
124
- * `changed: false` means the target key was already present (idempotent no-op).
130
+ * @returns {{ changed: boolean, source: string, entry: string | null, added: string[] }}
131
+ * `changed: false` means every target key was already present (idempotent no-op).
125
132
  */
126
133
  export function applyGlyphPatch(source) {
127
- if (source.includes(`["${TARGET_KEY}",`)) return { changed: false, source, entry: null }
134
+ const missing = TARGET_KEYS.filter((key) => !source.includes(`["${key}",`))
135
+ if (missing.length === 0) return { changed: false, source, entry: null, added: [] }
128
136
  const sourceEntry = sliceEntry(source, SOURCE_KEY)
129
137
  if (!sourceEntry.includes('svg')) {
130
138
  throw new GlyphPatchError(`the ["${SOURCE_KEY}",] entry does not look like a glyph`)
131
139
  }
132
- const clonedEntry = sourceEntry.replace(`["${SOURCE_KEY}",`, `["${TARGET_KEY}",`)
133
- return { changed: true, source: source.replace(sourceEntry, `${sourceEntry},\n\t\t\t${clonedEntry}`), entry: clonedEntry }
140
+ const clones = missing.map((key) => sourceEntry.replace(`["${SOURCE_KEY}",`, `["${key}",`))
141
+ return {
142
+ changed: true,
143
+ source: source.replace(sourceEntry, [sourceEntry, ...clones].join(',\n\t\t\t')),
144
+ entry: clones[0] ?? null,
145
+ added: missing,
146
+ }
134
147
  }
135
148
 
136
149
  /** Resolve the bundle to patch, or throw with an explanation. */
@@ -161,15 +174,16 @@ function main(argv) {
161
174
 
162
175
  const bundle = findBundle(argv, where)
163
176
  const original = readFileSync(bundle, 'utf8')
164
- const { changed, source: patched } = applyGlyphPatch(original)
177
+ const { changed, source: patched, added } = applyGlyphPatch(original)
178
+ const listed = added.length > 0 ? added.join('", ["') : TARGET_KEYS.join('", ["')
165
179
 
166
180
  if (!changed) {
167
- process.stdout.write(`patch-permission-glyph: ["${TARGET_KEY}",] already present in\n ${bundle}\n`)
181
+ process.stdout.write(`patch-permission-glyph: ["${TARGET_KEYS.join('", ["')}",] already present in\n ${bundle}\n`)
168
182
  return 0
169
183
  }
170
184
  if (check) {
171
185
  process.stdout.write(
172
- `patch-permission-glyph: ["${TARGET_KEY}",] is MISSING from\n ${bundle}\n` +
186
+ `patch-permission-glyph: ["${listed}",] is MISSING from\n ${bundle}\n` +
173
187
  ' (--check: nothing written; re-run without --check to apply)\n',
174
188
  )
175
189
  return 1
@@ -189,7 +203,7 @@ function main(argv) {
189
203
  }
190
204
 
191
205
  process.stdout.write(
192
- `patch-permission-glyph: added ["${TARGET_KEY}",] to\n ${bundle}\n` +
206
+ `patch-permission-glyph: added ["${listed}",] to\n ${bundle}\n` +
193
207
  ` backup: ${backup}\n` +
194
208
  ' NOTE: this edits a DSH package — re-run after any DSH upgrade or reinstall.\n',
195
209
  )