@homeflare/alchemy 0.40.0 → 0.42.0
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/dist/caddy/index.js.map +2 -2
- package/dist/ceph/index.js +2 -1
- package/dist/ceph/index.js.map +3 -3
- package/dist/launchd/fake-runner.d.ts.map +1 -1
- package/dist/launchd/index.d.ts +1 -1
- package/dist/launchd/index.d.ts.map +1 -1
- package/dist/launchd/index.js +7 -1
- package/dist/launchd/index.js.map +5 -5
- package/dist/launchd/local-runner.d.ts.map +1 -1
- package/dist/launchd/runner.d.ts +21 -0
- package/dist/launchd/runner.d.ts.map +1 -1
- package/dist/launchd/sudo-runner.d.ts.map +1 -1
- package/dist/linux/directory-lifecycle.d.ts.map +1 -1
- package/dist/linux/fake-linux-host.d.ts.map +1 -1
- package/dist/linux/index.d.ts +1 -1
- package/dist/linux/index.d.ts.map +1 -1
- package/dist/linux/index.js +16 -4
- package/dist/linux/index.js.map +7 -7
- package/dist/linux/ssh-runner.d.ts.map +1 -1
- package/dist/linux/sudo-runner.d.ts.map +1 -1
- package/dist/opnsense/alias-form.d.ts +14 -1
- package/dist/opnsense/alias-form.d.ts.map +1 -1
- package/dist/opnsense/alias.d.ts +1 -1
- package/dist/opnsense/alias.d.ts.map +1 -1
- package/dist/opnsense/group-form.d.ts +8 -2
- package/dist/opnsense/group-form.d.ts.map +1 -1
- package/dist/opnsense/group.d.ts +1 -1
- package/dist/opnsense/group.d.ts.map +1 -1
- package/dist/opnsense/index.js +15 -7
- package/dist/opnsense/index.js.map +7 -7
- package/dist/opnsense/wire.d.ts +42 -12
- package/dist/opnsense/wire.d.ts.map +1 -1
- package/dist/proxmox/acl.d.ts.map +1 -1
- package/dist/proxmox/api-token.d.ts.map +1 -1
- package/dist/proxmox/credential-errors.d.ts +18 -4
- package/dist/proxmox/credential-errors.d.ts.map +1 -1
- package/dist/proxmox/credentials.d.ts +35 -1
- package/dist/proxmox/credentials.d.ts.map +1 -1
- package/dist/proxmox/group.d.ts.map +1 -1
- package/dist/proxmox/index.js +81 -51
- package/dist/proxmox/index.js.map +19 -17
- package/dist/proxmox/lease-cache.d.ts +67 -5
- package/dist/proxmox/lease-cache.d.ts.map +1 -1
- package/dist/proxmox/mint.d.ts +4 -2
- package/dist/proxmox/mint.d.ts.map +1 -1
- package/dist/proxmox/node-network.d.ts.map +1 -1
- package/dist/proxmox/role.d.ts.map +1 -1
- package/dist/proxmox/storage-form.d.ts +28 -3
- package/dist/proxmox/storage-form.d.ts.map +1 -1
- package/dist/proxmox/storage-plugin-options.d.ts +2 -0
- package/dist/proxmox/storage-plugin-options.d.ts.map +1 -0
- package/dist/proxmox/storage-wire.d.ts +28 -1
- package/dist/proxmox/storage-wire.d.ts.map +1 -1
- package/dist/proxmox/storage.d.ts +6 -1
- package/dist/proxmox/storage.d.ts.map +1 -1
- package/dist/proxmox/unreadable-read.d.ts +8 -1
- package/dist/proxmox/unreadable-read.d.ts.map +1 -1
- package/dist/proxmox/user.d.ts.map +1 -1
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js.map +2 -2
- package/docs/credentials.md +14 -1
- package/docs/distilled-interim.md +7 -0
- package/docs/launchd.md +5 -3
- package/docs/provider-standard.md +5 -1
- package/docs/talos-argocd-dogfood.md +160 -0
- package/package.json +2 -2
- package/src/launchd/fake-runner.ts +2 -0
- package/src/launchd/index.ts +8 -1
- package/src/launchd/local-runner.ts +16 -1
- package/src/launchd/runner.ts +22 -0
- package/src/launchd/sudo-runner.ts +3 -0
- package/src/linux/directory-lifecycle.ts +18 -3
- package/src/linux/directory-sudo.test.ts +59 -0
- package/src/linux/directory.test.ts +34 -8
- package/src/linux/fake-linux-host.ts +2 -0
- package/src/linux/index.ts +1 -0
- package/src/linux/ssh-runner.ts +4 -0
- package/src/linux/sudo-runner.ts +3 -0
- package/src/opnsense/alias-form.test.ts +24 -12
- package/src/opnsense/alias-form.ts +20 -7
- package/src/opnsense/alias.test.ts +13 -6
- package/src/opnsense/alias.ts +16 -10
- package/src/opnsense/group-form.test.ts +12 -8
- package/src/opnsense/group-form.ts +13 -4
- package/src/opnsense/group.test.ts +18 -7
- package/src/opnsense/group.ts +10 -3
- package/src/opnsense/wire.ts +57 -12
- package/src/proxmox/acl.ts +7 -1
- package/src/proxmox/api-token.ts +7 -1
- package/src/proxmox/credential-errors.ts +19 -5
- package/src/proxmox/credentials.test.ts +73 -1
- package/src/proxmox/credentials.ts +72 -1
- package/src/proxmox/group.ts +7 -1
- package/src/proxmox/lease-cache-mint.test.ts +70 -0
- package/src/proxmox/lease-cache.test.ts +83 -7
- package/src/proxmox/lease-cache.ts +74 -10
- package/src/proxmox/mint.ts +12 -5
- package/src/proxmox/node-network.ts +7 -1
- package/src/proxmox/role.ts +7 -1
- package/src/proxmox/storage-form.ts +48 -8
- package/src/proxmox/storage-plugin-options.ts +45 -0
- package/src/proxmox/storage-update.test.ts +183 -0
- package/src/proxmox/storage-wire.ts +54 -7
- package/src/proxmox/storage.ts +25 -7
- package/src/proxmox/unreadable-read.ts +16 -4
- package/src/proxmox/user.ts +7 -1
- package/src/proxmox/zfs-pool.ts +7 -1
- package/src/release/index.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssh-runner.d.ts","sourceRoot":"","sources":["../../src/linux/ssh-runner.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAWnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,0DAA0D;IAC1D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,8CAA8C;IAC9C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAmCF,gGAAgG;AAChG,eAAO,MAAM,SAAS,YACX,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,mBAAmB,GAAG,eAAe,CAAC,CAAC,GACvF,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,SAC5B,MAAM,UACL,MAAM,UACN,UAAU,KACjB,OAAO,CAAC,UAAU,CAiBpB,CAAC;AAUF;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,YAAmB,gBAAgB,KAAG,OAAO,CAAC,UAAU,
|
|
1
|
+
{"version":3,"file":"ssh-runner.d.ts","sourceRoot":"","sources":["../../src/linux/ssh-runner.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAWnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,0DAA0D;IAC1D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,8CAA8C;IAC9C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAmCF,gGAAgG;AAChG,eAAO,MAAM,SAAS,YACX,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,mBAAmB,GAAG,eAAe,CAAC,CAAC,GACvF,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,SAC5B,MAAM,UACL,MAAM,UACN,UAAU,KACjB,OAAO,CAAC,UAAU,CAiBpB,CAAC;AAUF;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,YAAmB,gBAAgB,KAAG,OAAO,CAAC,UAAU,CAiE7E,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sudo-runner.d.ts","sourceRoot":"","sources":["../../src/linux/sudo-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,KAAK,gBAAgB,EAAa,MAAM,iBAAiB,CAAC;AAEnE,OAAO,EAUL,gBAAgB,EAKjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,MAAM,EAAe,MAAM,iBAAiB,CAAC;AAG3D,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IACpD,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF,iGAAiG;AACjG,MAAM,MAAM,WAAW,GAAG;IACxB,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,CAAC;AAeF,eAAO,MAAM,iBAAiB,eAAgB,SAAS,MAAM,EAAE,QAAQ,WAAW,KAAG,
|
|
1
|
+
{"version":3,"file":"sudo-runner.d.ts","sourceRoot":"","sources":["../../src/linux/sudo-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,KAAK,gBAAgB,EAAa,MAAM,iBAAiB,CAAC;AAEnE,OAAO,EAUL,gBAAgB,EAKjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,MAAM,EAAe,MAAM,iBAAiB,CAAC;AAG3D,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IACpD,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF,iGAAiG;AACjG,MAAM,MAAM,WAAW,GAAG;IACxB,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,CAAC;AAeF,eAAO,MAAM,iBAAiB,eAAgB,SAAS,MAAM,EAAE,QAAQ,WAAW,KAAG,UA8DpF,CAAC;AAsBF,gGAAgG;AAChG,eAAO,MAAM,aAAa,YAAmB,oBAAoB,KAAG,OAAO,CAAC,UAAU,CAYrF,CAAC"}
|
|
@@ -23,7 +23,20 @@ import type * as alias from '@distilled.cloud/opnsense/firewall_alias';
|
|
|
23
23
|
* noop declaration needs — see the exported `propsFromLive` alias in alias.ts's barrel and
|
|
24
24
|
* `alias-form.test.ts`'s round-trip test (`matches(attributesOf(uuid, live), attributesOf(uuid,
|
|
25
25
|
* live))` is always true by construction, and so is the general case).
|
|
26
|
+
*
|
|
27
|
+
* `live` is `ModelAliasReadItem` (the WHOLE-MODEL `get()`'s read-shaped item), not `AliasItem` —
|
|
28
|
+
* `type`/`interface`/`proto`/`categories` decode as option maps, not strings (OPNSENSE-2; see
|
|
29
|
+
* wire.ts's module doc). `selectedOf`/`selectedOneOf` extract the selected key(s) back to the
|
|
30
|
+
* same plain-string/string[] shape `AliasAttributes` already declared.
|
|
31
|
+
*
|
|
32
|
+
* `content` is `.\AliasContentField`, which overrides `getNodeData()` independently of
|
|
33
|
+
* `BaseListField` (`Firewall/FieldTypes/AliasContentField.php:69-76`) — it still decodes as a map,
|
|
34
|
+
* but every entry is unconditionally `selected: 1` (it is a raw newline-separated list, not a
|
|
35
|
+
* real option set), and ITS wire separator is `\n`, not `,`. `Object.keys` preserves the
|
|
36
|
+
* insertion order OPNsense's own `explode()` built the map in, which matters for a host/URL list
|
|
37
|
+
* in a way `selectedOf`'s alphabetical sort does not preserve — so this one field is reconstructed
|
|
38
|
+
* directly rather than through `selectedOf`.
|
|
26
39
|
*/
|
|
27
|
-
export declare const attributesOf: (uuid: string, live: alias.
|
|
40
|
+
export declare const attributesOf: (uuid: string, live: alias.ModelAliasReadItem) => AliasAttributes;
|
|
28
41
|
export declare const matches: (attributes: AliasAttributes, props: AliasProps) => boolean;
|
|
29
42
|
//# sourceMappingURL=alias-form.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alias-form.d.ts","sourceRoot":"","sources":["../../src/opnsense/alias-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAGvE
|
|
1
|
+
{"version":3,"file":"alias-form.d.ts","sourceRoot":"","sources":["../../src/opnsense/alias-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAGvE;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY,SAAU,MAAM,QAAQ,KAAK,CAAC,kBAAkB,KAAG,eAY1E,CAAC;AAEH,eAAO,MAAM,OAAO,eAAgB,eAAe,SAAS,UAAU,KAAG,OAUC,CAAC"}
|
package/dist/opnsense/alias.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface OpnsenseFirewallAlias extends Resource<'Opnsense.Firewall.Alias
|
|
|
42
42
|
export declare const OpnsenseFirewallAlias: import("alchemy").ResourceClass<OpnsenseFirewallAlias>;
|
|
43
43
|
export declare const isOpnsenseFirewallAlias: (value: unknown) => value is OpnsenseFirewallAlias;
|
|
44
44
|
/** Exported for direct testing against `fake-opnsense.ts` — the same seam `../discord/*.ts` uses. */
|
|
45
|
-
export declare const spec: OpnsenseSpec<AliasProps, Alias.
|
|
45
|
+
export declare const spec: OpnsenseSpec<AliasProps, Alias.ModelAliasReadItem, AliasAttributes, OpnsenseOpError>;
|
|
46
46
|
/**
|
|
47
47
|
* The pure declaration renderer: given the uuid `get()`'s map key names and the `AliasItem`
|
|
48
48
|
* decoded at that key, return the props a declaration needs so `firewallAlias.spec.matches`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alias.d.ts","sourceRoot":"","sources":["../../src/opnsense/alias.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"alias.d.ts","sourceRoot":"","sources":["../../src/opnsense/alias.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAW,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxE,MAAM,WAAW,UAAU;IACzB,mGAAmG;IACnG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mGAAmG;IACnG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,gGAAgG;IAChG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAsB,SAAQ,QAAQ,CACrD,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,KAAK,EACL,oBAAoB,CACrB;CAAG;AAEJ,eAAO,MAAM,qBAAqB,wDAGhC,CAAC;AAEH,eAAO,MAAM,uBAAuB,UAAW,OAAO,KAAG,KAAK,IAAI,qBAGA,CAAC;AAEnE,qGAAqG;AACrG,eAAO,MAAM,IAAI,EAAE,YAAY,CAC7B,UAAU,EACV,KAAK,CAAC,kBAAkB,EACxB,eAAe,EACf,eAAe,CAOhB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,qBAAe,CAAC;AAE1C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;CAAyB,CAAC;AAE/C,eAAO,MAAM,6BAA6B,2IAIvC,CAAC;AAEJ;uGACuG;AACvG,eAAO,MAAM,aAAa,OAAQ,MAAM,SAAS,UAAU,sGACP,CAAC"}
|
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { GroupAttributes, GroupProps } from './group.ts';
|
|
7
7
|
import type * as group from '@distilled.cloud/opnsense/firewall_group';
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* ★ Attributes and the declaration renderer are one function — see alias-form.ts's header note.
|
|
10
|
+
*
|
|
11
|
+
* `live` is `ModelIfgroupentryReadItem` (the WHOLE-MODEL `get()`'s read-shaped item), not
|
|
12
|
+
* `GroupItem` — `members` (`.\InterfaceField`, `Multiple="Y"`) decodes as an option map, not a
|
|
13
|
+
* comma string (OPNSENSE-2; see wire.ts's module doc).
|
|
14
|
+
*/
|
|
15
|
+
export declare const attributesOf: (uuid: string, live: group.ModelIfgroupentryReadItem) => GroupAttributes;
|
|
10
16
|
export declare const matches: (attributes: GroupAttributes, props: GroupProps) => boolean;
|
|
11
17
|
//# sourceMappingURL=group-form.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group-form.d.ts","sourceRoot":"","sources":["../../src/opnsense/group-form.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAGvE
|
|
1
|
+
{"version":3,"file":"group-form.d.ts","sourceRoot":"","sources":["../../src/opnsense/group-form.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAGvE;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,SACjB,MAAM,QACN,KAAK,CAAC,yBAAyB,KACpC,eAOD,CAAC;AAEH,eAAO,MAAM,OAAO,eAAgB,eAAe,SAAS,UAAU,KAAG,OAKL,CAAC"}
|
package/dist/opnsense/group.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface OpnsenseFirewallGroup extends Resource<'Opnsense.Firewall.Group
|
|
|
29
29
|
}
|
|
30
30
|
export declare const OpnsenseFirewallGroup: import("alchemy").ResourceClass<OpnsenseFirewallGroup>;
|
|
31
31
|
export declare const isOpnsenseFirewallGroup: (value: unknown) => value is OpnsenseFirewallGroup;
|
|
32
|
-
export declare const spec: OpnsenseSpec<GroupProps, Group.
|
|
32
|
+
export declare const spec: OpnsenseSpec<GroupProps, Group.ModelIfgroupentryReadItem, GroupAttributes, OpnsenseOpError>;
|
|
33
33
|
/** The pure declaration renderer — see alias.ts's `propsFromLive` for the full contract note. */
|
|
34
34
|
export declare const propsFromLive: typeof attributesOf;
|
|
35
35
|
export declare const handlers: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/opnsense/group.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/opnsense/group.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAW,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,kGAAkG;IAClG,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,+FAA+F;IAC/F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAsB,SAAQ,QAAQ,CACrD,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,KAAK,EACL,oBAAoB,CACrB;CAAG;AAEJ,eAAO,MAAM,qBAAqB,wDAEhC,CAAC;AAEH,eAAO,MAAM,uBAAuB,UAAW,OAAO,KAAG,KAAK,IAAI,qBAGA,CAAC;AAEnE,eAAO,MAAM,IAAI,EAAE,YAAY,CAC7B,UAAU,EACV,KAAK,CAAC,yBAAyB,EAC/B,eAAe,EACf,eAAe,CAOhB,CAAC;AAEF,iGAAiG;AACjG,eAAO,MAAM,aAAa,qBAAe,CAAC;AAE1C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;CAAyB,CAAC;AAE/C,eAAO,MAAM,6BAA6B,2IAIvC,CAAC;AAEJ,gGAAgG;AAChG,eAAO,MAAM,aAAa,OAAQ,MAAM,SAAS,UAAU,sGACP,CAAC"}
|
package/dist/opnsense/index.js
CHANGED
|
@@ -20,18 +20,26 @@ var numField = (value, fallback) => {
|
|
|
20
20
|
const parsed = Number(value);
|
|
21
21
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
22
22
|
};
|
|
23
|
+
var selectedOf = (map) => {
|
|
24
|
+
if (map === undefined)
|
|
25
|
+
return [];
|
|
26
|
+
const keys = Object.entries(map).filter(([key, entry]) => key !== "" && entry?.selected === 1).map(([key]) => key);
|
|
27
|
+
return [...new Set(keys)].sort();
|
|
28
|
+
};
|
|
29
|
+
var selectedOneOf = (map) => selectedOf(map)[0] ?? "";
|
|
23
30
|
|
|
24
31
|
// src/opnsense/alias-form.ts
|
|
25
32
|
var attributesOf = (uuid, live) => ({
|
|
26
|
-
categories:
|
|
27
|
-
content: live.content ??
|
|
33
|
+
categories: selectedOf(live.categories),
|
|
34
|
+
content: Object.keys(live.content ?? {}).join(`
|
|
35
|
+
`),
|
|
28
36
|
counters: bool01(live.counters, false),
|
|
29
37
|
description: live.description ?? "",
|
|
30
38
|
enabled: bool01(live.enabled, true),
|
|
31
|
-
interface: live.interface
|
|
39
|
+
interface: selectedOneOf(live.interface),
|
|
32
40
|
name: live.name,
|
|
33
|
-
proto: live.proto
|
|
34
|
-
type: live.type,
|
|
41
|
+
proto: selectedOf(live.proto).join(","),
|
|
42
|
+
type: selectedOneOf(live.type),
|
|
35
43
|
updatefreq: numField(live.updatefreq, 0),
|
|
36
44
|
uuid
|
|
37
45
|
});
|
|
@@ -158,7 +166,7 @@ import * as Effect5 from "effect/Effect";
|
|
|
158
166
|
var attributesOf3 = (uuid, live) => ({
|
|
159
167
|
description: live.descr ?? "",
|
|
160
168
|
ifname: live.ifname,
|
|
161
|
-
members:
|
|
169
|
+
members: selectedOf(live.members),
|
|
162
170
|
nogroup: bool01(live.nogroup, false),
|
|
163
171
|
sequence: numField(live.sequence, 0),
|
|
164
172
|
uuid
|
|
@@ -207,5 +215,5 @@ export {
|
|
|
207
215
|
providers
|
|
208
216
|
};
|
|
209
217
|
|
|
210
|
-
//# debugId=
|
|
218
|
+
//# debugId=681DFCB41A03047B64756E2164756E21
|
|
211
219
|
//# sourceMappingURL=index.js.map
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/opnsense/alias.ts", "../src/opnsense/wire.ts", "../src/opnsense/alias-form.ts", "../src/opnsense/resource.ts", "../src/opnsense/credentials.ts", "../src/opnsense/policy.ts", "../src/opnsense/category.ts", "../src/opnsense/category-form.ts", "../src/opnsense/group.ts", "../src/opnsense/group-form.ts", "../src/opnsense/providers.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * `Opnsense.Firewall.Alias` — a read-only declaration of one `firewall/alias` item on the edge.\n * READ-ONLY BY DESIGN — see policy.ts's header. `reconcile`/`delete` always refuse.\n *\n * ★ READ GOES THROUGH `get()`, NEVER `getItem`/`searchItem`. `get()` (`firewall_alias.ts#get`)\n * returns the WHOLE model tree — `{ alias: { aliases: { alias: { <uuid>:
|
|
6
|
-
"/**\n * How OPNsense's MVC model spells a value on the wire, and how to get it back to something\n * comparable — the `../proxmox/values.ts` idea, scoped to this vendor's own spellings.\n *\n * ★ OPNSENSE HAS EXACTLY TWO WIRE SHAPES THIS FAMILY NEEDS. Every `BooleanField` is the
|
|
7
|
-
"/**\n * `Opnsense.Firewall.Alias`'s wire shape: how a live `AliasItem` becomes attributes, and the\n * comparison that decides whether a declaration already matches it. Split out of alias.ts to\n * keep that file under the house's 250-line cap, mirroring `../proxmox/user-wire.ts`.\n *\n * ⛔ SCOPE, STATED ONCE. `AliasItem` (firewall_alias.ts) carries far more fields than this props\n * type declares — three groups are left out on purpose:\n * 1. Runtime counters (`current_items`, `last_updated`, `eval_nomatch`, `eval_match`,\n * `in_block_p`/`_b`, `in_pass_p`/`_b`, `out_block_p`/`_b`, `out_pass_p`/`_b`) — these are\n * the alias's hit/block statistics, not configuration, exactly the \"stats\" the task that\n * opened this family says to exclude.\n * 2. `path_expression` — only meaningful for the `urljson` alias type; a niche field this\n * first import pass leaves for a later one rather than modelling one type's one field.\n * 3. `password`/`username`/`authtype`/`expire` — the URL-fetch credential fields for the\n * `url`/`urltable`/`urljson` alias types. S25 (secrets stay out of props/attributes):\n * `password` is a literal stored credential and Alchemy persists Attributes unencrypted,\n * the same reasoning `../proxmox/user.ts` gives for shipping no `password` prop at all.\n */\nimport type { AliasAttributes, AliasProps } from './alias.ts';\nimport type * as alias from '@distilled.cloud/opnsense/firewall_alias';\nimport { bool01,
|
|
5
|
+
"/**\n * `Opnsense.Firewall.Alias` — a read-only declaration of one `firewall/alias` item on the edge.\n * READ-ONLY BY DESIGN — see policy.ts's header. `reconcile`/`delete` always refuse.\n *\n * ★ READ GOES THROUGH `get()`, NEVER `getItem`/`searchItem`. `get()` (`firewall_alias.ts#get`)\n * returns the WHOLE model tree — `{ alias: { aliases: { alias: { <uuid>: ModelAliasReadItem,\n * ... } } } }` — so finding one item by uuid is an object-key lookup, not a request the vendor\n * could answer ambiguously. `Alias` has no per-item `getAlias` operation at all (`getItemAction`\n * decorates its result — see the distilled clone's package README) — only `get` (whole model)\n * and `searchAlias` (paginated, see below) exist, so this family's whole-model read is the only\n * option here (unlike Category/Group, whose `getCategory`/`getGroup` per-item operations now\n * exist correctly — OPNSENSE-1 — but this file still uses `get()` uniformly across the three\n * resources; switching Category/Group to their per-item operation is a separate, later PR).\n * `ModelAliasReadItem`'s list-shaped fields (`type`/`interface`/`proto`/`categories`) decode as\n * option maps, not strings (OPNSENSE-2) — see `alias-form.ts`'s `attributesOf`.\n *\n * ⛔ NOT `searchAlias`. It is POST-shaped (a read, per OPNsense's own MVC convention — see the\n * family's docs page) and paginated with no typed limit/offset the generated request schema\n * exposes, so a large alias set could silently page-truncate through it. `get()` has neither\n * problem: GET, and the WHOLE model in one call, no pagination to fall short of.\n *\n * ⚠️ `uuid` IS THE ONLY STABLE IDENTITY THIS FAMILY DECLARES BY. A name is close to stable\n * (`AliasNameField` is unique) but is not what `get()`'s map is keyed on — obtain the live\n * uuid once (a future import script does this from a live read, via `propsFromLive` below)\n * and declare it from then on, the same \"adoption is explicit, one identity at a time\" posture\n * `../proxmox/user.ts` and `../netbox/resource.ts` document for their own families.\n */\nimport { adopt } from 'alchemy/AdoptPolicy';\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as Alias from '@distilled.cloud/opnsense/firewall_alias';\nimport type { OpnsenseOpError } from '@distilled.cloud/opnsense/Protocol';\nimport * as Effect from 'effect/Effect';\nimport { attributesOf, matches } from './alias-form.ts';\nimport type { OpnsenseRequirements, OpnsenseSpec } from './resource.ts';\nimport { opnsenseHandlers } from './resource.ts';\n\nexport interface AliasProps {\n /** OPNsense-assigned uuid — see the file header on why this is the only identity to declare by. */\n readonly uuid: string;\n readonly name: string;\n /** e.g. `host`, `network`, `port`, `url`, `geoip`, `networkgroup` — OPNsense's own alias types. */\n readonly type: string;\n /** Default true, matching OPNsense's own `enabled=1`. */\n readonly enabled?: boolean;\n /** The alias's members — a host list, a CIDR, a URL, depending on `type`. */\n readonly content?: string;\n readonly interface?: string;\n /** `IPv4` or `IPv6`, for a `geoip`/`networkgroup` alias; comma-joined on the wire (wire.ts). */\n readonly proto?: string;\n readonly counters?: boolean;\n /** Minutes between refreshes, for a `url`/`urltable` alias. */\n readonly updatefreq?: number;\n readonly categories?: readonly string[];\n readonly description?: string;\n}\n\nexport interface AliasAttributes {\n readonly uuid: string;\n readonly name: string;\n readonly type: string;\n readonly enabled: boolean;\n readonly content: string;\n readonly interface: string;\n readonly proto: string;\n readonly counters: boolean;\n readonly updatefreq: number;\n readonly categories: readonly string[];\n readonly description: string;\n}\n\nexport interface OpnsenseFirewallAlias extends Resource<\n 'Opnsense.Firewall.Alias',\n AliasProps,\n AliasAttributes,\n never,\n OpnsenseRequirements\n> {}\n\nexport const OpnsenseFirewallAlias = Resource<OpnsenseFirewallAlias>('Opnsense.Firewall.Alias', {\n // The edge's own config, not this stack's to reclaim — see policy.ts.\n defaultRemovalPolicy: 'retain',\n});\n\nexport const isOpnsenseFirewallAlias = (value: unknown): value is OpnsenseFirewallAlias =>\n typeof value === 'object' &&\n value !== null &&\n (value as { Type?: unknown }).Type === 'Opnsense.Firewall.Alias';\n\n/** Exported for direct testing against `fake-opnsense.ts` — the same seam `../discord/*.ts` uses. */\nexport const spec: OpnsenseSpec<\n AliasProps,\n Alias.ModelAliasReadItem,\n AliasAttributes,\n OpnsenseOpError\n> = {\n attributes: attributesOf,\n fetchLive: (props) =>\n Alias.get({}).pipe(Effect.map((res) => res.alias?.aliases?.alias?.[props.uuid])),\n matches,\n resourceType: 'Opnsense.Firewall.Alias',\n};\n\n/**\n * The pure declaration renderer: given the uuid `get()`'s map key names and the `AliasItem`\n * decoded at that key, return the props a declaration needs so `firewallAlias.spec.matches`\n * reports noop against it. A later import script iterates `Object.entries(map)` and calls this\n * once per row to generate an `alchemy.run.ts` row — see `alias-form.test.ts`'s round-trip test.\n */\nexport const propsFromLive = attributesOf;\n\nexport const handlers = opnsenseHandlers(spec);\n\nexport const OpnsenseFirewallAliasProvider = () =>\n Provider.effect(\n OpnsenseFirewallAlias,\n Effect.succeed(OpnsenseFirewallAlias.Provider.of(handlers)),\n );\n\n/** `firewallAlias('name-alias', { uuid, name, type })` — `adopt(true)` piped by default (H5), so\n * declaring a live alias's own uuid takes it over silently instead of failing `OwnedBySomeoneElse`. */\nexport const firewallAlias = (id: string, props: AliasProps) =>\n OpnsenseFirewallAlias(id, props).pipe(adopt(true));\n",
|
|
6
|
+
"/**\n * How OPNsense's MVC model spells a value on the wire, and how to get it back to something\n * comparable — the `../proxmox/values.ts` idea, scoped to this vendor's own spellings.\n *\n * ★ OPNSENSE HAS EXACTLY TWO WIRE SHAPES THIS FAMILY NEEDS ON WRITE (props stay plain values;\n * this family never writes, but `matches` compares against them). Every `BooleanField` is the\n * string `\"1\"` or `\"0\"` (never a JSON boolean). Every multi-select `OptionField`/\n * `ModelRelationField`'s WRITE-side spelling is a SINGLE comma-joined string, not a JSON array.\n *\n * ⚠️ READ IS A THIRD SHAPE (OPNSENSE-2, distilled `homeflare/opnsense`'s `read-shape.ts`): the\n * SAME list-shaped fields decode on GET as an option map `{key: {value, selected}}`, not a\n * string — `AliasItem`/`GroupItem` (this family's OLD, pre-fix type references) were the\n * WRITE-shaped structs; `fetchLive` actually decodes `ModelAliasReadItem`/\n * `ModelIfgroupentryReadItem` (the WHOLE-MODEL GET's read-shaped item), whose `type`/\n * `interface`/`proto`/`categories`/`members` are option maps. `selectedOf`/`selectedOneOf`\n * below extract the selected key(s) back to the SAME plain-string/string[] shape `matches`\n * already compares, so the rest of this family's logic (and `AliasAttributes`/\n * `GroupAttributes`'s own field types) is unchanged.\n *\n * ⚠️ COMMA-LIST ORDER ROUND-TRIP IS NOT ASSUMED STABLE. OPNsense builds these from a PHP\n * associative array, and nothing in the model XML promises the join order survives a\n * save-then-read unchanged. Sorting and deduplicating both sides before comparing is what makes\n * `matches` (alias-form.ts, group-form.ts) actually settle instead of reporting drift on a\n * re-ordered list that changed nothing — the same reasoning `../proxmox/user-wire.ts`'s\n * `groupSet` documents for PVE's `groups` field.\n */\n\n/** OPNsense's own boolean spelling. Absent takes the caller's stated default — OPNsense's own\n * default for a `BooleanField` is form-specific, so there is no house-wide fallback here. */\nexport const bool01 = (value: string | undefined, fallback: boolean): boolean =>\n value === undefined ? fallback : value === '1';\n\n/** The write direction, for symmetry — unused today (this family never writes), kept so a form\n * helper never has to guess the spelling if lifting the read-only policy adds one later. */\nexport const toBool01 = (value: boolean): string => (value ? '1' : '0');\n\n/**\n * A comma-joined multi-select column, normalised to a sorted, deduplicated set — see the module\n * header's ⚠️ for why normalising (not just splitting) is what makes a diff settle.\n */\nexport const csvSet = (value: string | undefined): readonly string[] => {\n if (value === undefined || value.trim() === '') return [];\n const items = value\n .split(',')\n .map((item) => item.trim())\n .filter((item) => item !== '');\n return [...new Set(items)].sort();\n};\n\n/** A declared array, normalised the SAME way `csvSet` normalises the wire, so `matches` compares\n * two sets built by one function rather than trusting the caller's array to already be sorted. */\nexport const csvSetOf = (value: readonly string[] | undefined): readonly string[] =>\n csvSet(value === undefined ? undefined : value.join(','));\n\n/**\n * OPNsense's numeric-as-string fields (`\"60\"`, `\"0\"`), tolerant of an already-numeric input the\n * same way `../proxmox/user-wire.ts`'s `int` is tolerant of PVE's mixed number/string releases.\n */\nexport const numField = (value: string | undefined, fallback: number): number => {\n if (value === undefined) return fallback;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : fallback;\n};\n\n/** One entry of a GET-decoded option map — `{value: <label>, selected: 0|1}`, see this file's\n * module doc. Structural, not imported from the SDK: every generated `*Map` field shares this\n * exact shape (`OptionEntry` in each service module), and staying structural here avoids a\n * per-resource-file type import just for this. */\nexport interface OpnsenseOptionEntry {\n readonly value: string;\n readonly selected: number;\n}\n\n/**\n * The selected key(s) of a GET-decoded option map, normalised the same way `csvSet` normalises\n * the write-side comma string — sorted, deduplicated, empty-key filtered. Use for a\n * `Multiple=\"Y\"` field (`AliasItem.proto`, `AliasItem.categories`, `GroupItem.members`); for a\n * single-select field use `selectedOneOf`.\n */\nexport const selectedOf = (\n map: Readonly<Record<string, OpnsenseOptionEntry | undefined>> | undefined,\n): readonly string[] => {\n if (map === undefined) return [];\n const keys = Object.entries(map)\n .filter(([key, entry]) => key !== '' && entry?.selected === 1)\n .map(([key]) => key);\n return [...new Set(keys)].sort();\n};\n\n/** Single-select convenience over `selectedOf` — the one selected key, or `''` if none (the same\n * \"absent\" spelling a plain string field uses, e.g. `AliasItem.type`/`GroupItem`'s own\n * `.\\InterfaceField` members when not `Multiple=\"Y\"`). */\nexport const selectedOneOf = (\n map: Readonly<Record<string, OpnsenseOptionEntry | undefined>> | undefined,\n): string => selectedOf(map)[0] ?? '';\n\n/** The encode direction, for test fixtures: every listed key marked `selected: 1`, mirroring a\n * real OPNsense GET response (`{[key]: {value, selected: 1}}`) — so a fixture exercises the exact\n * decode path `selectedOf`/`selectedOneOf` handle, not the pre-fix plain-string shape. */\nexport const optionMap = (...keys: readonly string[]): Record<string, OpnsenseOptionEntry> =>\n Object.fromEntries(keys.map((key) => [key, { value: key, selected: 1 }]));\n",
|
|
7
|
+
"/**\n * `Opnsense.Firewall.Alias`'s wire shape: how a live `AliasItem` becomes attributes, and the\n * comparison that decides whether a declaration already matches it. Split out of alias.ts to\n * keep that file under the house's 250-line cap, mirroring `../proxmox/user-wire.ts`.\n *\n * ⛔ SCOPE, STATED ONCE. `AliasItem` (firewall_alias.ts) carries far more fields than this props\n * type declares — three groups are left out on purpose:\n * 1. Runtime counters (`current_items`, `last_updated`, `eval_nomatch`, `eval_match`,\n * `in_block_p`/`_b`, `in_pass_p`/`_b`, `out_block_p`/`_b`, `out_pass_p`/`_b`) — these are\n * the alias's hit/block statistics, not configuration, exactly the \"stats\" the task that\n * opened this family says to exclude.\n * 2. `path_expression` — only meaningful for the `urljson` alias type; a niche field this\n * first import pass leaves for a later one rather than modelling one type's one field.\n * 3. `password`/`username`/`authtype`/`expire` — the URL-fetch credential fields for the\n * `url`/`urltable`/`urljson` alias types. S25 (secrets stay out of props/attributes):\n * `password` is a literal stored credential and Alchemy persists Attributes unencrypted,\n * the same reasoning `../proxmox/user.ts` gives for shipping no `password` prop at all.\n */\nimport type { AliasAttributes, AliasProps } from './alias.ts';\nimport type * as alias from '@distilled.cloud/opnsense/firewall_alias';\nimport { bool01, csvSetOf, numField, selectedOf, selectedOneOf } from './wire.ts';\n\n/**\n * ★ ATTRIBUTES AND THE DECLARATION RENDERER ARE ONE FUNCTION. Attributes ARE exactly the props a\n * noop declaration needs — see the exported `propsFromLive` alias in alias.ts's barrel and\n * `alias-form.test.ts`'s round-trip test (`matches(attributesOf(uuid, live), attributesOf(uuid,\n * live))` is always true by construction, and so is the general case).\n *\n * `live` is `ModelAliasReadItem` (the WHOLE-MODEL `get()`'s read-shaped item), not `AliasItem` —\n * `type`/`interface`/`proto`/`categories` decode as option maps, not strings (OPNSENSE-2; see\n * wire.ts's module doc). `selectedOf`/`selectedOneOf` extract the selected key(s) back to the\n * same plain-string/string[] shape `AliasAttributes` already declared.\n *\n * `content` is `.\\AliasContentField`, which overrides `getNodeData()` independently of\n * `BaseListField` (`Firewall/FieldTypes/AliasContentField.php:69-76`) — it still decodes as a map,\n * but every entry is unconditionally `selected: 1` (it is a raw newline-separated list, not a\n * real option set), and ITS wire separator is `\\n`, not `,`. `Object.keys` preserves the\n * insertion order OPNsense's own `explode()` built the map in, which matters for a host/URL list\n * in a way `selectedOf`'s alphabetical sort does not preserve — so this one field is reconstructed\n * directly rather than through `selectedOf`.\n */\nexport const attributesOf = (uuid: string, live: alias.ModelAliasReadItem): AliasAttributes => ({\n categories: selectedOf(live.categories),\n content: Object.keys(live.content ?? {}).join('\\n'),\n counters: bool01(live.counters, false),\n description: live.description ?? '',\n enabled: bool01(live.enabled, true),\n interface: selectedOneOf(live.interface),\n name: live.name,\n proto: selectedOf(live.proto).join(','),\n type: selectedOneOf(live.type),\n updatefreq: numField(live.updatefreq, 0),\n uuid,\n});\n\nexport const matches = (attributes: AliasAttributes, props: AliasProps): boolean =>\n attributes.name === props.name &&\n attributes.type === props.type &&\n attributes.enabled === (props.enabled ?? true) &&\n attributes.content === (props.content ?? '') &&\n attributes.interface === (props.interface ?? '') &&\n attributes.proto === (props.proto ?? '') &&\n attributes.counters === (props.counters ?? false) &&\n attributes.updatefreq === (props.updatefreq ?? 0) &&\n attributes.description === (props.description ?? '') &&\n attributes.categories.join(',') === csvSetOf(props.categories).join(',');\n",
|
|
8
8
|
"/**\n * One shape for every OPNsense object this family declares — `read`/`diff` mirroring\n * `../discord/resource.ts`'s marker-less-API posture exactly (S7–S9, H1), and `reconcile`/\n * `delete` replaced end to end with the read-only refusal `policy.ts` states.\n *\n * ★ WHY ONE ENGINE FOR THREE RESOURCES. `alias.ts`, `category.ts` and `group.ts` differ only in\n * which `firewall_*` module they call and how a live item maps to attributes — the read/diff/\n * reconcile/delete flow is identical, so it lives here once, the way `netbox/resource.ts` and\n * `discord/resource.ts` already do for their own families.\n *\n * ★ `Unowned(attrs)` ON A COLD READ, PLAIN ATTRIBUTES ON A WARM ONE (S7, S8). None of Alias,\n * Category or Group carries an ownership marker OPNsense itself understands — no tag, no\n * metadata field a stack could stamp — so this is the same \"marker-less API\" S8 describes, and\n * the same posture `../discord/resource.ts` documents against `Snippet.ts`'s reference read.\n * `adopt(true)` (H5), piped on by each resource file's convenience constructor, is what turns\n * an `Unowned` first read into a silent one-time takeover instead of an `OwnedBySomeoneElse`\n * refusal — exactly what an import script generating `alchemy.run.ts` rows from live reads\n * needs: declare what is live, adopt it, change nothing.\n *\n * ⛔ `reconcile` AND `delete` NEVER CALL AN SDK OPERATION. `reconcile` still OBSERVES first (one\n * GET, via `read`) so the refusal it raises can say whether it would have created or updated —\n * S9's \"observe before you write\" doctrine, kept for the error message even though there is no\n * write to guard. `delete` observes nothing: it refuses unconditionally, because under this\n * policy a delete never succeeds regardless of whether the object is still live. See policy.ts.\n */\nimport { Unowned } from 'alchemy/AdoptPolicy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport type { OpnsenseOpContext } from '@distilled.cloud/opnsense/Protocol';\nimport * as Effect from 'effect/Effect';\nimport type * as HttpClient from 'effect/unstable/http/HttpClient';\nimport { CredentialsFromEnv } from './credentials.ts';\nimport { refuseWrite } from './policy.ts';\n\n/** What every stack must still provide after `opnsenseHandlers` bakes in `CredentialsFromEnv`. */\nexport type OpnsenseRequirements = HttpClient.HttpClient;\n\n/** Every resource in this family identifies its live object by OPNsense's own assigned uuid. */\nexport interface OpnsenseIdentity {\n readonly uuid: string;\n}\n\n/**\n * One OPNsense object's read path. `Live` is whatever the generated `get()` response decodes to\n * for one item (an `AliasItem`, `CategoryItem`, `GroupItem`); `E` is left to each resource file\n * to declare — in practice always `OpnsenseOpError`, since every generated operation shares one\n * error channel (protocol.ts).\n */\nexport type OpnsenseSpec<Props extends OpnsenseIdentity, Live, Attributes extends object, E> = {\n readonly resourceType: string;\n /** Absent when `props.uuid` is not a key in the live model's map — see each resource file. */\n readonly fetchLive: (props: Props) => Effect.Effect<Live | undefined, E, OpnsenseOpContext>;\n /** Also the declaration renderer (S.T. propsFromLive re-export) — see each `*-form.ts`. */\n readonly attributes: (uuid: string, live: Live) => Attributes;\n readonly matches: (attributes: Attributes, props: Props) => boolean;\n};\n\nexport const opnsenseOperations = <\n Props extends OpnsenseIdentity,\n Live,\n Attributes extends object,\n E,\n>(\n spec: OpnsenseSpec<Props, Live, Attributes, E>,\n) => {\n const read = (props: Props) =>\n spec\n .fetchLive(props)\n .pipe(\n Effect.map((live) => (live === undefined ? undefined : spec.attributes(props.uuid, live))),\n );\n\n return {\n read: ({ olds, output }: { olds: Props; output: Attributes | undefined }) =>\n Effect.gen(function* () {\n const attrs = yield* read(olds);\n if (attrs === undefined) return undefined;\n // Warm read: a prior apply already adopted this exact uuid — ours.\n if (output !== undefined) return attrs;\n // Cold read (fresh store, or state lost): no ownership marker exists on this vendor.\n return Unowned(attrs);\n }),\n\n diff: (news: Input<Props>, output: Attributes | undefined) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n const live = yield* read(news);\n if (live === undefined) return { action: 'update' } as const;\n return spec.matches(live, news)\n ? ({ action: 'noop' } as const)\n : ({ action: 'update' } as const);\n }),\n\n reconcile: (news: Props) =>\n Effect.gen(function* () {\n const live = yield* read(news);\n return yield* refuseWrite(\n spec.resourceType,\n news.uuid,\n live === undefined ? 'create' : 'update',\n );\n }),\n\n destroy: (olds: Props) => refuseWrite(spec.resourceType, olds.uuid, 'delete'),\n };\n};\n\n/**\n * The provider handlers for a spec'd OPNsense object, wired once. `list` answers empty — every\n * `get()` in this family returns the WHOLE model tree with no per-account/per-scope narrowing,\n * so a `list` that enumerated it would sweep the estate's entire alias/category/group set into\n * one plan; adoption stays explicit, one uuid at a time, the same reasoning netbox's and\n * discord's own `list` give for a shared or marker-less object space (S12).\n *\n * `nuke: { skip: true }` — `delete` refuses unconditionally (policy.ts), so `alchemy unsafe\n * nuke` could never do anything here but fail loudly on every object it found; skipping keeps\n * that refusal out of a nuke census entirely, matching `postgres/database.ts` and\n * `github/repository-ruleset.ts`'s own `nuke: { skip: true }` for the same reason.\n */\nexport const opnsenseHandlers = <\n Props extends OpnsenseIdentity,\n Live,\n Attributes extends object,\n E,\n>(\n spec: OpnsenseSpec<Props, Live, Attributes, E>,\n) => {\n const ops = opnsenseOperations(spec);\n // ⚠️ GENERIC OVER ITS OWN ERROR AND REQUIREMENT CHANNELS, NOT PINNED TO `E`/`OpnsenseOpContext`.\n // `read`/`diff` fail with `E` and need `OpnsenseOpContext`; `reconcile` fails with\n // `E | OpnsenseWriteRefused` (it observes, then refuses) and still needs `OpnsenseOpContext`\n // for that observe; `delete` fails with `OpnsenseWriteRefused` alone and needs NOTHING (it\n // never observes) — three different signatures sharing one credentials-providing wrapper.\n const withCredentials = <A, Err, R>(effect: Effect.Effect<A, Err, R>) =>\n Effect.provide(effect, CredentialsFromEnv);\n return {\n list: () => Effect.succeed([]),\n nuke: { skip: true },\n read: ({ olds, output }: { olds: Props; output: Attributes | undefined }) =>\n withCredentials(ops.read({ olds, output })),\n diff: ({ news, output }: { news: Input<Props>; output: Attributes | undefined }) =>\n withCredentials(ops.diff(news, output)),\n reconcile: ({ news }: { news: Props }) => withCredentials(ops.reconcile(news)),\n delete: ({ olds }: { olds: Props }) => withCredentials(ops.destroy(olds)),\n };\n};\n",
|
|
9
9
|
"/**\n * OPNsense credentials — a key/secret pair REFERENCE, resolved lazily (S24), never a prop.\n *\n * ⛔ NO KEY OR SECRET IS EVER A PROP OR AN ATTRIBUTE. Alchemy persists Attributes unencrypted\n * (`State/StateEncoding.ts@v2.0.0-beta.79#encodeState`), so nothing a stack file holds may be a\n * credential. `@distilled.cloud/opnsense/Credentials` already ships exactly the shape S24 asks\n * for: `Credentials` is a lazy `Context.Service<Credentials, Effect.Effect<Config>>`, resolved\n * on the calling fiber inside each operation (`OpnsenseProtocol`'s `encode`), never at layer\n * build. This file re-exports it rather than re-implementing it — the same choice\n * `../discord/credentials.ts` made for its own SDK's `CredentialsFromEnv`.\n *\n * ★ `CredentialsFromEnv` READS `OPNSENSE_URL` / `OPNSENSE_API_KEY` / `OPNSENSE_API_SECRET` via\n * Effect's `Config` (the SDK's own `credentials.ts` — no vendor-standard env var names exist\n * for OPNsense's API, unlike Forgejo's `FORGEJO_URL`). The resource never sees the values,\n * only the deploy process's environment does, and the secret is `Redacted` the moment it is\n * read — exactly the \"reference, never a value\" shape S25 asks for.\n *\n * ⚠️ NO OpenBao MINT YET, THE SAME GAP `../discord/credentials.ts` DOCUMENTS. H8's target\n * (`Alchemy.Stack({ secrets })`) is unreleased (PR 1728, `main` only). Until the estate is on a\n * release that carries it, the deploy process's own environment is the credential seam — a\n * human or CI job exports the three `OPNSENSE_*` variables from wherever it mints them. This\n * package never mints, reads or logs the key/secret itself; it only names the variables.\n */\nimport type { Config } from '@distilled.cloud/opnsense/Credentials';\nimport {\n Credentials,\n CredentialsFromEnv,\n credentials,\n} from '@distilled.cloud/opnsense/Credentials';\n\n/** Re-exported so a consumer never has to reach into `@distilled.cloud/opnsense` by hand. */\nexport { Credentials, CredentialsFromEnv, credentials };\nexport type { Config as OpnsenseCredentialsConfig };\n\n/**\n * `OpnsenseCredentials.fromEnv()` — the layer `opnsenseHandlers` (resource.ts) provides by\n * default. A thin, named alias over `CredentialsFromEnv` so a stack's import reads `Opnsense.*`\n * throughout, the way `../discord/credentials.ts`'s `fromEnv()` keeps `Discord.*` names at the\n * front door.\n */\nexport const fromEnv = () => CredentialsFromEnv;\n",
|
|
10
10
|
"/**\n * `OpnsenseWriteRefused` — the one error every resource in this family can raise for a write,\n * and the only thing standing between \"read-only for now\" and an actual change against the edge.\n *\n * ⛔ TIM, 2026-09-24: UniFi AND OPNSENSE ARE READ-ONLY FOR NOW. `Opnsense.Firewall.Alias`,\n * `.Category` and `.Group` (alias.ts, category.ts, group.ts) exist to `read`, `diff` and adopt\n * against the live edge — never to change it. This is the estate's EDGE firewall: a failed\n * probe gets the caller's IP CrowdSec-banned (`edge-crowdsec-bans-lan-ssh-probes` in the shared\n * estate memory), and nothing about that risk is worth a resource that can also reconfigure it.\n * Every `reconcile` and `delete` in this directory calls `refuseWrite` below and NOTHING ELSE —\n * none of them imports an `add`/`set`/`del`/`toggle` operation from `@distilled.cloud/opnsense`\n * at all, and `write-refusal.test.ts` proves it with a fake HTTP client that fails the test on\n * any request that is not a GET.\n *\n * ★ LIFTING THIS IS A KIT CHANGE, NEVER A PROP. There is no `allowWrite` flag on any resource\n * here — a flag is one bad `true` (or one bad default) away from a live edit to the box that\n * fronts the whole estate. Lifting the policy means editing this file, and writing the\n * create/update/delete bodies these resources do not have, a decision Tim makes explicitly by\n * changing code and review, not a runtime toggle a stack can set.\n */\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\n\nexport type OpnsenseWriteAction = 'create' | 'update' | 'delete';\n\nexport class OpnsenseWriteRefused extends Data.TaggedError('OpnsenseWriteRefused')<{\n readonly resourceType: string;\n readonly id: string;\n readonly action: OpnsenseWriteAction;\n}> {\n override get message(): string {\n return (\n `${this.resourceType} ${this.id}: refusing to ${this.action} — read-only by Tim's rule, ` +\n '2026-09-24; lifting it is a kit change, not a prop.'\n );\n }\n}\n\n/**\n * Every `reconcile`/`delete` handler in this family funnels through this one call — see\n * `resource.ts`'s `opnsenseOperations`. Nothing downstream of this function can reach a write:\n * it never calls an SDK operation, it only builds and fails with the typed refusal above.\n */\nexport const refuseWrite = (\n resourceType: string,\n id: string,\n action: OpnsenseWriteAction,\n): Effect.Effect<never, OpnsenseWriteRefused> =>\n Effect.fail(new OpnsenseWriteRefused({ action, id, resourceType }));\n",
|
|
11
11
|
"/**\n * `Opnsense.Firewall.Category` — a read-only declaration of one `firewall/category` item (the\n * tag OPNsense's UI groups aliases, rules etc. by) on the edge. READ-ONLY BY DESIGN — see\n * policy.ts's header. `reconcile`/`delete` always refuse. Mirrors alias.ts exactly; see that\n * file's header for why `get()` (whole model) is the read source rather than `getCategory` —\n * the same generation gap (no `uuid` in `GetCategoryRequest`'s schema) applies here too.\n */\nimport { adopt } from 'alchemy/AdoptPolicy';\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as Category from '@distilled.cloud/opnsense/firewall_category';\nimport type { OpnsenseOpError } from '@distilled.cloud/opnsense/Protocol';\nimport * as Effect from 'effect/Effect';\nimport { attributesOf, matches } from './category-form.ts';\nimport type { OpnsenseRequirements, OpnsenseSpec } from './resource.ts';\nimport { opnsenseHandlers } from './resource.ts';\n\nexport interface CategoryProps {\n readonly uuid: string;\n /** Pattern `/[^,]+/` on the wire — a comma in the name would collide with the multi-select join. */\n readonly name: string;\n /** Whether OPNsense itself created this category (its own subsystems tag things this way); a\n * category a person made is `false`/absent. */\n readonly auto?: boolean;\n /** Six hex digits, no `#`, e.g. `0000FF`. */\n readonly color?: string;\n}\n\nexport interface CategoryAttributes {\n readonly uuid: string;\n readonly name: string;\n readonly auto: boolean;\n readonly color: string;\n}\n\nexport interface OpnsenseFirewallCategory extends Resource<\n 'Opnsense.Firewall.Category',\n CategoryProps,\n CategoryAttributes,\n never,\n OpnsenseRequirements\n> {}\n\nexport const OpnsenseFirewallCategory = Resource<OpnsenseFirewallCategory>(\n 'Opnsense.Firewall.Category',\n {\n defaultRemovalPolicy: 'retain',\n },\n);\n\nexport const isOpnsenseFirewallCategory = (value: unknown): value is OpnsenseFirewallCategory =>\n typeof value === 'object' &&\n value !== null &&\n (value as { Type?: unknown }).Type === 'Opnsense.Firewall.Category';\n\nexport const spec: OpnsenseSpec<\n CategoryProps,\n Category.CategoryItem,\n CategoryAttributes,\n OpnsenseOpError\n> = {\n attributes: attributesOf,\n fetchLive: (props) =>\n Category.get({}).pipe(Effect.map((res) => res.category?.categories?.category?.[props.uuid])),\n matches,\n resourceType: 'Opnsense.Firewall.Category',\n};\n\n/** The pure declaration renderer — see alias.ts's `propsFromLive` for the full contract note. */\nexport const propsFromLive = attributesOf;\n\nexport const handlers = opnsenseHandlers(spec);\n\nexport const OpnsenseFirewallCategoryProvider = () =>\n Provider.effect(\n OpnsenseFirewallCategory,\n Effect.succeed(OpnsenseFirewallCategory.Provider.of(handlers)),\n );\n\n/** `firewallCategory('name', { uuid, name })` — `adopt(true)` piped by default (H5). */\nexport const firewallCategory = (id: string, props: CategoryProps) =>\n OpnsenseFirewallCategory(id, props).pipe(adopt(true));\n",
|
|
12
12
|
"/**\n * `Opnsense.Firewall.Category`'s wire shape: how a live `CategoryItem` becomes attributes, and\n * the comparison that decides whether a declaration already matches it. Mirrors alias-form.ts.\n */\nimport type { CategoryAttributes, CategoryProps } from './category.ts';\nimport type * as category from '@distilled.cloud/opnsense/firewall_category';\nimport { bool01 } from './wire.ts';\n\n/** ★ Attributes and the declaration renderer are one function — see alias-form.ts's header note. */\nexport const attributesOf = (uuid: string, live: category.CategoryItem): CategoryAttributes => ({\n auto: bool01(live.auto, false),\n color: live.color ?? '',\n name: live.name,\n uuid,\n});\n\nexport const matches = (attributes: CategoryAttributes, props: CategoryProps): boolean =>\n attributes.name === props.name &&\n attributes.auto === (props.auto ?? false) &&\n attributes.color === (props.color ?? '');\n",
|
|
13
|
-
"/**\n * `Opnsense.Firewall.Group` — a read-only declaration of one `firewall/group` item (an\n * \"Interface Group\", not a permission group) on the edge. READ-ONLY BY DESIGN — see policy.ts's\n * header. `reconcile`/`delete` always refuse. Mirrors alias.ts exactly; see that file's header\n * for why `get()` (whole model) is the read source rather than `getGroup`
|
|
14
|
-
"/**\n * `Opnsense.Firewall.Group`'s wire shape: how a live `GroupItem` (an interface group) becomes\n * attributes, and the comparison that decides whether a declaration already matches it. Mirrors\n * alias-form.ts.\n */\nimport type { GroupAttributes, GroupProps } from './group.ts';\nimport type * as group from '@distilled.cloud/opnsense/firewall_group';\nimport { bool01,
|
|
13
|
+
"/**\n * `Opnsense.Firewall.Group` — a read-only declaration of one `firewall/group` item (an\n * \"Interface Group\", not a permission group) on the edge. READ-ONLY BY DESIGN — see policy.ts's\n * header. `reconcile`/`delete` always refuse. Mirrors alias.ts exactly; see that file's header\n * for why `get()` (whole model) is the read source uniformly across this family rather than the\n * now-correct `getGroup` per-item operation (OPNSENSE-1 fixed `GetGroupRequest`'s missing `uuid`,\n * but switching to it is a separate, later PR). `members` decodes as an option map, not a comma\n * string — see `group-form.ts`'s `attributesOf` (OPNSENSE-2).\n */\nimport { adopt } from 'alchemy/AdoptPolicy';\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as Group from '@distilled.cloud/opnsense/firewall_group';\nimport type { OpnsenseOpError } from '@distilled.cloud/opnsense/Protocol';\nimport * as Effect from 'effect/Effect';\nimport { attributesOf, matches } from './group-form.ts';\nimport type { OpnsenseRequirements, OpnsenseSpec } from './resource.ts';\nimport { opnsenseHandlers } from './resource.ts';\n\nexport interface GroupProps {\n readonly uuid: string;\n /** OPNsense's own `GroupNameField` — the interface group's name. */\n readonly ifname: string;\n /** Member interface names (`lan`, `opt1`, …), not their descriptions. */\n readonly members: readonly string[];\n /** OPNsense's own \"no group\" flag — excludes it from certain interface pickers. Default false. */\n readonly nogroup?: boolean;\n /** UI list position, `0`..`9999`. Carries no evaluation-order meaning for this object type. */\n readonly sequence?: number;\n readonly description?: string;\n}\n\nexport interface GroupAttributes {\n readonly uuid: string;\n readonly ifname: string;\n readonly members: readonly string[];\n readonly nogroup: boolean;\n readonly sequence: number;\n readonly description: string;\n}\n\nexport interface OpnsenseFirewallGroup extends Resource<\n 'Opnsense.Firewall.Group',\n GroupProps,\n GroupAttributes,\n never,\n OpnsenseRequirements\n> {}\n\nexport const OpnsenseFirewallGroup = Resource<OpnsenseFirewallGroup>('Opnsense.Firewall.Group', {\n defaultRemovalPolicy: 'retain',\n});\n\nexport const isOpnsenseFirewallGroup = (value: unknown): value is OpnsenseFirewallGroup =>\n typeof value === 'object' &&\n value !== null &&\n (value as { Type?: unknown }).Type === 'Opnsense.Firewall.Group';\n\nexport const spec: OpnsenseSpec<\n GroupProps,\n Group.ModelIfgroupentryReadItem,\n GroupAttributes,\n OpnsenseOpError\n> = {\n attributes: attributesOf,\n fetchLive: (props) =>\n Group.get({}).pipe(Effect.map((res) => res.group?.ifgroupentry?.[props.uuid])),\n matches,\n resourceType: 'Opnsense.Firewall.Group',\n};\n\n/** The pure declaration renderer — see alias.ts's `propsFromLive` for the full contract note. */\nexport const propsFromLive = attributesOf;\n\nexport const handlers = opnsenseHandlers(spec);\n\nexport const OpnsenseFirewallGroupProvider = () =>\n Provider.effect(\n OpnsenseFirewallGroup,\n Effect.succeed(OpnsenseFirewallGroup.Provider.of(handlers)),\n );\n\n/** `firewallGroup('name', { uuid, ifname, members })` — `adopt(true)` piped by default (H5). */\nexport const firewallGroup = (id: string, props: GroupProps) =>\n OpnsenseFirewallGroup(id, props).pipe(adopt(true));\n",
|
|
14
|
+
"/**\n * `Opnsense.Firewall.Group`'s wire shape: how a live `GroupItem` (an interface group) becomes\n * attributes, and the comparison that decides whether a declaration already matches it. Mirrors\n * alias-form.ts.\n */\nimport type { GroupAttributes, GroupProps } from './group.ts';\nimport type * as group from '@distilled.cloud/opnsense/firewall_group';\nimport { bool01, csvSetOf, numField, selectedOf } from './wire.ts';\n\n/**\n * ★ Attributes and the declaration renderer are one function — see alias-form.ts's header note.\n *\n * `live` is `ModelIfgroupentryReadItem` (the WHOLE-MODEL `get()`'s read-shaped item), not\n * `GroupItem` — `members` (`.\\InterfaceField`, `Multiple=\"Y\"`) decodes as an option map, not a\n * comma string (OPNSENSE-2; see wire.ts's module doc).\n */\nexport const attributesOf = (\n uuid: string,\n live: group.ModelIfgroupentryReadItem,\n): GroupAttributes => ({\n description: live.descr ?? '',\n ifname: live.ifname,\n members: selectedOf(live.members),\n nogroup: bool01(live.nogroup, false),\n sequence: numField(live.sequence, 0),\n uuid,\n});\n\nexport const matches = (attributes: GroupAttributes, props: GroupProps): boolean =>\n attributes.ifname === props.ifname &&\n attributes.nogroup === (props.nogroup ?? false) &&\n attributes.sequence === (props.sequence ?? 0) &&\n attributes.description === (props.description ?? '') &&\n attributes.members.join(',') === csvSetOf(props.members).join(',');\n",
|
|
15
15
|
"/**\n * All three OPNsense firewall providers as one layer for a stack.\n *\n * Layer.mergeAll(opnsenseProviders(), …the stack's other providers)\n * .pipe(Layer.provide(FetchHttpClient.layer))\n *\n * S16: a subpath's `providers()` plays the role upstream's own `Providers.ts` does — one minimal\n * insertion per family, requirements that never contain `unknown`. `FetchHttpClient.layer` is\n * the stack's own responsibility to provide, mirroring `../discord/providers.ts`.\n */\nimport * as Layer from 'effect/Layer';\nimport { OpnsenseFirewallAliasProvider } from './alias.ts';\nimport { OpnsenseFirewallCategoryProvider } from './category.ts';\nimport { OpnsenseFirewallGroupProvider } from './group.ts';\n\nexport const providers = () =>\n Layer.mergeAll(\n OpnsenseFirewallAliasProvider(),\n OpnsenseFirewallCategoryProvider(),\n OpnsenseFirewallGroupProvider(),\n );\n"
|
|
16
16
|
],
|
|
17
|
-
"mappings": ";
|
|
18
|
-
"debugId": "
|
|
17
|
+
"mappings": ";AA2BA;AACA;AACA;AACA;AAEA;;;ACHO,IAAM,SAAS,CAAC,OAA2B,aAChD,UAAU,YAAY,WAAW,UAAU;AAUtC,IAAM,SAAS,CAAC,UAAiD;AAAA,EACtE,IAAI,UAAU,aAAa,MAAM,KAAK,MAAM;AAAA,IAAI,OAAO,CAAC;AAAA,EACxD,MAAM,QAAQ,MACX,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,CAAC,SAAS,SAAS,EAAE;AAAA,EAC/B,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,EAAE,KAAK;AAAA;AAK3B,IAAM,WAAW,CAAC,UACvB,OAAO,UAAU,YAAY,YAAY,MAAM,KAAK,GAAG,CAAC;AAMnD,IAAM,WAAW,CAAC,OAA2B,aAA6B;AAAA,EAC/E,IAAI,UAAU;AAAA,IAAW,OAAO;AAAA,EAChC,MAAM,SAAS,OAAO,KAAK;AAAA,EAC3B,OAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAAA;AAkBrC,IAAM,aAAa,CACxB,QACsB;AAAA,EACtB,IAAI,QAAQ;AAAA,IAAW,OAAO,CAAC;AAAA,EAC/B,MAAM,OAAO,OAAO,QAAQ,GAAG,EAC5B,OAAO,EAAE,KAAK,WAAW,QAAQ,MAAM,OAAO,aAAa,CAAC,EAC5D,IAAI,EAAE,SAAS,GAAG;AAAA,EACrB,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,EAAE,KAAK;AAAA;AAM1B,IAAM,gBAAgB,CAC3B,QACW,WAAW,GAAG,EAAE,MAAM;;;ACrD5B,IAAM,eAAe,CAAC,MAAc,UAAqD;AAAA,EAC9F,YAAY,WAAW,KAAK,UAAU;AAAA,EACtC,SAAS,OAAO,KAAK,KAAK,WAAW,CAAC,CAAC,EAAE,KAAK;AAAA,CAAI;AAAA,EAClD,UAAU,OAAO,KAAK,UAAU,KAAK;AAAA,EACrC,aAAa,KAAK,eAAe;AAAA,EACjC,SAAS,OAAO,KAAK,SAAS,IAAI;AAAA,EAClC,WAAW,cAAc,KAAK,SAAS;AAAA,EACvC,MAAM,KAAK;AAAA,EACX,OAAO,WAAW,KAAK,KAAK,EAAE,KAAK,GAAG;AAAA,EACtC,MAAM,cAAc,KAAK,IAAI;AAAA,EAC7B,YAAY,SAAS,KAAK,YAAY,CAAC;AAAA,EACvC;AACF;AAEO,IAAM,UAAU,CAAC,YAA6B,UACnD,WAAW,SAAS,MAAM,QAC1B,WAAW,SAAS,MAAM,QAC1B,WAAW,aAAa,MAAM,WAAW,SACzC,WAAW,aAAa,MAAM,WAAW,OACzC,WAAW,eAAe,MAAM,aAAa,OAC7C,WAAW,WAAW,MAAM,SAAS,OACrC,WAAW,cAAc,MAAM,YAAY,UAC3C,WAAW,gBAAgB,MAAM,cAAc,MAC/C,WAAW,iBAAiB,MAAM,eAAe,OACjD,WAAW,WAAW,KAAK,GAAG,MAAM,SAAS,MAAM,UAAU,EAAE,KAAK,GAAG;;;ACxCzE;AACA;AAGA;;;ACLA;AAAA;AAAA;AAAA;AAAA;AAgBO,IAAM,UAAU,MAAM;;;ACpB7B;AACA;AAAA;AAIO,MAAM,6BAAkC,iBAAY,sBAAsB,EAI9E;AAAA,MACY,OAAO,GAAW;AAAA,IAC7B,OACE,GAAG,KAAK,gBAAgB,KAAK,mBAAmB,KAAK,uCACrD;AAAA;AAGN;AAOO,IAAM,cAAc,CACzB,cACA,IACA,WAEO,YAAK,IAAI,qBAAqB,EAAE,QAAQ,IAAI,aAAa,CAAC,CAAC;;;AFS7D,IAAM,qBAAqB,CAMhC,SACG;AAAA,EACH,MAAM,OAAO,CAAC,UACZ,KACG,UAAU,KAAK,EACf,KACQ,YAAI,CAAC,SAAU,SAAS,YAAY,YAAY,KAAK,WAAW,MAAM,MAAM,IAAI,CAAE,CAC3F;AAAA,EAEJ,OAAO;AAAA,IACL,MAAM,GAAG,MAAM,aACN,YAAI,UAAU,GAAG;AAAA,MACtB,MAAM,QAAQ,OAAO,KAAK,IAAI;AAAA,MAC9B,IAAI,UAAU;AAAA,QAAW;AAAA,MAEzB,IAAI,WAAW;AAAA,QAAW,OAAO;AAAA,MAEjC,OAAO,QAAQ,KAAK;AAAA,KACrB;AAAA,IAEH,MAAM,CAAC,MAAoB,WAClB,YAAI,UAAU,GAAG;AAAA,MACtB,IAAI,WAAW,aAAa,CAAC,WAAW,IAAI;AAAA,QAAG;AAAA,MAC/C,MAAM,OAAO,OAAO,KAAK,IAAI;AAAA,MAC7B,IAAI,SAAS;AAAA,QAAW,OAAO,EAAE,QAAQ,SAAS;AAAA,MAClD,OAAO,KAAK,QAAQ,MAAM,IAAI,IACzB,EAAE,QAAQ,OAAO,IACjB,EAAE,QAAQ,SAAS;AAAA,KACzB;AAAA,IAEH,WAAW,CAAC,SACH,YAAI,UAAU,GAAG;AAAA,MACtB,MAAM,OAAO,OAAO,KAAK,IAAI;AAAA,MAC7B,OAAO,OAAO,YACZ,KAAK,cACL,KAAK,MACL,SAAS,YAAY,WAAW,QAClC;AAAA,KACD;AAAA,IAEH,SAAS,CAAC,SAAgB,YAAY,KAAK,cAAc,KAAK,MAAM,QAAQ;AAAA,EAC9E;AAAA;AAeK,IAAM,mBAAmB,CAM9B,SACG;AAAA,EACH,MAAM,MAAM,mBAAmB,IAAI;AAAA,EAMnC,MAAM,kBAAkB,CAAY,WAC3B,gBAAQ,QAAQ,kBAAkB;AAAA,EAC3C,OAAO;AAAA,IACL,MAAM,MAAa,gBAAQ,CAAC,CAAC;AAAA,IAC7B,MAAM,EAAE,MAAM,KAAK;AAAA,IACnB,MAAM,GAAG,MAAM,aACb,gBAAgB,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC;AAAA,IAC5C,MAAM,GAAG,MAAM,aACb,gBAAgB,IAAI,KAAK,MAAM,MAAM,CAAC;AAAA,IACxC,WAAW,GAAG,WAA4B,gBAAgB,IAAI,UAAU,IAAI,CAAC;AAAA,IAC7E,QAAQ,GAAG,WAA4B,gBAAgB,IAAI,QAAQ,IAAI,CAAC;AAAA,EAC1E;AAAA;;;AHjEK,IAAM,wBAAwB,SAAgC,2BAA2B;AAAA,EAE9F,sBAAsB;AACxB,CAAC;AAEM,IAAM,0BAA0B,CAAC,UACtC,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAS;AAGlC,IAAM,OAKT;AAAA,EACF,YAAY;AAAA,EACZ,WAAW,CAAC,UACJ,UAAI,CAAC,CAAC,EAAE,KAAY,YAAI,CAAC,QAAQ,IAAI,OAAO,SAAS,QAAQ,MAAM,KAAK,CAAC;AAAA,EACjF;AAAA,EACA,cAAc;AAChB;AAQO,IAAM,gBAAgB;AAEtB,IAAM,WAAW,iBAAiB,IAAI;AAEtC,IAAM,gCAAgC,MAClC,gBACP,uBACO,gBAAQ,sBAAsB,SAAS,GAAG,QAAQ,CAAC,CAC5D;AAIK,IAAM,gBAAgB,CAAC,IAAY,UACxC,sBAAsB,IAAI,KAAK,EAAE,KAAK,MAAM,IAAI,CAAC;;AMnHnD,kBAAS;AACT,qBAAS;AACT;AACA;AAEA;;;ACHO,IAAM,gBAAe,CAAC,MAAc,UAAqD;AAAA,EAC9F,MAAM,OAAO,KAAK,MAAM,KAAK;AAAA,EAC7B,OAAO,KAAK,SAAS;AAAA,EACrB,MAAM,KAAK;AAAA,EACX;AACF;AAEO,IAAM,WAAU,CAAC,YAAgC,UACtD,WAAW,SAAS,MAAM,QAC1B,WAAW,UAAU,MAAM,QAAQ,UACnC,WAAW,WAAW,MAAM,SAAS;;;ADwBhC,IAAM,2BAA2B,UACtC,8BACA;AAAA,EACE,sBAAsB;AACxB,CACF;AAEO,IAAM,6BAA6B,CAAC,UACzC,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAS;AAElC,IAAM,QAKT;AAAA,EACF,YAAY;AAAA,EACZ,WAAW,CAAC,UACD,aAAI,CAAC,CAAC,EAAE,KAAY,YAAI,CAAC,QAAQ,IAAI,UAAU,YAAY,WAAW,MAAM,KAAK,CAAC;AAAA,EAC7F;AAAA,EACA,cAAc;AAChB;AAGO,IAAM,iBAAgB;AAEtB,IAAM,YAAW,iBAAiB,KAAI;AAEtC,IAAM,mCAAmC,MACrC,iBACP,0BACO,gBAAQ,yBAAyB,SAAS,GAAG,SAAQ,CAAC,CAC/D;AAGK,IAAM,mBAAmB,CAAC,IAAY,UAC3C,yBAAyB,IAAI,KAAK,EAAE,KAAK,OAAM,IAAI,CAAC;;AExEtD,kBAAS;AACT,qBAAS;AACT;AACA;AAEA;;;ACEO,IAAM,gBAAe,CAC1B,MACA,UACqB;AAAA,EACrB,aAAa,KAAK,SAAS;AAAA,EAC3B,QAAQ,KAAK;AAAA,EACb,SAAS,WAAW,KAAK,OAAO;AAAA,EAChC,SAAS,OAAO,KAAK,SAAS,KAAK;AAAA,EACnC,UAAU,SAAS,KAAK,UAAU,CAAC;AAAA,EACnC;AACF;AAEO,IAAM,WAAU,CAAC,YAA6B,UACnD,WAAW,WAAW,MAAM,UAC5B,WAAW,aAAa,MAAM,WAAW,UACzC,WAAW,cAAc,MAAM,YAAY,MAC3C,WAAW,iBAAiB,MAAM,eAAe,OACjD,WAAW,QAAQ,KAAK,GAAG,MAAM,SAAS,MAAM,OAAO,EAAE,KAAK,GAAG;;;ADgB5D,IAAM,wBAAwB,UAAgC,2BAA2B;AAAA,EAC9F,sBAAsB;AACxB,CAAC;AAEM,IAAM,0BAA0B,CAAC,UACtC,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAS;AAElC,IAAM,QAKT;AAAA,EACF,YAAY;AAAA,EACZ,WAAW,CAAC,UACJ,UAAI,CAAC,CAAC,EAAE,KAAY,YAAI,CAAC,QAAQ,IAAI,OAAO,eAAe,MAAM,KAAK,CAAC;AAAA,EAC/E;AAAA,EACA,cAAc;AAChB;AAGO,IAAM,iBAAgB;AAEtB,IAAM,YAAW,iBAAiB,KAAI;AAEtC,IAAM,gCAAgC,MAClC,iBACP,uBACO,gBAAQ,sBAAsB,SAAS,GAAG,SAAQ,CAAC,CAC5D;AAGK,IAAM,gBAAgB,CAAC,IAAY,UACxC,sBAAsB,IAAI,KAAK,EAAE,KAAK,OAAM,IAAI,CAAC;;AE1EnD;AAKO,IAAM,YAAY,MACjB,eACJ,8BAA8B,GAC9B,iCAAiC,GACjC,8BAA8B,CAChC;",
|
|
18
|
+
"debugId": "681DFCB41A03047B64756E2164756E21",
|
|
19
19
|
"names": []
|
|
20
20
|
}
|
package/dist/opnsense/wire.d.ts
CHANGED
|
@@ -2,20 +2,27 @@
|
|
|
2
2
|
* How OPNsense's MVC model spells a value on the wire, and how to get it back to something
|
|
3
3
|
* comparable — the `../proxmox/values.ts` idea, scoped to this vendor's own spellings.
|
|
4
4
|
*
|
|
5
|
-
* ★ OPNSENSE HAS EXACTLY TWO WIRE SHAPES THIS FAMILY NEEDS
|
|
6
|
-
*
|
|
7
|
-
* string
|
|
8
|
-
* `ModelRelationField` is a SINGLE comma-joined string, not a JSON array
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* ★ OPNSENSE HAS EXACTLY TWO WIRE SHAPES THIS FAMILY NEEDS ON WRITE (props stay plain values;
|
|
6
|
+
* this family never writes, but `matches` compares against them). Every `BooleanField` is the
|
|
7
|
+
* string `"1"` or `"0"` (never a JSON boolean). Every multi-select `OptionField`/
|
|
8
|
+
* `ModelRelationField`'s WRITE-side spelling is a SINGLE comma-joined string, not a JSON array.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ READ IS A THIRD SHAPE (OPNSENSE-2, distilled `homeflare/opnsense`'s `read-shape.ts`): the
|
|
11
|
+
* SAME list-shaped fields decode on GET as an option map `{key: {value, selected}}`, not a
|
|
12
|
+
* string — `AliasItem`/`GroupItem` (this family's OLD, pre-fix type references) were the
|
|
13
|
+
* WRITE-shaped structs; `fetchLive` actually decodes `ModelAliasReadItem`/
|
|
14
|
+
* `ModelIfgroupentryReadItem` (the WHOLE-MODEL GET's read-shaped item), whose `type`/
|
|
15
|
+
* `interface`/`proto`/`categories`/`members` are option maps. `selectedOf`/`selectedOneOf`
|
|
16
|
+
* below extract the selected key(s) back to the SAME plain-string/string[] shape `matches`
|
|
17
|
+
* already compares, so the rest of this family's logic (and `AliasAttributes`/
|
|
18
|
+
* `GroupAttributes`'s own field types) is unchanged.
|
|
11
19
|
*
|
|
12
20
|
* ⚠️ COMMA-LIST ORDER ROUND-TRIP IS NOT ASSUMED STABLE. OPNsense builds these from a PHP
|
|
13
|
-
* associative array
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `groups` field.
|
|
21
|
+
* associative array, and nothing in the model XML promises the join order survives a
|
|
22
|
+
* save-then-read unchanged. Sorting and deduplicating both sides before comparing is what makes
|
|
23
|
+
* `matches` (alias-form.ts, group-form.ts) actually settle instead of reporting drift on a
|
|
24
|
+
* re-ordered list that changed nothing — the same reasoning `../proxmox/user-wire.ts`'s
|
|
25
|
+
* `groupSet` documents for PVE's `groups` field.
|
|
19
26
|
*/
|
|
20
27
|
/** OPNsense's own boolean spelling. Absent takes the caller's stated default — OPNsense's own
|
|
21
28
|
* default for a `BooleanField` is form-specific, so there is no house-wide fallback here. */
|
|
@@ -36,4 +43,27 @@ export declare const csvSetOf: (value: readonly string[] | undefined) => readonl
|
|
|
36
43
|
* same way `../proxmox/user-wire.ts`'s `int` is tolerant of PVE's mixed number/string releases.
|
|
37
44
|
*/
|
|
38
45
|
export declare const numField: (value: string | undefined, fallback: number) => number;
|
|
46
|
+
/** One entry of a GET-decoded option map — `{value: <label>, selected: 0|1}`, see this file's
|
|
47
|
+
* module doc. Structural, not imported from the SDK: every generated `*Map` field shares this
|
|
48
|
+
* exact shape (`OptionEntry` in each service module), and staying structural here avoids a
|
|
49
|
+
* per-resource-file type import just for this. */
|
|
50
|
+
export interface OpnsenseOptionEntry {
|
|
51
|
+
readonly value: string;
|
|
52
|
+
readonly selected: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The selected key(s) of a GET-decoded option map, normalised the same way `csvSet` normalises
|
|
56
|
+
* the write-side comma string — sorted, deduplicated, empty-key filtered. Use for a
|
|
57
|
+
* `Multiple="Y"` field (`AliasItem.proto`, `AliasItem.categories`, `GroupItem.members`); for a
|
|
58
|
+
* single-select field use `selectedOneOf`.
|
|
59
|
+
*/
|
|
60
|
+
export declare const selectedOf: (map: Readonly<Record<string, OpnsenseOptionEntry | undefined>> | undefined) => readonly string[];
|
|
61
|
+
/** Single-select convenience over `selectedOf` — the one selected key, or `''` if none (the same
|
|
62
|
+
* "absent" spelling a plain string field uses, e.g. `AliasItem.type`/`GroupItem`'s own
|
|
63
|
+
* `.\InterfaceField` members when not `Multiple="Y"`). */
|
|
64
|
+
export declare const selectedOneOf: (map: Readonly<Record<string, OpnsenseOptionEntry | undefined>> | undefined) => string;
|
|
65
|
+
/** The encode direction, for test fixtures: every listed key marked `selected: 1`, mirroring a
|
|
66
|
+
* real OPNsense GET response (`{[key]: {value, selected: 1}}`) — so a fixture exercises the exact
|
|
67
|
+
* decode path `selectedOf`/`selectedOneOf` handle, not the pre-fix plain-string shape. */
|
|
68
|
+
export declare const optionMap: (...keys: readonly string[]) => Record<string, OpnsenseOptionEntry>;
|
|
39
69
|
//# sourceMappingURL=wire.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../src/opnsense/wire.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../src/opnsense/wire.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;6FAC6F;AAC7F,eAAO,MAAM,MAAM,UAAW,MAAM,GAAG,SAAS,YAAY,OAAO,KAAG,OACtB,CAAC;AAEjD;4FAC4F;AAC5F,eAAO,MAAM,QAAQ,UAAW,OAAO,KAAG,MAA6B,CAAC;AAExE;;;GAGG;AACH,eAAO,MAAM,MAAM,UAAW,MAAM,GAAG,SAAS,KAAG,SAAS,MAAM,EAOjE,CAAC;AAEF;kGACkG;AAClG,eAAO,MAAM,QAAQ,UAAW,SAAS,MAAM,EAAE,GAAG,SAAS,KAAG,SAAS,MAAM,EACpB,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,QAAQ,UAAW,MAAM,GAAG,SAAS,YAAY,MAAM,KAAG,MAItE,CAAC;AAEF;;;kDAGkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,QAChB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,KACzE,SAAS,MAAM,EAMjB,CAAC;AAEF;;0DAE0D;AAC1D,eAAO,MAAM,aAAa,QACnB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,KACzE,MAAkC,CAAC;AAEtC;;0FAE0F;AAC1F,eAAO,MAAM,SAAS,YAAa,SAAS,MAAM,EAAE,KAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CACd,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acl.d.ts","sourceRoot":"","sources":["../../src/proxmox/acl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"acl.d.ts","sourceRoot":"","sources":["../../src/proxmox/acl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAUtE,4FAA4F;AAC5F,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAExD,MAAM,WAAW,QAAS,SAAQ,UAAU;IAC1C;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,IAAI,EAAE,cAAc,CAAC;IACrB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,gGAAgG;IAChG,MAAM,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,+FAA+F;IAC/F,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAW,SAAQ,QAAQ,CAC1C,aAAa,EACb,QAAQ,EACR,aAAa,EACb,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,UAAU,6CAAsC,CAAC;AAyB9D,eAAO,MAAM,kBAAkB,gIAkF5B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-token.d.ts","sourceRoot":"","sources":["../../src/proxmox/api-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"api-token.d.ts","sourceRoot":"","sources":["../../src/proxmox/api-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAM7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAStE,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;OAYG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;OAQG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,8FAA8F;IAC9F,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAKlB;AAED,MAAM,WAAW,eAAgB,SAAQ,QAAQ,CAC/C,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,eAAe,kDAE1B,CAAC;AAuBH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,qIAmFjC,CAAC"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type { PveRole } from './credentials.ts';
|
|
2
1
|
declare const PveCredentialDenied_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
3
2
|
readonly _tag: "PveCredentialDenied";
|
|
4
3
|
} & Readonly<A>;
|
|
5
4
|
/**
|
|
6
|
-
* OpenBao answered 403 to `GET /v1/<mount>/creds/<
|
|
7
|
-
* for `
|
|
5
|
+
* OpenBao answered 403 to `GET /v1/<mount>/creds/<tier>`: this identity's AppRole has no grant
|
|
6
|
+
* for `tier` on `mount` — measured 2026-09-24 as "the agent lane cannot mint `provision`" (the
|
|
8
7
|
* lane's policy grants `read` but not `provision`). A TYPED tag, not the plain `Error` every
|
|
9
8
|
* other mint failure still is (404 no such role/mount, 503 sealed, a network failure), because
|
|
10
9
|
* this ONE case means "refused, not evidence of anything about the object" — a family's
|
|
@@ -15,10 +14,25 @@ declare const PveCredentialDenied_base: new <A extends Record<string, any> = {}>
|
|
|
15
14
|
* BROKEN, not this identity being narrowly scoped — folding those into "unreadable, report
|
|
16
15
|
* noop" would hide a real outage behind a quiet, misleadingly reassuring plan. Only 403 is
|
|
17
16
|
* "this identity, this role, denied" and safe to read as "cannot see, not evidence of absence".
|
|
17
|
+
*
|
|
18
|
+
* ⛔ NO `role` FIELD (removed, K-T1 second red-team pass). `mint.ts` knows the semantic `PveRole`
|
|
19
|
+
* it was asked for, but `lease-cache.ts`'s synthetic `mintForKey` reconstruction (after M3
|
|
20
|
+
* dropped `role` from `LeaseKey`) cannot supply the ORIGINAL caller's role on a cache-path
|
|
21
|
+
* mint — only the tier. A `role` field would then read as `'read'` on every pve cache miss
|
|
22
|
+
* regardless of which role was actually denied, which is exactly the stale/misleading-field
|
|
23
|
+
* trap M6 existed to close, one layer down. `tier` alone is what the message reads and what
|
|
24
|
+
* `readOrUnreadable`'s callers act on; nothing needs the semantic role name.
|
|
18
25
|
*/
|
|
19
26
|
export declare class PveCredentialDenied extends PveCredentialDenied_base<{
|
|
20
27
|
readonly mount: string;
|
|
21
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The OpenBao mint TIER actually requested (`credentials.ts`'s `mintTier`). ⛔ THE MESSAGE
|
|
30
|
+
* READS THIS, NOT A SEMANTIC ROLE NAME: printing `'read'` for a denied `talos-provision`
|
|
31
|
+
* mint would spell it as `creds/read`, exactly the path that was never called — silently
|
|
32
|
+
* wrong at precisely the moment an operator is reading this string to decide whether a
|
|
33
|
+
* grant, a policy or a target config is wrong (K-T1's own PR discussion).
|
|
34
|
+
*/
|
|
35
|
+
readonly tier: string;
|
|
22
36
|
readonly detail: string;
|
|
23
37
|
}> {
|
|
24
38
|
get message(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-errors.d.ts","sourceRoot":"","sources":["../../src/proxmox/credential-errors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credential-errors.d.ts","sourceRoot":"","sources":["../../src/proxmox/credential-errors.ts"],"names":[],"mappings":";;;AAMA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,mBAAoB,SAAQ,yBAAwC;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAK7B;CACF"}
|
|
@@ -85,8 +85,41 @@ export type PveTarget = {
|
|
|
85
85
|
* and a wrong guess fails closed with a 401 rather than doing something odd.
|
|
86
86
|
*/
|
|
87
87
|
readonly scheme: 'pve';
|
|
88
|
+
/**
|
|
89
|
+
* Overrides which OpenBao mint TIER NAME (`<mount>/creds/<tier>`) a semantic role resolves to.
|
|
90
|
+
* Omitted or missing a role: unchanged, `role` is also the tier name, as every consumer before
|
|
91
|
+
* this field got. Present: `mintTier` below reads it first.
|
|
92
|
+
*
|
|
93
|
+
* ★ WHY A TARGET-LEVEL OVERRIDE, NOT A THIRD `PveRole`. A consumer whose OpenBao mount vends a
|
|
94
|
+
* single least-privilege tier for BOTH read and write (one tier that can only ever reach the
|
|
95
|
+
* scope that consumer needs, rather than the estate's general `read`/`provision` split) sets
|
|
96
|
+
* `roles: { read: 'my-tier', provision: 'my-tier' }` and every read/diff/reconcile/delete this
|
|
97
|
+
* package already issues resolves to that tier, with no new call site anywhere in the package.
|
|
98
|
+
* Widening `PveRole` itself to a per-consumer string would instead touch every literal
|
|
99
|
+
* `'read'`/`'provision'` call site across the package (`client.ts`, `lxc-read.ts`,
|
|
100
|
+
* `lxc-task.ts`, `network-apply-read.ts`, `network-apply-run.ts`, `qemu-read.ts`,
|
|
101
|
+
* `qemu-task.ts`, `storage-download-task.ts` — the eight this field's `mintTier` indirection
|
|
102
|
+
* above already covers) for something only the TARGET, never the CALL, needs to know.
|
|
103
|
+
*
|
|
104
|
+
* ⛔ SET BOTH ROLES OR NEITHER (I2, red team on K-T1). An unmentioned role is NOT scoped down —
|
|
105
|
+
* it still mints the estate-wide tier this file's own header describes (`hf-provision@pve`,
|
|
106
|
+
* 27 privileges including `Permissions.Modify`/`Sys.Modify` for `provision`). Setting only
|
|
107
|
+
* `roles: { read: 'my-tier' }` leaves every reconcile/delete on this target minting that wide
|
|
108
|
+
* credential while the plan output looks fully scoped down — nothing here warns. See
|
|
109
|
+
* `docs/credentials.md`'s override paragraph for the same warning at the consumer-facing level.
|
|
110
|
+
*/
|
|
111
|
+
readonly roles?: {
|
|
112
|
+
readonly read?: string;
|
|
113
|
+
readonly provision?: string;
|
|
114
|
+
};
|
|
88
115
|
};
|
|
89
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* The same client against a Proxmox Backup Server. See the ⛔ on `scheme` above.
|
|
118
|
+
* ⛔ NO `roles` FIELD. The estate has no PBS consumer with a scoped tier yet, and a PBS mount
|
|
119
|
+
* vending only `read`/`provision` is the same shape `mint.ts` always assumed — `mintTier` below
|
|
120
|
+
* never overrides a PBS target for exactly that reason. Add the field here, the same way, when
|
|
121
|
+
* a PBS consumer needs it.
|
|
122
|
+
*/
|
|
90
123
|
export type PbsTarget = {
|
|
91
124
|
readonly mount: string;
|
|
92
125
|
readonly api: string;
|
|
@@ -94,6 +127,7 @@ export type PbsTarget = {
|
|
|
94
127
|
};
|
|
95
128
|
/** Either product. What `pve()` and `pveOperations` accept; what a RESOURCE accepts is narrower. */
|
|
96
129
|
export type ApiTarget = PbsTarget | PveTarget;
|
|
130
|
+
export declare const mintTier: (target: ApiTarget, role: PveRole) => string;
|
|
97
131
|
/**
|
|
98
132
|
* What a mint returns.
|
|
99
133
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/proxmox/credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH;;;;;;;;;;;;GAYG;AACH,uFAAuF;AACvF,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/proxmox/credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH;;;;;;;;;;;;GAYG;AACH,uFAAuF;AACvF,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IACvB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;CACxB,CAAC;AAEF,oGAAoG;AACpG,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AA0B9C,eAAO,MAAM,QAAQ,WAAY,SAAS,QAAQ,OAAO,KAAG,MAY3D,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iEAAiE;IACjE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,iGAAiG;AACjG,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/proxmox/group.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAc7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/proxmox/group.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAc7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAKtE,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC5C;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAa,SAAQ,QAAQ,CAC5C,eAAe,EACf,UAAU,EACV,eAAe,EACf,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,oGAAoG;AACpG,eAAO,MAAM,YAAY,+CAEvB,CAAC;AAEH,eAAO,MAAM,oBAAoB,kIAiG9B,CAAC"}
|