dsh-permission-rules 0.7.1 → 0.7.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/lib/index.js CHANGED
@@ -1826,11 +1826,18 @@ function assertBoolean(key, value) {
1826
1826
  //#endregion
1827
1827
  //#region src/allow-host.ts
1828
1828
  /**
1829
- * The settings-page "allow this host" action, as pure pieces: normalizing
1829
+ * The settings-page "allow this host" action, host-only half: normalizing
1830
1830
  * and validating a blocked host, inserting ONE minimal
1831
- * `match.network.domains` allow rule at the HEAD of a rule document, choosing
1832
- * which rule file the rule belongs in, and mapping the action's result onto
1833
- * the notice the page renders.
1831
+ * `match.network.domains` allow rule at the HEAD of a rule document, and
1832
+ * choosing which rule file the rule belongs in.
1833
+ *
1834
+ * The workspace choices (`allowHostWorkspaces`) and the notice mapping
1835
+ * (`allowHostNotice`) live in `./allow-host-notice.ts`: the settings page
1836
+ * imports them from the BROWSER bundle, whose bundler inlines every value
1837
+ * import — keeping them next to the `node:path`/`node:net`/`yaml` code here
1838
+ * emitted `require("process")`/`require("buffer")` calls the shell's frozen
1839
+ * module table cannot answer, failing the whole plugin load. They are
1840
+ * re-exported below so existing host-side imports keep working.
1834
1841
  *
1835
1842
  * Three invariants hold here and are asserted by `test/allow-host.spec.ts`:
1836
1843
  *
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The client-safe half of the settings-page "allow this host" action: the
3
+ * workspace choices offered for a host-level block ({@link allowHostWorkspaces})
4
+ * and the notice mapping ({@link allowHostNotice}).
5
+ *
6
+ * These live in their own module — separate from the host-only
7
+ * `./allow-host.ts` — because the settings page (the browser bundle built
8
+ * from `src/client/index.ts`) imports them, and the client bundler inlines
9
+ * every value import it meets. A single `import { allowHostNotice } from
10
+ * '../allow-host.ts'` used to drag the whole host module — `node:path`,
11
+ * `node:net`, `yaml`, `process.platform` — into `lib/client.js` as external
12
+ * `require("process")`/`require("buffer")` calls the shell's frozen module
13
+ * table cannot answer, which made the entire plugin fail to load.
14
+ *
15
+ * The hard rule for this module: NO runtime imports beyond pure type-only
16
+ * references to `./wire.ts`. Anything that needs a node builtin, the yaml
17
+ * parser, or the rule compiler belongs in `./allow-host.ts` (or deeper),
18
+ * never here.
19
+ * @module dsh-permission-rules/allow-host-notice
20
+ */
21
+ import type { AllowHostResult, RuleSourceView } from './wire.js';
22
+ /**
23
+ * The workspaces a host-level block may be allowed in: the distinct non-null
24
+ * `cwd`s of the loaded rule sources.
25
+ *
26
+ * A block with no cwd was judged by the proxy's session-less host chain, which
27
+ * any loaded workspace chain outranks the moment one exists — so writing the
28
+ * host file can leave the connection blocked while the rule sits in a file
29
+ * nothing consults. The settings page therefore offers these workspaces and
30
+ * sends the chosen one as the request's cwd; when there is exactly one (or
31
+ * none), there is nothing to choose between.
32
+ * @param sources - the snapshot's rule sources.
33
+ * @returns the distinct workspace roots, in source order.
34
+ */
35
+ export declare function allowHostWorkspaces(sources: readonly RuleSourceView[]): string[];
36
+ /** The locale keys the allow action reports through. */
37
+ export type AllowHostNoticeKey = 'allowHostSaved' | 'allowHostAlready' | 'allowHostStillBlocked' | 'allowHostFailed';
38
+ /** The notice one action result maps onto: a locale key plus its interpolation values. */
39
+ export interface AllowHostNotice {
40
+ readonly ok: boolean;
41
+ readonly key: AllowHostNoticeKey;
42
+ readonly vars: Record<string, string | number>;
43
+ }
44
+ /**
45
+ * Map an action result onto the page notice.
46
+ *
47
+ * A non-`allow` outcome outranks `alreadyAllowed`: the whole point of
48
+ * returning the RECOMPUTED decision is that the page can never say "allowed"
49
+ * while the connection is still blocked (a nearer chain with an earlier deny,
50
+ * or a mode default, still wins). `alreadyAllowed` therefore only ever reads
51
+ * as success when the decision really is `allow`.
52
+ * @param result - the `permissionRules/allowHost` result.
53
+ * @returns the notice to render.
54
+ */
55
+ export declare function allowHostNotice(result: AllowHostResult): AllowHostNotice;
56
+ //# sourceMappingURL=allow-host-notice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allow-host-notice.d.ts","sourceRoot":"","sources":["../../src/allow-host-notice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,EAAE,CAShF;AAED,wDAAwD;AACxD,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG,kBAAkB,GAAG,uBAAuB,GAAG,iBAAiB,CAAA;AAEpH,0FAA0F;AAC1F,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,kBAAkB,CAAA;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;CAC/C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAOxE"}
@@ -1,9 +1,16 @@
1
1
  /**
2
- * The settings-page "allow this host" action, as pure pieces: normalizing
2
+ * The settings-page "allow this host" action, host-only half: normalizing
3
3
  * and validating a blocked host, inserting ONE minimal
4
- * `match.network.domains` allow rule at the HEAD of a rule document, choosing
5
- * which rule file the rule belongs in, and mapping the action's result onto
6
- * the notice the page renders.
4
+ * `match.network.domains` allow rule at the HEAD of a rule document, and
5
+ * choosing which rule file the rule belongs in.
6
+ *
7
+ * The workspace choices (`allowHostWorkspaces`) and the notice mapping
8
+ * (`allowHostNotice`) live in `./allow-host-notice.ts`: the settings page
9
+ * imports them from the BROWSER bundle, whose bundler inlines every value
10
+ * import — keeping them next to the `node:path`/`node:net`/`yaml` code here
11
+ * emitted `require("process")`/`require("buffer")` calls the shell's frozen
12
+ * module table cannot answer, failing the whole plugin load. They are
13
+ * re-exported below so existing host-side imports keep working.
7
14
  *
8
15
  * Three invariants hold here and are asserted by `test/allow-host.spec.ts`:
9
16
  *
@@ -21,7 +28,8 @@
21
28
  * what makes the target choice testable without touching a real host.
22
29
  * @module dsh-permission-rules/allow-host
23
30
  */
24
- import type { AllowHostResult, RuleSourceView } from './wire.js';
31
+ export { allowHostNotice, allowHostWorkspaces } from './allow-host-notice.js';
32
+ export type { AllowHostNotice, AllowHostNoticeKey } from './allow-host-notice.js';
25
33
  /**
26
34
  * Canonical spelling of a host for the allow action, matching target parsing
27
35
  * exactly (see `normalizeHost`): lowercase, IPv6 brackets stripped, one
@@ -114,38 +122,4 @@ export interface AllowHostTargetInput {
114
122
  * a path outside the writable set.
115
123
  */
116
124
  export declare function resolveAllowHostTarget(input: AllowHostTargetInput): string;
117
- /**
118
- * The workspaces a host-level block may be allowed in: the distinct non-null
119
- * `cwd`s of the loaded rule sources.
120
- *
121
- * A block with no cwd was judged by the proxy's session-less host chain, which
122
- * any loaded workspace chain outranks the moment one exists — so writing the
123
- * host file can leave the connection blocked while the rule sits in a file
124
- * nothing consults. The settings page therefore offers these workspaces and
125
- * sends the chosen one as the request's cwd; when there is exactly one (or
126
- * none), there is nothing to choose between.
127
- * @param sources - the snapshot's rule sources.
128
- * @returns the distinct workspace roots, in source order.
129
- */
130
- export declare function allowHostWorkspaces(sources: readonly RuleSourceView[]): string[];
131
- /** The locale keys the allow action reports through. */
132
- export type AllowHostNoticeKey = 'allowHostSaved' | 'allowHostAlready' | 'allowHostStillBlocked' | 'allowHostFailed';
133
- /** The notice one action result maps onto: a locale key plus its interpolation values. */
134
- export interface AllowHostNotice {
135
- readonly ok: boolean;
136
- readonly key: AllowHostNoticeKey;
137
- readonly vars: Record<string, string | number>;
138
- }
139
- /**
140
- * Map an action result onto the page notice.
141
- *
142
- * A non-`allow` outcome outranks `alreadyAllowed`: the whole point of
143
- * returning the RECOMPUTED decision is that the page can never say "allowed"
144
- * while the connection is still blocked (a nearer chain with an earlier deny,
145
- * or a mode default, still wins). `alreadyAllowed` therefore only ever reads
146
- * as success when the decision really is `allow`.
147
- * @param result - the `permissionRules/allowHost` result.
148
- * @returns the notice to render.
149
- */
150
- export declare function allowHostNotice(result: AllowHostResult): AllowHostNotice;
151
125
  //# sourceMappingURL=allow-host.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"allow-host.d.ts","sourceRoot":"","sources":["../../src/allow-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAqBhE;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIpD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,CA0B9E;AAiCD,oGAAoG;AACpG,MAAM,WAAW,oBAAoB;IACnC,sFAAsF;IACtF,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,+FAA+F;IAC/F,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,2FAA2F;IAC3F,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,sFAAsF;IACtF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;IACzC,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAS1E;AAiCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,EAAE,CAShF;AAED,wDAAwD;AACxD,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG,kBAAkB,GAAG,uBAAuB,GAAG,iBAAiB,CAAA;AAEpH,0FAA0F;AAC1F,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,kBAAkB,CAAA;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;CAC/C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAOxE"}
1
+ {"version":3,"file":"allow-host.d.ts","sourceRoot":"","sources":["../../src/allow-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAUH,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC7E,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAqBjF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIpD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,CA0B9E;AAiCD,oGAAoG;AACpG,MAAM,WAAW,oBAAoB;IACnC,sFAAsF;IACtF,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,+FAA+F;IAC/F,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,2FAA2F;IAC3F,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,sFAAsF;IACtF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;IACzC,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAS1E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-permission-rules",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Declarative Claude Code-style permission rules plus a Codex-style process-level network policy for DeepSeek Harness: ordered allow/deny/ask rules with tool-name, argument (glob/regex), workspace-path, and network-target (domain/ip/port/scheme) matching on the tools/pre-execute waterfall, a built-in local HTTP/CONNECT proxy governing shell subprocess traffic, three network modes mapped onto the official sandbox presets, full session-log audit, HMR rule reload, and a settings page with a rule editor, block counters, and recent interception lists.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -194,6 +194,7 @@
194
194
  "test:coverage": "vitest run --coverage",
195
195
  "check:readmes": "node scripts/check-readme-sync.mjs",
196
196
  "verify:self-contained": "node scripts/verify-self-contained.mjs",
197
+ "verify:client-bundle": "node scripts/verify-client-bundle.mjs",
197
198
  "pack:check": "node scripts/prepare.mjs && pnpm pack",
198
199
  "prepare": "node scripts/prepare.mjs"
199
200
  },
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The client-safe half of the settings-page "allow this host" action: the
3
+ * workspace choices offered for a host-level block ({@link allowHostWorkspaces})
4
+ * and the notice mapping ({@link allowHostNotice}).
5
+ *
6
+ * These live in their own module — separate from the host-only
7
+ * `./allow-host.ts` — because the settings page (the browser bundle built
8
+ * from `src/client/index.ts`) imports them, and the client bundler inlines
9
+ * every value import it meets. A single `import { allowHostNotice } from
10
+ * '../allow-host.ts'` used to drag the whole host module — `node:path`,
11
+ * `node:net`, `yaml`, `process.platform` — into `lib/client.js` as external
12
+ * `require("process")`/`require("buffer")` calls the shell's frozen module
13
+ * table cannot answer, which made the entire plugin fail to load.
14
+ *
15
+ * The hard rule for this module: NO runtime imports beyond pure type-only
16
+ * references to `./wire.ts`. Anything that needs a node builtin, the yaml
17
+ * parser, or the rule compiler belongs in `./allow-host.ts` (or deeper),
18
+ * never here.
19
+ * @module dsh-permission-rules/allow-host-notice
20
+ */
21
+
22
+ import type { AllowHostResult, RuleSourceView } from './wire.ts'
23
+
24
+ /**
25
+ * The workspaces a host-level block may be allowed in: the distinct non-null
26
+ * `cwd`s of the loaded rule sources.
27
+ *
28
+ * A block with no cwd was judged by the proxy's session-less host chain, which
29
+ * any loaded workspace chain outranks the moment one exists — so writing the
30
+ * host file can leave the connection blocked while the rule sits in a file
31
+ * nothing consults. The settings page therefore offers these workspaces and
32
+ * sends the chosen one as the request's cwd; when there is exactly one (or
33
+ * none), there is nothing to choose between.
34
+ * @param sources - the snapshot's rule sources.
35
+ * @returns the distinct workspace roots, in source order.
36
+ */
37
+ export function allowHostWorkspaces(sources: readonly RuleSourceView[]): string[] {
38
+ const seen = new Set<string>()
39
+ const workspaces: string[] = []
40
+ for (const source of sources) {
41
+ if (source.cwd === null || seen.has(source.cwd)) continue
42
+ seen.add(source.cwd)
43
+ workspaces.push(source.cwd)
44
+ }
45
+ return workspaces
46
+ }
47
+
48
+ /** The locale keys the allow action reports through. */
49
+ export type AllowHostNoticeKey = 'allowHostSaved' | 'allowHostAlready' | 'allowHostStillBlocked' | 'allowHostFailed'
50
+
51
+ /** The notice one action result maps onto: a locale key plus its interpolation values. */
52
+ export interface AllowHostNotice {
53
+ readonly ok: boolean
54
+ readonly key: AllowHostNoticeKey
55
+ readonly vars: Record<string, string | number>
56
+ }
57
+
58
+ /**
59
+ * Map an action result onto the page notice.
60
+ *
61
+ * A non-`allow` outcome outranks `alreadyAllowed`: the whole point of
62
+ * returning the RECOMPUTED decision is that the page can never say "allowed"
63
+ * while the connection is still blocked (a nearer chain with an earlier deny,
64
+ * or a mode default, still wins). `alreadyAllowed` therefore only ever reads
65
+ * as success when the decision really is `allow`.
66
+ * @param result - the `permissionRules/allowHost` result.
67
+ * @returns the notice to render.
68
+ */
69
+ export function allowHostNotice(result: AllowHostResult): AllowHostNotice {
70
+ if (!result.ok) return { ok: false, key: 'allowHostFailed', vars: { error: result.error ?? 'unknown error' } }
71
+ if (result.outcome !== 'allow') {
72
+ return { ok: false, key: 'allowHostStillBlocked', vars: { outcome: result.outcome ?? 'unknown' } }
73
+ }
74
+ if (result.alreadyAllowed) return { ok: true, key: 'allowHostAlready', vars: {} }
75
+ return { ok: true, key: 'allowHostSaved', vars: { path: result.path ?? '', reloaded: result.reloaded } }
76
+ }
package/src/allow-host.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  /**
2
- * The settings-page "allow this host" action, as pure pieces: normalizing
2
+ * The settings-page "allow this host" action, host-only half: normalizing
3
3
  * and validating a blocked host, inserting ONE minimal
4
- * `match.network.domains` allow rule at the HEAD of a rule document, choosing
5
- * which rule file the rule belongs in, and mapping the action's result onto
6
- * the notice the page renders.
4
+ * `match.network.domains` allow rule at the HEAD of a rule document, and
5
+ * choosing which rule file the rule belongs in.
6
+ *
7
+ * The workspace choices (`allowHostWorkspaces`) and the notice mapping
8
+ * (`allowHostNotice`) live in `./allow-host-notice.ts`: the settings page
9
+ * imports them from the BROWSER bundle, whose bundler inlines every value
10
+ * import — keeping them next to the `node:path`/`node:net`/`yaml` code here
11
+ * emitted `require("process")`/`require("buffer")` calls the shell's frozen
12
+ * module table cannot answer, failing the whole plugin load. They are
13
+ * re-exported below so existing host-side imports keep working.
7
14
  *
8
15
  * Three invariants hold here and are asserted by `test/allow-host.spec.ts`:
9
16
  *
@@ -27,7 +34,11 @@ import { isIP } from 'node:net'
27
34
  import { isMap, isSeq, parseDocument } from 'yaml'
28
35
  import type { YAMLSeq } from 'yaml'
29
36
  import { RuleError, normalizeHost } from './rules.ts'
30
- import type { AllowHostResult, RuleSourceView } from './wire.ts'
37
+
38
+ // Client-safe pieces re-exported from their own module — see the module doc
39
+ // above for why they must not live in this host-only file.
40
+ export { allowHostNotice, allowHostWorkspaces } from './allow-host-notice.ts'
41
+ export type { AllowHostNotice, AllowHostNoticeKey } from './allow-host-notice.ts'
31
42
 
32
43
  /**
33
44
  * Why a generated rule may legitimately live in a rule file the parser would
@@ -251,57 +262,3 @@ function samePath(a: string, b: string): boolean {
251
262
  const right = resolve(b)
252
263
  return process.platform === 'win32' ? left.toLowerCase() === right.toLowerCase() : left === right
253
264
  }
254
-
255
- /**
256
- * The workspaces a host-level block may be allowed in: the distinct non-null
257
- * `cwd`s of the loaded rule sources.
258
- *
259
- * A block with no cwd was judged by the proxy's session-less host chain, which
260
- * any loaded workspace chain outranks the moment one exists — so writing the
261
- * host file can leave the connection blocked while the rule sits in a file
262
- * nothing consults. The settings page therefore offers these workspaces and
263
- * sends the chosen one as the request's cwd; when there is exactly one (or
264
- * none), there is nothing to choose between.
265
- * @param sources - the snapshot's rule sources.
266
- * @returns the distinct workspace roots, in source order.
267
- */
268
- export function allowHostWorkspaces(sources: readonly RuleSourceView[]): string[] {
269
- const seen = new Set<string>()
270
- const workspaces: string[] = []
271
- for (const source of sources) {
272
- if (source.cwd === null || seen.has(source.cwd)) continue
273
- seen.add(source.cwd)
274
- workspaces.push(source.cwd)
275
- }
276
- return workspaces
277
- }
278
-
279
- /** The locale keys the allow action reports through. */
280
- export type AllowHostNoticeKey = 'allowHostSaved' | 'allowHostAlready' | 'allowHostStillBlocked' | 'allowHostFailed'
281
-
282
- /** The notice one action result maps onto: a locale key plus its interpolation values. */
283
- export interface AllowHostNotice {
284
- readonly ok: boolean
285
- readonly key: AllowHostNoticeKey
286
- readonly vars: Record<string, string | number>
287
- }
288
-
289
- /**
290
- * Map an action result onto the page notice.
291
- *
292
- * A non-`allow` outcome outranks `alreadyAllowed`: the whole point of
293
- * returning the RECOMPUTED decision is that the page can never say "allowed"
294
- * while the connection is still blocked (a nearer chain with an earlier deny,
295
- * or a mode default, still wins). `alreadyAllowed` therefore only ever reads
296
- * as success when the decision really is `allow`.
297
- * @param result - the `permissionRules/allowHost` result.
298
- * @returns the notice to render.
299
- */
300
- export function allowHostNotice(result: AllowHostResult): AllowHostNotice {
301
- if (!result.ok) return { ok: false, key: 'allowHostFailed', vars: { error: result.error ?? 'unknown error' } }
302
- if (result.outcome !== 'allow') {
303
- return { ok: false, key: 'allowHostStillBlocked', vars: { outcome: result.outcome ?? 'unknown' } }
304
- }
305
- if (result.alreadyAllowed) return { ok: true, key: 'allowHostAlready', vars: {} }
306
- return { ok: true, key: 'allowHostSaved', vars: { path: result.path ?? '', reloaded: result.reloaded } }
307
- }
@@ -11,7 +11,7 @@
11
11
 
12
12
  import * as React from 'react'
13
13
  import type { ReactElement } from 'react'
14
- import { allowHostNotice, allowHostWorkspaces } from '../allow-host.ts'
14
+ import { allowHostNotice, allowHostWorkspaces } from '../allow-host-notice.ts'
15
15
  import type { PermissionRulesLocaleKey } from './locales.ts'
16
16
  import type { AllowHostRequest, AllowHostResult, NetworkBlockView, PermissionRulesSnapshot, RulesReadResult, RulesReloadResult, RulesSaveResult } from '../wire.ts'
17
17