@homeflare/alchemy 0.38.0 → 0.40.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/README.md +32 -8
- package/dist/ceph/auth-entity.d.ts +34 -0
- package/dist/ceph/auth-entity.d.ts.map +1 -0
- package/dist/ceph/ceph-argv-config.d.ts +10 -0
- package/dist/ceph/ceph-argv-config.d.ts.map +1 -0
- package/dist/ceph/ceph-argv.d.ts +19 -0
- package/dist/ceph/ceph-argv.d.ts.map +1 -0
- package/dist/ceph/ceph-auth-form.d.ts +54 -0
- package/dist/ceph/ceph-auth-form.d.ts.map +1 -0
- package/dist/ceph/ceph-auth-parse.d.ts +56 -0
- package/dist/ceph/ceph-auth-parse.d.ts.map +1 -0
- package/dist/ceph/ceph-auth-reconcile.d.ts +35 -0
- package/dist/ceph/ceph-auth-reconcile.d.ts.map +1 -0
- package/dist/ceph/ceph-config-options.d.ts +23 -0
- package/dist/ceph/ceph-config-options.d.ts.map +1 -0
- package/dist/ceph/ceph-key-leak-assert.d.ts +25 -0
- package/dist/ceph/ceph-key-leak-assert.d.ts.map +1 -0
- package/dist/ceph/ceph-transport.d.ts +37 -0
- package/dist/ceph/ceph-transport.d.ts.map +1 -0
- package/dist/ceph/fake-ceph-dial.d.ts +35 -0
- package/dist/ceph/fake-ceph-dial.d.ts.map +1 -0
- package/dist/ceph/index.d.ts +13 -0
- package/dist/ceph/index.d.ts.map +1 -0
- package/dist/ceph/index.js +1609 -0
- package/dist/ceph/index.js.map +38 -0
- package/dist/forgejo/index.js +4 -2
- package/dist/forgejo/index.js.map +3 -3
- package/dist/forgejo/team-member.d.ts +25 -0
- package/dist/forgejo/team-member.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pve-access.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-pools.d.ts +1 -1
- package/dist/proxmox/generated/constraints/pve-storage.d.ts +1 -1
- package/dist/proxmox/index.d.ts +14 -3
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +574 -133
- package/dist/proxmox/index.js.map +33 -23
- package/dist/proxmox/qemu-form.d.ts +27 -18
- package/dist/proxmox/qemu-form.d.ts.map +1 -1
- package/dist/proxmox/qemu-judge.d.ts +30 -0
- package/dist/proxmox/qemu-judge.d.ts.map +1 -0
- package/dist/proxmox/qemu-lifecycle.d.ts +16 -2
- package/dist/proxmox/qemu-lifecycle.d.ts.map +1 -1
- package/dist/proxmox/qemu-net.d.ts +9 -0
- package/dist/proxmox/qemu-net.d.ts.map +1 -0
- package/dist/proxmox/qemu-props.d.ts +112 -0
- package/dist/proxmox/qemu-props.d.ts.map +1 -0
- package/dist/proxmox/qemu-volume.d.ts +27 -0
- package/dist/proxmox/qemu-volume.d.ts.map +1 -0
- package/dist/proxmox/qemu.d.ts +14 -27
- package/dist/proxmox/qemu.d.ts.map +1 -1
- package/dist/proxmox/storage-download-errors.d.ts +5 -0
- package/dist/proxmox/storage-download-errors.d.ts.map +1 -0
- package/dist/proxmox/storage-download-identity.d.ts +27 -0
- package/dist/proxmox/storage-download-identity.d.ts.map +1 -0
- package/dist/proxmox/storage-download-lifecycle.d.ts +97 -0
- package/dist/proxmox/storage-download-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/storage-download-props.d.ts +36 -0
- package/dist/proxmox/storage-download-props.d.ts.map +1 -0
- package/dist/proxmox/storage-download-task.d.ts +17 -0
- package/dist/proxmox/storage-download-task.d.ts.map +1 -0
- package/dist/proxmox/storage-download.d.ts +44 -0
- package/dist/proxmox/storage-download.d.ts.map +1 -0
- package/dist/talos/credentials-write.d.ts +46 -0
- package/dist/talos/credentials-write.d.ts.map +1 -0
- package/dist/talos/credentials.d.ts +51 -9
- package/dist/talos/credentials.d.ts.map +1 -1
- package/dist/talos/fake-process.d.ts +56 -0
- package/dist/talos/fake-process.d.ts.map +1 -0
- package/dist/talos/index.d.ts +19 -4
- package/dist/talos/index.d.ts.map +1 -1
- package/dist/talos/index.js +357 -173
- package/dist/talos/index.js.map +16 -10
- package/dist/talos/kubeconfig-attrs.d.ts +4 -0
- package/dist/talos/kubeconfig-attrs.d.ts.map +1 -0
- package/dist/talos/kubeconfig.d.ts +41 -23
- package/dist/talos/kubeconfig.d.ts.map +1 -1
- package/dist/talos/machine-config-fixtures.d.ts +60 -0
- package/dist/talos/machine-config-fixtures.d.ts.map +1 -0
- package/dist/talos/machine-config-poll.d.ts +43 -0
- package/dist/talos/machine-config-poll.d.ts.map +1 -0
- package/dist/talos/machine-config-read.d.ts +49 -0
- package/dist/talos/machine-config-read.d.ts.map +1 -0
- package/dist/talos/resource.d.ts +18 -0
- package/dist/talos/resource.d.ts.map +1 -1
- package/dist/talos/talos-bootstrap-etcd.d.ts +38 -0
- package/dist/talos/talos-bootstrap-etcd.d.ts.map +1 -0
- package/dist/talos/talos-bootstrap.d.ts +87 -0
- package/dist/talos/talos-bootstrap.d.ts.map +1 -1
- package/dist/talos/talos-cluster-health.d.ts +43 -1
- package/dist/talos/talos-cluster-health.d.ts.map +1 -1
- package/dist/talos/talos-errors.d.ts +68 -0
- package/dist/talos/talos-errors.d.ts.map +1 -0
- package/dist/talos/talos-machine-config.d.ts +51 -15
- package/dist/talos/talos-machine-config.d.ts.map +1 -1
- package/dist/talos/talosctl.d.ts +7 -0
- package/dist/talos/talosctl.d.ts.map +1 -1
- package/dist/talos/values.d.ts +23 -3
- package/dist/talos/values.d.ts.map +1 -1
- package/dist/telemetry/fake-collector.d.ts +36 -0
- package/dist/telemetry/fake-collector.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +11 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +223 -0
- package/dist/telemetry/index.js.map +12 -0
- package/dist/telemetry/layer.d.ts +86 -0
- package/dist/telemetry/layer.d.ts.map +1 -0
- package/dist/telemetry/redact-serialization.d.ts +21 -0
- package/dist/telemetry/redact-serialization.d.ts.map +1 -0
- package/dist/telemetry/redact.d.ts +46 -0
- package/dist/telemetry/redact.d.ts.map +1 -0
- package/dist/unifi/acl-rule-drift.d.ts +22 -0
- package/dist/unifi/acl-rule-drift.d.ts.map +1 -0
- package/dist/unifi/acl-rule-form.d.ts +90 -0
- package/dist/unifi/acl-rule-form.d.ts.map +1 -0
- package/dist/unifi/acl-rule-ordering-form.d.ts +49 -0
- package/dist/unifi/acl-rule-ordering-form.d.ts.map +1 -0
- package/dist/unifi/acl-rule-ordering.d.ts +54 -0
- package/dist/unifi/acl-rule-ordering.d.ts.map +1 -0
- package/dist/unifi/acl-rule.d.ts +49 -0
- package/dist/unifi/acl-rule.d.ts.map +1 -0
- package/dist/unifi/dns-policy-drift.d.ts +21 -0
- package/dist/unifi/dns-policy-drift.d.ts.map +1 -0
- package/dist/unifi/dns-policy-form.d.ts +97 -0
- package/dist/unifi/dns-policy-form.d.ts.map +1 -0
- package/dist/unifi/dns-policy.d.ts +55 -0
- package/dist/unifi/dns-policy.d.ts.map +1 -0
- package/dist/unifi/drift.d.ts +27 -0
- package/dist/unifi/drift.d.ts.map +1 -0
- package/dist/unifi/fake-unifi.d.ts +5 -1
- package/dist/unifi/fake-unifi.d.ts.map +1 -1
- package/dist/unifi/firewall-policy-drift.d.ts +26 -0
- package/dist/unifi/firewall-policy-drift.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-form.d.ts +102 -0
- package/dist/unifi/firewall-policy-form.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-ordering-form.d.ts +66 -0
- package/dist/unifi/firewall-policy-ordering-form.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-ordering.d.ts +57 -0
- package/dist/unifi/firewall-policy-ordering.d.ts.map +1 -0
- package/dist/unifi/firewall-policy.d.ts +57 -0
- package/dist/unifi/firewall-policy.d.ts.map +1 -0
- package/dist/unifi/firewall-zone-form.d.ts +6 -0
- package/dist/unifi/firewall-zone-form.d.ts.map +1 -1
- package/dist/unifi/firewall-zone.d.ts +1 -1
- package/dist/unifi/firewall-zone.d.ts.map +1 -1
- package/dist/unifi/index.d.ts +11 -3
- package/dist/unifi/index.d.ts.map +1 -1
- package/dist/unifi/index.js +725 -60
- package/dist/unifi/index.js.map +31 -9
- package/dist/unifi/network-drift.d.ts +26 -0
- package/dist/unifi/network-drift.d.ts.map +1 -0
- package/dist/unifi/network-form.d.ts +40 -7
- package/dist/unifi/network-form.d.ts.map +1 -1
- package/dist/unifi/network.d.ts +1 -0
- package/dist/unifi/network.d.ts.map +1 -1
- package/dist/unifi/paginate.d.ts +24 -0
- package/dist/unifi/paginate.d.ts.map +1 -0
- package/dist/unifi/providers.d.ts +1 -1
- package/dist/unifi/providers.d.ts.map +1 -1
- package/dist/unifi/resource.d.ts +36 -1
- package/dist/unifi/resource.d.ts.map +1 -1
- package/dist/unifi/scan-source.d.ts +11 -0
- package/dist/unifi/scan-source.d.ts.map +1 -0
- package/dist/unifi/traffic-matching-list-drift.d.ts +22 -0
- package/dist/unifi/traffic-matching-list-drift.d.ts.map +1 -0
- package/dist/unifi/traffic-matching-list-form.d.ts +56 -0
- package/dist/unifi/traffic-matching-list-form.d.ts.map +1 -0
- package/dist/unifi/traffic-matching-list.d.ts +51 -0
- package/dist/unifi/traffic-matching-list.d.ts.map +1 -0
- package/dist/unifi/wifi-broadcast-drift.d.ts +22 -0
- package/dist/unifi/wifi-broadcast-drift.d.ts.map +1 -0
- package/dist/unifi/wifi-broadcast-form.d.ts +127 -0
- package/dist/unifi/wifi-broadcast-form.d.ts.map +1 -0
- package/dist/unifi/wifi-broadcast.d.ts +73 -0
- package/dist/unifi/wifi-broadcast.d.ts.map +1 -0
- package/docs/telemetry-spike.md +94 -0
- package/docs/telemetry.md +109 -0
- package/docs/unifi-acl-rule.md +122 -0
- package/docs/unifi-dns-policy.md +75 -0
- package/docs/unifi-firewall-policy.md +109 -0
- package/docs/unifi-traffic-matching-list.md +82 -0
- package/docs/unifi-wifi.md +120 -0
- package/docs/unifi.md +64 -3
- package/package.json +10 -2
- package/src/ceph/auth-entity.ts +74 -0
- package/src/ceph/ceph-argv-config.ts +78 -0
- package/src/ceph/ceph-argv.test.ts +226 -0
- package/src/ceph/ceph-argv.ts +189 -0
- package/src/ceph/ceph-auth-form.test.ts +103 -0
- package/src/ceph/ceph-auth-form.ts +107 -0
- package/src/ceph/ceph-auth-parse.test.ts +155 -0
- package/src/ceph/ceph-auth-parse.ts +121 -0
- package/src/ceph/ceph-auth-reconcile-secrecy.test.ts +82 -0
- package/src/ceph/ceph-auth-reconcile.test.ts +233 -0
- package/src/ceph/ceph-auth-reconcile.ts +119 -0
- package/src/ceph/ceph-config-options.test.ts +44 -0
- package/src/ceph/ceph-config-options.ts +36 -0
- package/src/ceph/ceph-key-leak-assert.ts +35 -0
- package/src/ceph/ceph-no-output-logging.test.ts +52 -0
- package/src/ceph/ceph-transport.test.ts +125 -0
- package/src/ceph/ceph-transport.ts +117 -0
- package/src/ceph/fake-ceph-dial.ts +55 -0
- package/src/ceph/index.ts +12 -0
- package/src/forgejo/team-member.test.ts +14 -0
- package/src/forgejo/team-member.ts +28 -1
- package/src/index.ts +1 -0
- package/src/proxmox/constraints-required.test.ts +1 -0
- package/src/proxmox/generated/constraints/index.ts +3 -1
- package/src/proxmox/generated/constraints/pve-access.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-backup.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-ha.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-replication.ts +1 -1
- package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-disks.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-network.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +1 -1
- package/src/proxmox/generated/constraints/pve-nodes-storage.ts +30 -0
- package/src/proxmox/generated/constraints/pve-pools.ts +1 -1
- package/src/proxmox/generated/constraints/pve-storage.ts +1 -1
- package/src/proxmox/index.test.ts +11 -6
- package/src/proxmox/index.ts +19 -3
- package/src/proxmox/qemu-form.ts +49 -13
- package/src/proxmox/qemu-identity.test.ts +30 -0
- package/src/proxmox/qemu-judge.test.ts +65 -0
- package/src/proxmox/qemu-judge.ts +84 -0
- package/src/proxmox/qemu-lifecycle.test.ts +78 -19
- package/src/proxmox/qemu-lifecycle.ts +131 -32
- package/src/proxmox/qemu-net.test.ts +48 -0
- package/src/proxmox/qemu-net.ts +78 -0
- package/src/proxmox/qemu-ownership.test.ts +44 -0
- package/src/proxmox/qemu-props.test.ts +87 -0
- package/src/proxmox/qemu-props.ts +157 -0
- package/src/proxmox/qemu-volume.test.ts +229 -0
- package/src/proxmox/qemu-volume.ts +178 -0
- package/src/proxmox/qemu.ts +15 -29
- package/src/proxmox/storage-download-errors.ts +7 -0
- package/src/proxmox/storage-download-identity.test.ts +54 -0
- package/src/proxmox/storage-download-identity.ts +50 -0
- package/src/proxmox/storage-download-lifecycle.ts +226 -0
- package/src/proxmox/storage-download-ownership.test.ts +50 -0
- package/src/proxmox/storage-download-props.ts +38 -0
- package/src/proxmox/storage-download-task.ts +72 -0
- package/src/proxmox/storage-download.test.ts +216 -0
- package/src/proxmox/storage-download.ts +58 -0
- package/src/talos/credentials-write.test.ts +81 -0
- package/src/talos/credentials-write.ts +95 -0
- package/src/talos/credentials.test.ts +157 -0
- package/src/talos/credentials.ts +160 -90
- package/src/talos/fake-process.ts +94 -0
- package/src/talos/index.ts +39 -4
- package/src/talos/kubeconfig-attrs.ts +55 -0
- package/src/talos/kubeconfig.test.ts +256 -0
- package/src/talos/kubeconfig.ts +170 -90
- package/src/talos/machine-config-fixtures.ts +86 -0
- package/src/talos/machine-config-poll.test.ts +57 -0
- package/src/talos/machine-config-poll.ts +82 -0
- package/src/talos/machine-config-read.ts +118 -0
- package/src/talos/resource.ts +19 -0
- package/src/talos/secrets-gate.test.ts +63 -0
- package/src/talos/talos-bootstrap-etcd.ts +71 -0
- package/src/talos/talos-bootstrap.test.ts +218 -0
- package/src/talos/talos-bootstrap.ts +151 -45
- package/src/talos/talos-cluster-health.test.ts +143 -0
- package/src/talos/talos-cluster-health.ts +91 -28
- package/src/talos/talos-errors.ts +85 -0
- package/src/talos/talos-machine-config.test.ts +189 -0
- package/src/talos/talos-machine-config.ts +102 -88
- package/src/talos/talosctl.ts +8 -0
- package/src/talos/values.test.ts +34 -6
- package/src/talos/values.ts +41 -7
- package/src/telemetry/fake-collector.ts +59 -0
- package/src/telemetry/http-client-sealing.test.ts +82 -0
- package/src/telemetry/index.ts +10 -0
- package/src/telemetry/layer.test.ts +204 -0
- package/src/telemetry/layer.ts +152 -0
- package/src/telemetry/redact-serialization.ts +178 -0
- package/src/telemetry/redact.test.ts +124 -0
- package/src/telemetry/redact.ts +147 -0
- package/src/unifi/acl-rule-drift.ts +57 -0
- package/src/unifi/acl-rule-form.ts +135 -0
- package/src/unifi/acl-rule-ordering-form.ts +79 -0
- package/src/unifi/acl-rule-ordering.test.ts +141 -0
- package/src/unifi/acl-rule-ordering.ts +70 -0
- package/src/unifi/acl-rule.test.ts +183 -0
- package/src/unifi/acl-rule.ts +61 -0
- package/src/unifi/discriminated-filter-decode.test.ts +134 -0
- package/src/unifi/dns-policy-drift.ts +48 -0
- package/src/unifi/dns-policy-form.ts +142 -0
- package/src/unifi/dns-policy.test.ts +154 -0
- package/src/unifi/dns-policy.ts +67 -0
- package/src/unifi/drift.test.ts +78 -0
- package/src/unifi/drift.ts +56 -0
- package/src/unifi/errors-and-secrets.test.ts +127 -0
- package/src/unifi/fake-unifi.ts +5 -1
- package/src/unifi/firewall-policy-drift.test.ts +151 -0
- package/src/unifi/firewall-policy-drift.ts +55 -0
- package/src/unifi/firewall-policy-filter-roundtrip.test.ts +143 -0
- package/src/unifi/firewall-policy-form.ts +146 -0
- package/src/unifi/firewall-policy-ordering-form.ts +111 -0
- package/src/unifi/firewall-policy-ordering.test.ts +185 -0
- package/src/unifi/firewall-policy-ordering.ts +87 -0
- package/src/unifi/firewall-policy.test.ts +183 -0
- package/src/unifi/firewall-policy.ts +73 -0
- package/src/unifi/firewall-zone-form.ts +31 -2
- package/src/unifi/firewall-zone.test.ts +21 -1
- package/src/unifi/firewall-zone.ts +1 -1
- package/src/unifi/get-only-guard.test.ts +165 -0
- package/src/unifi/index.ts +78 -1
- package/src/unifi/network-drift.test.ts +207 -0
- package/src/unifi/network-drift.ts +80 -0
- package/src/unifi/network-form.ts +11 -39
- package/src/unifi/network.test.ts +7 -82
- package/src/unifi/network.ts +6 -6
- package/src/unifi/paginate.test.ts +252 -0
- package/src/unifi/paginate.ts +141 -0
- package/src/unifi/providers.ts +19 -1
- package/src/unifi/resource.ts +73 -3
- package/src/unifi/scan-source.ts +64 -0
- package/src/unifi/traffic-matching-list-drift.ts +40 -0
- package/src/unifi/traffic-matching-list-form.ts +77 -0
- package/src/unifi/traffic-matching-list.test.ts +202 -0
- package/src/unifi/traffic-matching-list.ts +78 -0
- package/src/unifi/wifi-broadcast-details-call.test.ts +92 -0
- package/src/unifi/wifi-broadcast-drift.ts +69 -0
- package/src/unifi/wifi-broadcast-form.ts +211 -0
- package/src/unifi/wifi-broadcast-secrets.test.ts +145 -0
- package/src/unifi/wifi-broadcast.test.ts +155 -0
- package/src/unifi/wifi-broadcast.ts +90 -0
- package/src/unifi/write-op-reference.test.ts +190 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Unifi.WifiBroadcast` — one WiFi network (SSID) broadcast on a UniFi Network site, READ-ONLY
|
|
3
|
+
* (`policy.ts`).
|
|
4
|
+
*
|
|
5
|
+
* ⛔ T23, THE REASON THIS FAMILY NEVER CALLS `getWifiBroadcastDetails`. That single-object GET
|
|
6
|
+
* returns `WifiBroadcastDetails`, whose `securityConfiguration`
|
|
7
|
+
* (`WifiSecurityConfigurationDetailObject`) carries the WPA passphrase AND every PPSK entry's
|
|
8
|
+
* passphrase — the pinned spec has NO `writeOnly` flag on either (verified: 4 `passphrase`
|
|
9
|
+
* props, 0 `writeOnly`; A2 already redacts them to `Redacted.Redacted<string>` at the SDK layer,
|
|
10
|
+
* `errors-and-secrets.test.ts`). This family goes further and never fetches that shape AT ALL:
|
|
11
|
+
* the only call below is `getWifiBroadcastPage`, which answers `WifiBroadcastOverview` instead —
|
|
12
|
+
* whose own `securityConfiguration` (`WifiSecurityConfigurationOverview`) is `{type,
|
|
13
|
+
* presharedKeyNetworkIds}`. No passphrase field exists in that type, so `WifiBroadcastProps`/
|
|
14
|
+
* `Attributes` (`wifi-broadcast-form.ts`) cannot carry one even by a future mistake.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ NO GET-BY-ID CALL EXISTS FOR THE OVERVIEW SHAPE — unlike every other family in this
|
|
17
|
+
* directory (`getNetworkDetails`, `getFirewallZone`, `getDnsPolicy`, `getAclRule`),
|
|
18
|
+
* `wifi_broadcasts.ts` only has `getWifiBroadcastPage` (list) and `getWifiBroadcastDetails`
|
|
19
|
+
* (single, passphrase-bearing — forbidden above). `fetchLive` below walks every page with B0b's
|
|
20
|
+
* `pageAll` and finds the one row matching `wifiBroadcastId` — the first real resource-level
|
|
21
|
+
* consumer of that pager (previously exercised only by `paginate.test.ts` in isolation).
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ THE `NotFound` CATCH BELOW IS FOR THE WHOLE WALK, NOT JUST THE FIRST PAGE CALL (red team,
|
|
24
|
+
* MINOR-4, 2026-09-26 — corrected from an earlier claim that it only applied to the first call).
|
|
25
|
+
* `pageAll` makes its sequential page calls inside one Effect pipeline, and `catchTag` here wraps
|
|
26
|
+
* that entire pipeline; a 404 on ANY page of the walk — not only the first — folds to `undefined`
|
|
27
|
+
* and discards whatever rows the walk had already collected. In practice this means "the site
|
|
28
|
+
* vanished partway through listing its broadcasts" is indistinguishable from "the site was never
|
|
29
|
+
* there" and from "the target id just isn't among the collected rows" — none of these three cases
|
|
30
|
+
* has a separate vendor signal, and no other family's `fetchLive` distinguishes its own analogous
|
|
31
|
+
* cases either. Read-only reporting treats all three the same conservative way (absent, not an
|
|
32
|
+
* error); a write path would need to tell them apart.
|
|
33
|
+
*/
|
|
34
|
+
import { Resource } from 'alchemy';
|
|
35
|
+
import * as Provider from 'alchemy/Provider';
|
|
36
|
+
import * as wifiBroadcasts from '@distilled.cloud/unifi-network/wifi_broadcasts';
|
|
37
|
+
import * as Effect from 'effect/Effect';
|
|
38
|
+
import { type WifiBroadcastAttributes, type WifiBroadcastProps } from './wifi-broadcast-form.ts';
|
|
39
|
+
import { type UnifiPaginationInconsistent } from './paginate.ts';
|
|
40
|
+
import type { UnifiRequirements, UnifiSpec } from './resource.ts';
|
|
41
|
+
export type { WifiBroadcastAttributes, WifiBroadcastProps } from './wifi-broadcast-form.ts';
|
|
42
|
+
export { declareWifiBroadcast } from './wifi-broadcast-form.ts';
|
|
43
|
+
export { driftOf } from './wifi-broadcast-drift.ts';
|
|
44
|
+
export interface UnifiWifiBroadcast extends Resource<'Unifi.WifiBroadcast', WifiBroadcastProps, WifiBroadcastAttributes, never, UnifiRequirements> {
|
|
45
|
+
}
|
|
46
|
+
export declare const UnifiWifiBroadcast: import("alchemy").ResourceClass<UnifiWifiBroadcast>;
|
|
47
|
+
/** `wifiBroadcast('guest', props)` — `adopt(true)` piped on by default (H5); see `network.ts`. */
|
|
48
|
+
export declare const wifiBroadcast: (id: string, props: WifiBroadcastProps) => Effect.Effect<UnifiWifiBroadcast, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
49
|
+
/** ★ EXPORTED for direct testing against a fake `Credentials` layer — see `network.ts`'s own. */
|
|
50
|
+
export declare const spec: UnifiSpec<WifiBroadcastProps, wifiBroadcasts.WifiBroadcastOverview, WifiBroadcastAttributes, wifiBroadcasts.GetWifiBroadcastPageError | UnifiPaginationInconsistent>;
|
|
51
|
+
export declare const handlers: {
|
|
52
|
+
list: () => Effect.Effect<never[], never, never>;
|
|
53
|
+
read: (args: {
|
|
54
|
+
olds: WifiBroadcastProps;
|
|
55
|
+
output: WifiBroadcastAttributes | undefined;
|
|
56
|
+
}) => Effect.Effect<WifiBroadcastAttributes | undefined, UnifiPaginationInconsistent | wifiBroadcasts.UnifiNetworkOpError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
57
|
+
diff: (args: {
|
|
58
|
+
news: import("alchemy").Input<WifiBroadcastProps>;
|
|
59
|
+
output: WifiBroadcastAttributes | undefined;
|
|
60
|
+
}) => Effect.Effect<{
|
|
61
|
+
readonly action: 'update';
|
|
62
|
+
} | {
|
|
63
|
+
readonly action: 'noop';
|
|
64
|
+
} | undefined, UnifiPaginationInconsistent | wifiBroadcasts.UnifiNetworkOpError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
65
|
+
reconcile: (args: {
|
|
66
|
+
news: WifiBroadcastProps;
|
|
67
|
+
}) => Effect.Effect<WifiBroadcastAttributes, UnifiPaginationInconsistent | import("./policy.ts").UnifiWriteRefused | wifiBroadcasts.UnifiNetworkOpError, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
68
|
+
delete: (args: {
|
|
69
|
+
olds: WifiBroadcastProps;
|
|
70
|
+
}) => Effect.Effect<never, import("./policy.ts").UnifiWriteRefused, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
71
|
+
};
|
|
72
|
+
export declare const UnifiWifiBroadcastProvider: () => import("effect/Layer").Layer<Provider.Provider<UnifiWifiBroadcast>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
73
|
+
//# sourceMappingURL=wifi-broadcast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wifi-broadcast.d.ts","sourceRoot":"","sources":["../../src/unifi/wifi-broadcast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,cAAc,MAAM,gDAAgD,CAAC;AACjF,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EAExB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,KAAK,2BAA2B,EAAW,MAAM,eAAe,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAGlE,YAAY,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC5F,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAEpD,MAAM,WAAW,kBAAmB,SAAQ,QAAQ,CAClD,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,EACL,iBAAiB,CAClB;CAAG;AAEJ,eAAO,MAAM,kBAAkB,qDAE7B,CAAC;AAEH,kGAAkG;AAClG,eAAO,MAAM,aAAa,OAAQ,MAAM,SAAS,kBAAkB,mGAClB,CAAC;AAElD,iGAAiG;AACjG,eAAO,MAAM,IAAI,EAAE,SAAS,CAC1B,kBAAkB,EAClB,cAAc,CAAC,qBAAqB,EACpC,uBAAuB,EACvB,cAAc,CAAC,yBAAyB,GAAG,2BAA2B,CAWvE,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;CAAsB,CAAC;AAE5C,eAAO,MAAM,0BAA0B,wIACwD,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Spike: what a stack's `providers` Layer captures (2026-09-26)
|
|
2
|
+
|
|
3
|
+
Offline, no live Victoria call, per the workflow's own rule. A throwaway script (deleted after this
|
|
4
|
+
was recorded — not committed; `layer.test.ts` and `http-client-sealing.test.ts` are what's kept) ran
|
|
5
|
+
`verify/fake-engine.ts`'s `engineOver` — the REAL `Alchemy.Stack` + `Plan.make` + `apply` over an
|
|
6
|
+
in-memory state store, alchemy `2.0.0-beta.79` — with:
|
|
7
|
+
|
|
8
|
+
- a fake OTLP collector (`fake-collector.ts`'s `Bun.serve`, capturing every POST's path/headers/body)
|
|
9
|
+
- `telemetryLayer({ endpoints: { traces: '<collector>/v1/traces' }, serviceName: 'spike' })` merged
|
|
10
|
+
into the stack's `providers`
|
|
11
|
+
- one fake resource (`Spike.Thing`) whose `reconcile` wraps an `Effect.withSpan('spike.provider.
|
|
12
|
+
custom-span')` around an `HttpClient` `GET` to a SECOND fake server (`target`, standing in for
|
|
13
|
+
whatever real endpoint a provider calls) at `<target>/probe?token=SECRET123` with an
|
|
14
|
+
`authorization: Bearer sekrit` header
|
|
15
|
+
- a real `FetchHttpClient.layer` merged in as `ambient`, standing in for the platform layer a real
|
|
16
|
+
`alchemy.run.ts`'s `Stack.ts` provides underneath the merged `providers` (admin-transport-
|
|
17
|
+
scope.test.ts's own pattern) — `telemetryLayer`'s own `FetchHttpClient` is sealed, so without this
|
|
18
|
+
the fake provider's `HttpClient.HttpClient` requirement has nothing to resolve to
|
|
19
|
+
|
|
20
|
+
## Question 1: which spans arrive when the tracer is provided via `providers`?
|
|
21
|
+
|
|
22
|
+
**All of them.** One `deploy()` (a create) produced exactly one POST to `/v1/traces` containing:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
state_store.init, plan.diff.resource, plan.make,
|
|
26
|
+
http.client GET, spike.provider.custom-span,
|
|
27
|
+
provider.create, apply.resource, apply
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- **The provider's own span** (`spike.provider.custom-span`) arrived.
|
|
31
|
+
- **The `HttpClient` client span** (`http.client GET`, effect's own `HttpClient.make` instrumentation)
|
|
32
|
+
arrived — effect's tracing reads whatever `Tracer.Tracer` is ambient when the request runs, and
|
|
33
|
+
the stack's `providers` context is exactly that.
|
|
34
|
+
- **Alchemy's own engine spans arrived too**: `plan.make` (Plan.ts), `apply`/`apply.resource`
|
|
35
|
+
(Apply.ts's own `Effect.withSpan` calls), and `provider.create` — alchemy's OWN
|
|
36
|
+
`instrumentLifecycle` wrapper (Apply.ts) puts every provider lifecycle call (`read`/`reconcile`/
|
|
37
|
+
`delete`) in a `provider.${op}` span ALREADY, with `alchemy.resource.fqn`/`type`/`op` attributes —
|
|
38
|
+
a provider doesn't need to add its own span just to get lifecycle-level tracing.
|
|
39
|
+
- `state_store.init` and `plan.diff.resource` are alchemy internals not named in the facts this
|
|
40
|
+
workflow started from — recorded here since "measure which spans arrive" means all of them, not
|
|
41
|
+
only the three anticipated.
|
|
42
|
+
|
|
43
|
+
**Why**: `verify/fake-engine.ts`'s `engineOver` builds `compiled.services` from the `providers`
|
|
44
|
+
layer, then runs `Plan.make`/`apply` with `Effect.provide(Layer.succeedContext(compiled.services))`
|
|
45
|
+
around them (`fake-engine.ts`'s `run`). Since `Tracer.Tracer` is an `effect/Tracer` `Context.
|
|
46
|
+
Reference` (a value with a process-wide default that any `Effect.provide` can override for the
|
|
47
|
+
effects it wraps), and `telemetryLayer` sets that reference via `Layer.effect(Tracer.Tracer, …)`
|
|
48
|
+
(the same shape `OtlpTracer.layer` itself uses), every span created inside that `Effect.provide` —
|
|
49
|
+
alchemy's own internals included — reads the OTLP-backed, redacting tracer, not effect's default.
|
|
50
|
+
**This holds specifically because the tracer is installed through the stack's OWN `providers`
|
|
51
|
+
option**, the mechanism `alchemy.run.ts` already exposes to every consumer stack — not through
|
|
52
|
+
alchemy's separate CLI-wide `Telemetry/Layer.ts` install this workflow's decision explicitly does
|
|
53
|
+
not touch.
|
|
54
|
+
|
|
55
|
+
## Question 2: does redaction hold on a REAL span, not just the unit tests?
|
|
56
|
+
|
|
57
|
+
Yes — the captured `http.client GET` span's `url.full` was `http://127.0.0.1:<port>/probe`: no
|
|
58
|
+
`?token=SECRET123`. With a `redaction: { denylist: [<target's host>, 'secret-site-abc123'] }` policy
|
|
59
|
+
and a target URL of `<target>/secret-site-abc123/probe?token=SECRET`, the captured attributes were:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
server.address: "<redacted>"
|
|
63
|
+
url.full: "http://<redacted>/<redacted>/probe"
|
|
64
|
+
url.path: "/<redacted>/probe"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
with **no `http.request.header.*` key at all** — the `authorization: Bearer sekrit` header was
|
|
68
|
+
dropped outright, not value-redacted. One correction this made to the implementation: effect's
|
|
69
|
+
`HttpClient.make` sets `server.address` to `url.origin` (`http://host:port`), not a bare hostname —
|
|
70
|
+
`redactAttribute`'s `server.address` branch now parses it with `URL` before comparing to the
|
|
71
|
+
denylist, rather than comparing the raw scheme-prefixed string (which would never match a
|
|
72
|
+
consumer's hostname-only denylist entry).
|
|
73
|
+
|
|
74
|
+
## Question 3: does the sealed transport actually seal?
|
|
75
|
+
|
|
76
|
+
Confirmed twice, once by accident: the FIRST version of this spike's script had no `ambient`
|
|
77
|
+
`FetchHttpClient` merged in, on the assumption that a leaking `telemetryLayer` would supply one. It
|
|
78
|
+
crashed instead — `error: Service not found: effect/HttpClient` — which is direct, if accidental,
|
|
79
|
+
proof that `telemetryLayer`'s own `FetchHttpClient` never reaches a sibling provider.
|
|
80
|
+
`http-client-sealing.test.ts`'s second test makes that assertion explicit and permanent
|
|
81
|
+
(`.rejects.toThrow()` with no `ambient` layer present) rather than relying on stumbling into it
|
|
82
|
+
again; its first test is the positive case (a sibling gets the real `ambient` client, not
|
|
83
|
+
telemetry's).
|
|
84
|
+
|
|
85
|
+
## Question 4: does the shutdown flush actually deliver a short CLI run's spans?
|
|
86
|
+
|
|
87
|
+
Yes, with no extra wait beyond `engine.deploy()`'s own returned `Promise` resolving. `OtlpExporter.
|
|
88
|
+
make` (effect's shared batch exporter) registers a `Scope.addFinalizer` that forces one more export
|
|
89
|
+
and awaits it (bounded by `shutdownTimeout`, default 3s) — and `engineOver`'s `run` wraps the whole
|
|
90
|
+
plan+apply effect in `Effect.scoped`, so that finalizer runs, and is awaited, before `deploy()`'s
|
|
91
|
+
promise settles. The spike's default `exportInterval` ('1 second') never even had to fire for the
|
|
92
|
+
one-resource run above; the shutdown flush alone delivered the batch. Confirmed separately: with
|
|
93
|
+
`endpoints: {}`, the fake collector saw zero requests even after waiting 1.5s (longer than the
|
|
94
|
+
default interval) past `deploy()` returning — nothing fires later on some other timer.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# `@homeflare/alchemy/telemetry` — OTLP tracing/logging/metrics for a stack
|
|
2
|
+
|
|
3
|
+
`telemetryLayer` builds an OTLP/HTTP `Layer` for a stack's own `providers` — spans, logs and
|
|
4
|
+
metrics from `Effect.withSpan`, `HttpClient` calls and alchemy's own plan/apply engine, exported to
|
|
5
|
+
whatever OTLP collector a consumer names. Read 2026-09-26 against `alchemy@2.0.0-beta.79` and
|
|
6
|
+
`effect@4.0.0-rc.115`'s `effect/unstable/observability` module (this file names no estate host — see
|
|
7
|
+
below). See `telemetry-spike.md` for what was actually measured arriving at a collector.
|
|
8
|
+
|
|
9
|
+
Bodies are sent **protobuf-encoded** (`content-type: application/x-protobuf`), not JSON — measured
|
|
10
|
+
against the pinned binaries in homeflare-mini's own `src/obs`: VictoriaLogs 1.52.0 and
|
|
11
|
+
VictoriaMetrics 1.151.0 both answer OTLP/HTTP JSON with a `400`, and effect's exporter treats a 400
|
|
12
|
+
as non-transient and drops the batch silently at debug-only log level, so a JSON-encoded consumer
|
|
13
|
+
would see traces (VictoriaTraces alone tolerates JSON) but never logs or metrics, with no visible
|
|
14
|
+
error. There is no option to choose JSON instead.
|
|
15
|
+
|
|
16
|
+
## Off unless you give it endpoints
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { telemetryLayer } from '@homeflare/alchemy/telemetry';
|
|
20
|
+
|
|
21
|
+
export const providers = Layer.mergeAll(
|
|
22
|
+
myProviders(),
|
|
23
|
+
telemetryLayer({
|
|
24
|
+
endpoints: {
|
|
25
|
+
traces: 'http://victoria.example.test:10428/insert/opentelemetry/v1/traces',
|
|
26
|
+
logs: 'http://victoria.example.test:9428/insert/opentelemetry/v1/logs',
|
|
27
|
+
metrics: 'http://victoria.example.test:8428/opentelemetry/v1/metrics',
|
|
28
|
+
},
|
|
29
|
+
serviceName: 'my-stack',
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Every entry in `endpoints` is optional and `undefined` by default — an entry you leave out sends
|
|
35
|
+
nothing for that signal, and `telemetryLayer({ endpoints: {} , … })` sends nothing at all
|
|
36
|
+
(`layer.test.ts`'s own "nothing sent when unconfigured"). There is no default collector anywhere in
|
|
37
|
+
this module; a consumer's own site config supplies real hosts. `victoria.example.test` above is an
|
|
38
|
+
RFC 2606 reserved name — put your actual Victoria host there, e.g. from a `bao read` or an
|
|
39
|
+
environment variable your site file already has.
|
|
40
|
+
|
|
41
|
+
The three paths above are VictoriaTraces/Logs/Metrics's own OTLP ingest paths (their upstream docs,
|
|
42
|
+
version-specific — verify against the versions you run, not this file, per this repo's own house
|
|
43
|
+
rule that a documented path is not true because it is written down). They are examples, not
|
|
44
|
+
defaults: `telemetryLayer` never assumes a path shape, since `OtlpTracer`/`OtlpLogger`/`OtlpMetrics`
|
|
45
|
+
each post to the exact `url` given, with nothing appended.
|
|
46
|
+
|
|
47
|
+
## Options
|
|
48
|
+
|
|
49
|
+
| option | type | notes |
|
|
50
|
+
| -------------------- | ------------------------------ | ------------------------------------------------------------------ |
|
|
51
|
+
| `endpoints` | `{ traces?, logs?, metrics? }` | full URLs, each independent — see above. |
|
|
52
|
+
| `serviceName` | `string` | required — OTLP `service.name`. |
|
|
53
|
+
| `serviceVersion` | `string?` | OTLP `service.version`. |
|
|
54
|
+
| `resourceAttributes` | `Record<string, unknown>?` | extra OTLP resource attributes, e.g. `deployment.environment`. |
|
|
55
|
+
| `redaction` | `{ denylist?: string[] }?` | see below. |
|
|
56
|
+
| `exportInterval` | `Duration.Input?` | default `'1 second'` — short on purpose, see `telemetry-spike.md`. |
|
|
57
|
+
| `logsMinimumLevel` | `LogLevel.LogLevel?` | default `'Info'` — floor for what `logs` exports; see below. |
|
|
58
|
+
|
|
59
|
+
## Redaction — what leaves this process
|
|
60
|
+
|
|
61
|
+
Two passes run, at two different points, because no single hook sees every field (redteam PR 302,
|
|
62
|
+
2026-09-26 — `redact.ts`/`redact-serialization.ts` have the full detail):
|
|
63
|
+
|
|
64
|
+
1. **`redactAttribute`** (`redact.ts`) wraps the `Tracer.Tracer` itself, so it runs on every span
|
|
65
|
+
attribute AS IT IS SET — provider spans, `HttpClient` client spans, and alchemy's own engine
|
|
66
|
+
spans:
|
|
67
|
+
- **Every HTTP header is dropped**, request or response, never merely value-redacted — a header
|
|
68
|
+
NAME can itself be sensitive.
|
|
69
|
+
- **Every query string is dropped** — `url.query` outright, and `url.full`'s own `?…`/`#…` — no
|
|
70
|
+
per-parameter allowlist, since the next secret param a provider adds would otherwise leak.
|
|
71
|
+
- **A denylisted host or path segment is blanked to `<redacted>`**, exact-segment match (never a
|
|
72
|
+
substring), in `url.full`, `url.path` and `server.address` (bare hostname or full origin, both).
|
|
73
|
+
2. **`scrubText`/`redactTraceData`/`redactLogsData`** (`redact-serialization.ts`) run at
|
|
74
|
+
`OtlpSerialization`, right before the wire body is built — the one place that also sees fields
|
|
75
|
+
`redactAttribute` never does: `status.message` and the `exception.*` event attributes
|
|
76
|
+
`OtlpTracer` builds from a failed span's `Cause` at export time, and a log line inside a span
|
|
77
|
+
(turned into a span event by effect's default `tracerLogger` — the log message becomes the event
|
|
78
|
+
NAME). These are free text, not a structured URL, so this pass matches a denylist entry as a
|
|
79
|
+
**substring anywhere** in the string, and strips the query from any embedded URL. **Logs get the
|
|
80
|
+
same pass** (`redactLogsData`) — a log record's body and its `log.error` (`Cause.pretty`, full
|
|
81
|
+
formatted error text) attribute.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
telemetryLayer({
|
|
85
|
+
endpoints: { traces: 'http://victoria.example.test:10428/insert/opentelemetry/v1/traces' },
|
|
86
|
+
redaction: { denylist: ['a1b2c3d4e5f6'] }, // e.g. a UniFi console id in the request path
|
|
87
|
+
serviceName: 'my-stack',
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Everything else — span names once scrubbed, timing, alchemy's own `alchemy.resource.*`/
|
|
92
|
+
`alchemy.stack` attributes, non-URL attributes a provider sets itself — passes through untouched. A
|
|
93
|
+
provider that puts a secret in a NON-url, non-header, non-denylisted attribute (e.g.
|
|
94
|
+
`Effect.withSpan('x', { attributes: { 'my.token': secret } })`) is not caught by either pass; that is
|
|
95
|
+
a provider bug this module cannot see, not a gap in the redaction rules above.
|
|
96
|
+
|
|
97
|
+
`logsMinimumLevel` (default `'Info'`) is a separate concern from redaction: the `alchemy` CLI sets
|
|
98
|
+
the GLOBAL minimum log level to Debug, and without a floor of its own the OTLP logger would ship
|
|
99
|
+
every Debug-level record. It applies only to what this layer exports — a consumer's console/file
|
|
100
|
+
loggers are unaffected. Metrics carry numeric data points, not provider-supplied free text, and pass
|
|
101
|
+
through both passes untouched.
|
|
102
|
+
|
|
103
|
+
## What this is not
|
|
104
|
+
|
|
105
|
+
This is **not** alchemy's own CLI telemetry (`alchemy`'s `src/Telemetry/Layer.ts`, hard-coded to
|
|
106
|
+
`otel.alchemy.run`, opted out of estate-wide via `~/.alchemy/telemetry-disabled`). That stays
|
|
107
|
+
retired for this decision (2026-09-26): this module is a per-stack, explicit-endpoint layer
|
|
108
|
+
each consumer opts into, not a CLI-wide install, and it never calls alchemy's endpoint or reads its
|
|
109
|
+
opt-out file.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# UniFi ACL Rules — `Unifi.AclRule` / `Unifi.AclRuleOrdering`
|
|
2
|
+
|
|
3
|
+
⛔ **READ-ONLY, BY TIM'S RULE (2026-09-24).** Same posture as `docs/unifi.md`'s `Unifi.Network`/
|
|
4
|
+
`Unifi.FirewallZone`: `reconcile`/`delete` both fail with a typed `UnifiWriteRefused`, and the
|
|
5
|
+
`GetOnlyHttpClient` wire guard (`resource.ts`) refuses any non-`GET` request before it reaches the
|
|
6
|
+
transport. Neither family here calls `createAclRule`, `updateAclRule`, `deleteAclRule` or
|
|
7
|
+
`updateAclRuleOrdering` anywhere — `write-op-reference.test.ts` proves it statically.
|
|
8
|
+
|
|
9
|
+
## Two resources, because rule order is not a rule's own property (T5)
|
|
10
|
+
|
|
11
|
+
`Unifi.AclRule` is one user-defined ACL rule (`getAclRule`/`getAclRulePage`).
|
|
12
|
+
`Unifi.AclRuleOrdering` is the site's ONE ordered list of rule IDs (`getAclRuleOrdering`,
|
|
13
|
+
`GET /v1/sites/{siteId}/acl-rules/ordering`) — not one-per-rule and not one-per-zone-pair the way a
|
|
14
|
+
future `Unifi.FirewallPolicyOrdering` will be (`network.ts`'s header names that as a separate,
|
|
15
|
+
bigger PR), because ACL rule ordering is site-wide, not keyed by a source/destination zone pair.
|
|
16
|
+
|
|
17
|
+
⛔ **`orderedAclRuleIds` IS NEVER RUN THROUGH `sortedSet`.** Every other multi-value field in this
|
|
18
|
+
directory that gets normalized (`firewall-zone-form.ts`'s `networkIds`, this family's own
|
|
19
|
+
`enforcingDeviceFilter.deviceIds`/`protocolFilter` below) is a SET with no documented order.
|
|
20
|
+
`orderedAclRuleIds` is the opposite: its position IS the value — `ACLRule.index`'s own field
|
|
21
|
+
comment says "Lower index has higher priority," and the whole point of `updateAclRuleOrdering` is
|
|
22
|
+
to change that priority by reordering the list. `acl-rule-ordering-form.ts`'s `matches`/`driftOf`
|
|
23
|
+
compare it with plain `deepEqual`, which — per `drift.ts`'s own header — sorts object keys but
|
|
24
|
+
never array elements, so leaving this one field un-normalized is what makes the resource actually
|
|
25
|
+
represent order.
|
|
26
|
+
|
|
27
|
+
## `Unifi.AclRule`'s own fields
|
|
28
|
+
|
|
29
|
+
- **`sourceFilter`/`destinationFilter` stay opaque `unknown`**, on the SDK's own authority
|
|
30
|
+
(`access_control_acl_rules.ts`'s comment on both: "Shape varies by variant — widened by
|
|
31
|
+
scripts/convert.ts"). This is the same converter-flattening trap (T10) `network-form.ts` already
|
|
32
|
+
documents for `ipv4Configuration`; typing these per-discriminator (A3) is explicitly scoped OUT
|
|
33
|
+
of this PR. Compared with plain `deepEqual` — honest about not being decoded, not a guess at a
|
|
34
|
+
narrower shape. ⚠️ **Known gap until A3 (T15, red team, Minor finding 4)**: the vendor's own
|
|
35
|
+
filter variants (`IntegrationIpAclRuleNetworkEndpointFilterDto`'s `networkIds`,
|
|
36
|
+
`IntegrationIpAclRuleSubnetEndpointFilterDto`'s `ipAddressesOrSubnets`, both variants'
|
|
37
|
+
`portFilter`, `IntegrationMacAclRuleMacAddressEndpointFilterDto`'s `macAddresses`) are themselves
|
|
38
|
+
set-like arrays — `deepEqual` compares them order-sensitively, the same false-`update` risk
|
|
39
|
+
already fixed for `deviceIds`/`protocolFilter` below, just not fixable here without decoding the
|
|
40
|
+
filter first.
|
|
41
|
+
- **`enforcingDeviceFilter.deviceIds` and `protocolFilter` are SETS**, normalized with the same
|
|
42
|
+
`sortedSet` `firewall-zone-form.ts` uses for `networkIds`: neither field's own vendor description
|
|
43
|
+
("IDs of the Switch-capable devices used to enforce the ACL rule," "Protocols this ACL rule will
|
|
44
|
+
be applied to") indicates order, and leaving either un-normalized would let a console-side
|
|
45
|
+
reorder plan a spurious `update` this read-only family's `reconcile` then refuses forever.
|
|
46
|
+
- **`index` is attributes-only, never declared.** `CreateAclRuleRequest`'s own comment on the
|
|
47
|
+
field: "This property is deprecated and has no effect. Use the dedicated ACL rule reordering
|
|
48
|
+
endpoint." Reported so a reader can see a rule's current position, but never compared —
|
|
49
|
+
`Unifi.AclRuleOrdering` above is where that position is actually declared.
|
|
50
|
+
- **`metadata.origin` is attributes-only**, same split `firewall-zone-form.ts`'s `metadataOrigin`
|
|
51
|
+
uses — `UserDefinedOrDerivedEntityMetadata` is server-derived, no writable field accepts it.
|
|
52
|
+
|
|
53
|
+
## T11 gate: per-tag closure diff, 10.4.57 vs 10.6.97 (checked 2026-09-26)
|
|
54
|
+
|
|
55
|
+
Same method `spec-version-provenance.md` (distilled package) used for Networks/FirewallZones,
|
|
56
|
+
run here for `DNS Policies` (5 ops) and `Access Control (ACL Rules)` (7 ops) — 12 operations total,
|
|
57
|
+
covering both families in this PR plus `Unifi.DnsPolicy` (`docs/unifi-dns-policy.md`).
|
|
58
|
+
|
|
59
|
+
- **Pin**: `~/.cache/homeflare/schemas/unifi/network_v10.4.57_openapi.json`, re-verified against
|
|
60
|
+
its own `SHA256SUMS` today (`3773947b…c9df59d`).
|
|
61
|
+
- **Mirror**: `raw.githubusercontent.com/beezly/unifi-apis/main/unifi-network/10.6.97.json`,
|
|
62
|
+
diffing aid only, never a spec of record (Q5/M9) — `info.version: "10.6.97"`,
|
|
63
|
+
`sha256: 44fd0e7f81603bb1279889a528a443d8f1a0102436e4a29675d8451adf32bd78`, mirror commit
|
|
64
|
+
`f979dd64ec0ea6008c2a698c278fe49a10e102c8` (Andrew Beresford, 2026-08-20, "Add UniFi API specs:
|
|
65
|
+
Network 10.6.97", GitHub-verified signature).
|
|
66
|
+
- **Operation count**: both versions carry exactly the same 12 operations for these two tags — zero
|
|
67
|
+
added, zero removed, in either direction.
|
|
68
|
+
- **Full raw operation objects** (parameters, request body, responses, unresolved `$ref`s
|
|
69
|
+
included — not just named schemas, the same reason `unifi.md`'s own check goes past a
|
|
70
|
+
schema-name diff): byte-identical for all 12, both versions.
|
|
71
|
+
- **14 component schemas differ somewhere in the whole document** (of 379/380 total) — the SAME 14
|
|
72
|
+
`unifi.md`'s own Networks/FirewallZones check found: 9 switch-stack/LAG schemas (`Switching`
|
|
73
|
+
tag), 4 generic `filter`-query-syntax schemas (`FilterExpression` and its three siblings,
|
|
74
|
+
matching the new `Filtering` tag), and one mDNS enum addition (`SHELLY`, `UniFi Devices` tag).
|
|
75
|
+
Full per-schema breakdown: the distilled package's own `docs/spec-version-provenance.md`.
|
|
76
|
+
- **Reachability, corrected 2026-09-26 (red team, Important finding 1)**: the original pass
|
|
77
|
+
resolved only plain `$ref`s reachable from the 12 operations' full parameter/body/response trees
|
|
78
|
+
— 17 schema names. ⛔ **That walk is wrong for any discriminated schema**: both `DNS policy`
|
|
79
|
+
(`type`: `A_RECORD`…`TXT_RECORD`, `FORWARD_DOMAIN` — 7-way) and `ACL rule` (`type`: `IPV4`, `MAC`
|
|
80
|
+
— 2-way) are `discriminator`-typed, and a variant reachable only via `discriminator.mapping`
|
|
81
|
+
carries no `$ref` from its parent at all. Re-walked following `$ref` **and** `mapping` targets:
|
|
82
|
+
the closure is **46 schema names**, identical set in both versions. The 29 names the `$ref`-only
|
|
83
|
+
walk missed: all 7 DNS record variant DTOs (`IntegrationDnsARecordDto`,
|
|
84
|
+
`IntegrationDnsAaaaRecordDto`, `IntegrationDnsCnameRecordDto`, `IntegrationDnsForwardDomainPolicyDto`,
|
|
85
|
+
`IntegrationDnsMxRecordDto`, `IntegrationDnsSrvRecordDto`, `IntegrationDnsTxtRecordDto`) plus their
|
|
86
|
+
7 `*CreateUpdateDto` siblings; both ACL rule variant DTOs (`IntegrationIpAclRuleDto`,
|
|
87
|
+
`IntegrationMacAclRuleDto`) plus their 2 `*CreateUpdateDto` siblings; the IP/MAC endpoint-filter
|
|
88
|
+
DTOs (`IP ACL rule endpoint`, `MAC ACL rule endpoint`, `IntegrationIpAclRuleNetworkEndpointFilterDto`,
|
|
89
|
+
`IntegrationIpAclRulePortEndpointFilterDto`, `IntegrationIpAclRuleSubnetEndpointFilterDto`,
|
|
90
|
+
`IntegrationMacAclRuleMacAddressEndpointFilterDto`); `IntegrationAclRuleDevicesFilterDto`
|
|
91
|
+
(the device-filter's own `DEVICES` mapping target); and 2 more entity-metadata variants beyond the
|
|
92
|
+
five the original count already had. Re-diffed all 46 against the mirror with an order-independent
|
|
93
|
+
structural compare (not a byte/string compare — JSON key order differs between the pin and the
|
|
94
|
+
mirror file): **zero differ.** **Zero overlap** with the 14 schemas that changed elsewhere in the
|
|
95
|
+
document.
|
|
96
|
+
|
|
97
|
+
**Answer unchanged: neither `DnsPolicy` nor `AclRule`/`AclRuleOrdering` would decode differently
|
|
98
|
+
against 10.6.97** — the conclusion survives the correction; only the closure method and the schema
|
|
99
|
+
count were wrong. **This does not generalize past 10.6.97 or to any other tag — re-run this check,
|
|
100
|
+
following BOTH `$ref` and `discriminator.mapping`, before importing or declaring against `Clients`,
|
|
101
|
+
`WiFi Broadcasts`, `Traffic Matching Lists`, `Switching`, or `Firewall` (`FirewallPolicy`); a
|
|
102
|
+
`$ref`-only walk on `WiFi Broadcasts` finds 15 schemas and would miss
|
|
103
|
+
`IntegrationWifiMdnsProxyPredefinedServiceDto`, which DID change (10.6.97 added the `SHELLY` enum
|
|
104
|
+
member) and is reachable only through a `mapping`.**
|
|
105
|
+
|
|
106
|
+
## The declaration renderers
|
|
107
|
+
|
|
108
|
+
`declareAclRule(live, siteId)` and `declareAclRuleOrdering(live, siteId)` are pure functions, same
|
|
109
|
+
contract as `declareNetwork`/`declareFirewallZone` (`docs/unifi.md`): given one live read, they
|
|
110
|
+
return the `Props` a declaration needs so its plan is a no-op. Neither performs the read itself.
|
|
111
|
+
|
|
112
|
+
## Field-level drift
|
|
113
|
+
|
|
114
|
+
`acl-rule.ts`/`acl-rule-ordering.ts` each export a pure `driftOf(live, props)`
|
|
115
|
+
(`acl-rule-drift.ts`, `acl-rule-ordering-form.ts`), built on the same `drift.ts`'s `makeDriftOf`
|
|
116
|
+
framework as `Unifi.Network`/`Unifi.FirewallZone` — `matches` is exactly
|
|
117
|
+
`fieldDrift(...).length === 0`, never a second hand-written comparison (MEDIUM-4).
|
|
118
|
+
|
|
119
|
+
## Vendor version
|
|
120
|
+
|
|
121
|
+
UniFi Network Integration API **10.4.57**, same pin as `docs/unifi.md`, via
|
|
122
|
+
`@distilled.cloud/unifi-network` aliased onto `@homeflare/distilled-unifi-network@0.2.0`.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# UniFi DNS Policies — `Unifi.DnsPolicy`
|
|
2
|
+
|
|
3
|
+
⛔ **READ-ONLY, BY TIM'S RULE (2026-09-24).** Same posture as `docs/unifi.md`'s `Unifi.Network`/
|
|
4
|
+
`Unifi.FirewallZone`: `reconcile`/`delete` both fail with a typed `UnifiWriteRefused`, and the
|
|
5
|
+
`GetOnlyHttpClient` wire guard (`resource.ts`) refuses any non-`GET` request before it reaches the
|
|
6
|
+
transport. This family calls no SDK write op anywhere — `write-op-reference.test.ts` proves it
|
|
7
|
+
statically, and `dns-policy.test.ts`'s own write-path tests prove `reconcile`/`destroy` never send
|
|
8
|
+
anything but the one `GET` this spec's `fetchLive` makes.
|
|
9
|
+
|
|
10
|
+
## One flat DTO for every DNS record type
|
|
11
|
+
|
|
12
|
+
⚠️ **Corrected 2026-09-26 (red team, Important finding 1): the vendor spec DOES discriminate.**
|
|
13
|
+
The pinned spec's `DNS policy` schema carries a 7-way `discriminator.mapping` on `type`
|
|
14
|
+
(`A_RECORD`, `AAAA_RECORD`, `CNAME_RECORD`, `FORWARD_DOMAIN`, `MX_RECORD`, `SRV_RECORD`,
|
|
15
|
+
`TXT_RECORD`), each mapping to its own variant DTO (`IntegrationDnsARecordDto`, …) that carries the
|
|
16
|
+
record-specific fields (`ipv4Address`, `ttlSeconds`, `text`, …) — the base `DNS policy` schema
|
|
17
|
+
itself has only `domain`/`enabled`/`id`/`metadata`/`type`. None of those variant DTOs are reachable
|
|
18
|
+
from the base schema by a plain `$ref` walk; they exist only behind the `mapping` (T11 gate below).
|
|
19
|
+
|
|
20
|
+
What IS still true: `scripts/convert.ts` FLATTENS all 7 variants into one struct — every variant
|
|
21
|
+
field becomes optional on the same `DNSPolicy`/`CreateDnsPolicyRequest`/`UpdateDnsPolicyRequest`
|
|
22
|
+
type, rather than widening any of them to `unknown` (T10). No two variants collide on a field name,
|
|
23
|
+
so the flattening loses no information. **That means `dns-policy-form.ts` still has no opaque-JSON
|
|
24
|
+
field to flag or defer, unlike `acl-rule-form.ts`'s `sourceFilter`/`destinationFilter`
|
|
25
|
+
(`docs/unifi-acl-rule.md`)** — that conclusion holds; only the "never discriminates" reason given
|
|
26
|
+
for it originally was wrong.
|
|
27
|
+
|
|
28
|
+
⛔ **No array field exists anywhere in this shape**, so none of `network-form.ts`'s/
|
|
29
|
+
`firewall-zone-form.ts`'s/`acl-rule-form.ts`'s `sortedSet` set-normalizing applies here —
|
|
30
|
+
`drift.ts`'s default `stripNullish` tolerance for UniFi's `null`-for-unset-optional fields is the
|
|
31
|
+
whole story for `dns-policy-drift.ts`.
|
|
32
|
+
|
|
33
|
+
## `id`/`metadata` are attributes-only, same split as every other family here
|
|
34
|
+
|
|
35
|
+
`DnsPolicyProps` mirrors `UpdateDnsPolicyRequest`, not `DNSPolicy`: `id` and `metadata.origin` are
|
|
36
|
+
server-derived (no writable field accepts either), reported in `DnsPolicyAttributes` but never
|
|
37
|
+
compared or declared — the same split `network-form.ts` and `firewall-zone-form.ts` document for
|
|
38
|
+
their own server-derived fields.
|
|
39
|
+
|
|
40
|
+
## T11 gate: per-tag closure diff, 10.4.57 vs 10.6.97 (checked 2026-09-26, corrected same day)
|
|
41
|
+
|
|
42
|
+
Run together with `Unifi.AclRule`'s own gate, since both tags were diffed in the same pass —
|
|
43
|
+
**full breakdown, pin/mirror checksums and mirror commit are in
|
|
44
|
+
[`docs/unifi-acl-rule.md`](./unifi-acl-rule.md), not repeated here.** Summary for this family: all
|
|
45
|
+
5 `DNS Policies` operations are byte-identical between 10.4.57 and 10.6.97 (parameters, request
|
|
46
|
+
body, responses, unresolved `$ref`s included). ⚠️ The schema closure is **46 schemas, not 17** — a
|
|
47
|
+
plain `$ref` walk misses everything reachable only through `discriminator.mapping` (see "One flat
|
|
48
|
+
DTO" above), which for this tag is all 7 DNS record variant DTOs plus their 7 create/update
|
|
49
|
+
siblings. Re-run with `$ref` + `mapping` both followed, the full 46-schema closure (both tags
|
|
50
|
+
together) shares zero names with the 14 schemas that changed anywhere in the document between the
|
|
51
|
+
two versions — all 14 are `Switching`-, `Filtering`- or one `UniFi Devices` enum-shaped, none of it
|
|
52
|
+
DNS- or ACL-shaped. **Answer unchanged: `Unifi.DnsPolicy` would not decode differently against a
|
|
53
|
+
10.6.97 console** — the conclusion survives the correction; only the closure method and count were
|
|
54
|
+
wrong. **Method note for the next family (B4+): always follow `discriminator.mapping`, not just
|
|
55
|
+
`$ref` — a `$ref`-only walk silently under-counts any tag with a discriminated schema.**
|
|
56
|
+
|
|
57
|
+
## The declaration renderer
|
|
58
|
+
|
|
59
|
+
`declareDnsPolicy(live, siteId)` is a pure function, same contract as `declareNetwork`/
|
|
60
|
+
`declareFirewallZone` (`docs/unifi.md`): given one `getDnsPolicy` response, it returns the `Props`
|
|
61
|
+
a declaration needs so its plan is a no-op. A later import script reads every DNS policy on a site
|
|
62
|
+
and writes one `dnsPolicy('<id>', declareDnsPolicy(live, siteId))` row per result — nothing here
|
|
63
|
+
performs that write.
|
|
64
|
+
|
|
65
|
+
## Field-level drift
|
|
66
|
+
|
|
67
|
+
`dns-policy.ts` exports a pure `driftOf(live, props)` (`dns-policy-drift.ts`), built on `drift.ts`'s
|
|
68
|
+
`makeDriftOf` framework — the same one `Unifi.Network`/`Unifi.FirewallZone`/`Unifi.AclRule` use.
|
|
69
|
+
`matches` is exactly `fieldDrift(...).length === 0`, never a second hand-written comparison
|
|
70
|
+
(MEDIUM-4, `network-drift.ts`'s own header).
|
|
71
|
+
|
|
72
|
+
## Vendor version
|
|
73
|
+
|
|
74
|
+
UniFi Network Integration API **10.4.57**, same pin as `docs/unifi.md`, via
|
|
75
|
+
`@distilled.cloud/unifi-network` aliased onto `@homeflare/distilled-unifi-network@0.2.0`.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# UniFi Firewall Policies — `Unifi.FirewallPolicy` / `Unifi.FirewallPolicyOrdering`
|
|
2
|
+
|
|
3
|
+
⛔ **READ-ONLY, BY TIM'S RULE (2026-09-24).** Same posture as `docs/unifi.md`'s `Unifi.Network`/
|
|
4
|
+
`Unifi.FirewallZone`: `reconcile`/`delete` both fail with a typed `UnifiWriteRefused`, and the
|
|
5
|
+
`GetOnlyHttpClient` wire guard (`resource.ts`) refuses any non-`GET` request before it reaches the
|
|
6
|
+
transport. Neither family here calls `createFirewallPolicy`, `updateFirewallPolicy`,
|
|
7
|
+
`patchFirewallPolicy`, `deleteFirewallPolicy` or `updateFirewallPolicyOrdering` anywhere —
|
|
8
|
+
`write-op-reference.test.ts` proves it statically.
|
|
9
|
+
|
|
10
|
+
## Two resources, because policy order is not a policy's own property (T5)
|
|
11
|
+
|
|
12
|
+
`Unifi.FirewallPolicy` is one policy (`getFirewallPolicy`/`getFirewallPolicies`).
|
|
13
|
+
`Unifi.FirewallPolicyOrdering` is the ordered `before`/`afterSystemDefined` policy-id lists for ONE
|
|
14
|
+
`(sourceFirewallZoneId, destinationFirewallZoneId)` zone pair (`getFirewallPolicyOrdering`,
|
|
15
|
+
`GET /v1/sites/{siteId}/firewall/policies/ordering?sourceFirewallZoneId=…&destinationFirewallZoneId=…`)
|
|
16
|
+
— unlike `Unifi.AclRuleOrdering` (`docs/unifi-acl-rule.md`), which is site-wide, because firewall
|
|
17
|
+
policies are scoped to a zone pair and the vendor keys ordering the same way. There is no "list
|
|
18
|
+
every zone pair" endpoint; a caller has to already know which pairs exist, typically by walking
|
|
19
|
+
`getFirewallPolicies` (T9 — see below) and grouping by each policy's `source.zoneId`/
|
|
20
|
+
`destination.zoneId`. Neither resource performs that discovery.
|
|
21
|
+
|
|
22
|
+
⛔ **`beforeSystemDefined`/`afterSystemDefined` ARE NEVER RUN THROUGH `sortedSet`.** Both are
|
|
23
|
+
sequences — within each half, position is priority — the same "order IS the value" reasoning
|
|
24
|
+
`docs/unifi-acl-rule.md` gives for `orderedAclRuleIds`. `firewall-policy-ordering-form.ts` compares
|
|
25
|
+
each half independently with plain `deepEqual`, so a policy moving from one half to the other
|
|
26
|
+
reports as drift on BOTH fields rather than one combined reorder.
|
|
27
|
+
|
|
28
|
+
## `Unifi.FirewallPolicy`'s own fields
|
|
29
|
+
|
|
30
|
+
- **`action`/`destination`/`ipProtocolScope`/`schedule`/`source` are compound, compared wholesale.**
|
|
31
|
+
Post-A3 (`8ee11f1`), these decode as real per-discriminator TS unions instead of `unknown`
|
|
32
|
+
(`discriminated-filter-decode.test.ts` proves the wire path keeps every key, including one this
|
|
33
|
+
pinned spec doesn't yet declare) — but this PR does not go on to build a leaf-by-leaf declarable
|
|
34
|
+
schema for all 12 filter variants underneath them. Each is compared as ONE value with plain
|
|
35
|
+
`deepEqual` (`stripNullish`), the same proportion `acl-rule-form.ts`'s `enforcingDeviceFilter`
|
|
36
|
+
gets (one level of structure, not exploded into scalars).
|
|
37
|
+
⚠️ **Known gap (T15, same shape as `acl-rule-form.ts`'s own)**: several of the vendor's filter
|
|
38
|
+
variants nested inside these fields are themselves set-like arrays with no documented order
|
|
39
|
+
(`FirewallPolicyMACAddressFilter.macAddresses`, `FirewallPolicyNetworkFilter.networkIds`,
|
|
40
|
+
`FirewallPolicyApplicationFilter.applicationIds`, `FirewallPolicyIPAddressFilter.items`,
|
|
41
|
+
`FirewallPolicyRegionFilter.regions`, `FirewallPolicyVPNServerFilter.vpnServerIds`,
|
|
42
|
+
`FirewallSchedule.repeatOnDays`) — `deepEqual` compares them order-sensitively, so a console-side
|
|
43
|
+
reorder with no membership change would report a spurious drift this read-only family's
|
|
44
|
+
`reconcile` then refuses forever. Accepted, not fixed: normalizing 12 filter variants' own nested
|
|
45
|
+
arrays is bigger, separate modelling work, not a mechanical extension of A3.
|
|
46
|
+
- **`connectionStateFilter` is the one top-level Props array, and is a SET** — "matches all
|
|
47
|
+
connection states" if null describes membership, not a sequence (unlike the ordering resource's
|
|
48
|
+
own lists, where position IS the value). Normalized with the same `sortedSet` every other family
|
|
49
|
+
here uses for a set-shaped array.
|
|
50
|
+
- **`index` is attributes-only, never declared.** `CreateFirewallPolicyRequest` has no `index`
|
|
51
|
+
field — the live object's position among ALL policies on the site is reported, but its priority
|
|
52
|
+
relative to its own zone pair is `Unifi.FirewallPolicyOrdering`'s concern, same split
|
|
53
|
+
`acl-rule-form.ts`'s own `index` documents.
|
|
54
|
+
- **`metadata.origin` is attributes-only**, same split every family here uses.
|
|
55
|
+
|
|
56
|
+
## T11 gate: per-tag closure diff, 10.4.57 vs 10.6.97 (checked 2026-09-26)
|
|
57
|
+
|
|
58
|
+
Same method `docs/unifi-acl-rule.md` used, run here for the `Firewall` tag's 13 operations (5
|
|
59
|
+
already covered for `Unifi.FirewallZone` in `docs/unifi.md`; 8 new here: `getFirewallPolicies`,
|
|
60
|
+
`createFirewallPolicy`, `getFirewallPolicyOrdering`, `updateFirewallPolicyOrdering`,
|
|
61
|
+
`deleteFirewallPolicy`, `getFirewallPolicy`, `patchFirewallPolicy`, `updateFirewallPolicy`).
|
|
62
|
+
|
|
63
|
+
- **Pin**: `~/.cache/homeflare/schemas/unifi/network_v10.4.57_openapi.json`, re-verified against
|
|
64
|
+
its own `SHA256SUMS` today (`3773947b…c9df59d`).
|
|
65
|
+
- **Mirror**: `raw.githubusercontent.com/beezly/unifi-apis/main/unifi-network/10.6.97.json`,
|
|
66
|
+
diffing aid only, never a spec of record — re-fetched today, still `info.version: "10.6.97"`,
|
|
67
|
+
`sha256: 44fd0e7f81603bb1279889a528a443d8f1a0102436e4a29675d8451adf32bd78`, unchanged since mirror
|
|
68
|
+
commit `f979dd64ec0ea6008c2a698c278fe49a10e102c8` (Andrew Beresford, 2026-08-20, "Add UniFi API
|
|
69
|
+
specs: Network 10.6.97", GitHub-verified signature) — the same file `docs/unifi-acl-rule.md` cites.
|
|
70
|
+
- **Operation count**: both versions carry exactly the same 13 operations for `Firewall` — zero
|
|
71
|
+
added, zero removed, either direction.
|
|
72
|
+
- **Full raw operation objects** (parameters, request body, responses, unresolved `$ref`s included):
|
|
73
|
+
byte-identical for all 13, both versions.
|
|
74
|
+
- **Reachability** (`$ref` AND `discriminator.mapping`, per `docs/unifi-acl-rule.md`'s corrected
|
|
75
|
+
method): the closure is **106 schema names**, identical set in both versions. Every one of the 12
|
|
76
|
+
filter variants' own sub-schemas (`IntegrationFirewallPolicySourceMacAddressFilterDto`, all three
|
|
77
|
+
IP-protocol-scope Dto families, both action Dtos, etc.) is in it. **Zero overlap** with the 14
|
|
78
|
+
schemas that changed elsewhere in the document (the same 9 `Switching`, 4 `Filtering`, 1 mDNS
|
|
79
|
+
schemas `docs/unifi.md`/`docs/unifi-acl-rule.md` already found) — re-diffed all 106 with an
|
|
80
|
+
order-independent structural compare: **zero differ**.
|
|
81
|
+
- **Corroboration**: the A3 changeset's own broader re-check
|
|
82
|
+
(`.changeset/unifi-network-a3-typed-discriminator-filters.md`) independently walked all 25
|
|
83
|
+
FirewallPolicy/ACL rule/TrafficMatchingList operations together — a 139-schema closure, also
|
|
84
|
+
byte-identical between versions — against a mirror fetch the same day citing commit
|
|
85
|
+
`2457b337173c83599c583993210218b1b6c0f8e7` (that commit only touches `README.md`; the spec file
|
|
86
|
+
itself has been untouched, hence unchanged, since `f979dd64` above).
|
|
87
|
+
|
|
88
|
+
**Answer: neither `FirewallPolicy` nor `FirewallPolicyOrdering` would decode differently against
|
|
89
|
+
10.6.97.** This does not generalize past 10.6.97 or to any other tag — re-run this check, following
|
|
90
|
+
BOTH `$ref` and `discriminator.mapping`, before importing or declaring against `Clients`,
|
|
91
|
+
`Switching`, or any tag not yet covered by an existing `docs/unifi-*.md`.
|
|
92
|
+
|
|
93
|
+
## The declaration renderers
|
|
94
|
+
|
|
95
|
+
`declareFirewallPolicy(live, siteId)` and `declareFirewallPolicyOrdering(live, siteId, sourceZoneId,
|
|
96
|
+
destZoneId)` are pure functions, same contract as every other family's own (`docs/unifi.md`): given
|
|
97
|
+
one live read, they return the `Props` a declaration needs so its plan is a no-op.
|
|
98
|
+
|
|
99
|
+
## Field-level drift
|
|
100
|
+
|
|
101
|
+
`firewall-policy.ts`/`firewall-policy-ordering.ts` each export a pure `driftOf(live, props)`
|
|
102
|
+
(`firewall-policy-drift.ts`, `firewall-policy-ordering-form.ts`), built on the same `drift.ts`'s
|
|
103
|
+
`makeDriftOf` framework as every other family — `matches` is exactly `fieldDrift(...).length === 0`,
|
|
104
|
+
never a second hand-written comparison (MEDIUM-4).
|
|
105
|
+
|
|
106
|
+
## Vendor version
|
|
107
|
+
|
|
108
|
+
UniFi Network Integration API **10.4.57**, same pin as `docs/unifi.md`, via
|
|
109
|
+
`@distilled.cloud/unifi-network` aliased onto `@homeflare/distilled-unifi-network@0.3.0`.
|