@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.
Files changed (353) hide show
  1. package/README.md +32 -8
  2. package/dist/ceph/auth-entity.d.ts +34 -0
  3. package/dist/ceph/auth-entity.d.ts.map +1 -0
  4. package/dist/ceph/ceph-argv-config.d.ts +10 -0
  5. package/dist/ceph/ceph-argv-config.d.ts.map +1 -0
  6. package/dist/ceph/ceph-argv.d.ts +19 -0
  7. package/dist/ceph/ceph-argv.d.ts.map +1 -0
  8. package/dist/ceph/ceph-auth-form.d.ts +54 -0
  9. package/dist/ceph/ceph-auth-form.d.ts.map +1 -0
  10. package/dist/ceph/ceph-auth-parse.d.ts +56 -0
  11. package/dist/ceph/ceph-auth-parse.d.ts.map +1 -0
  12. package/dist/ceph/ceph-auth-reconcile.d.ts +35 -0
  13. package/dist/ceph/ceph-auth-reconcile.d.ts.map +1 -0
  14. package/dist/ceph/ceph-config-options.d.ts +23 -0
  15. package/dist/ceph/ceph-config-options.d.ts.map +1 -0
  16. package/dist/ceph/ceph-key-leak-assert.d.ts +25 -0
  17. package/dist/ceph/ceph-key-leak-assert.d.ts.map +1 -0
  18. package/dist/ceph/ceph-transport.d.ts +37 -0
  19. package/dist/ceph/ceph-transport.d.ts.map +1 -0
  20. package/dist/ceph/fake-ceph-dial.d.ts +35 -0
  21. package/dist/ceph/fake-ceph-dial.d.ts.map +1 -0
  22. package/dist/ceph/index.d.ts +13 -0
  23. package/dist/ceph/index.d.ts.map +1 -0
  24. package/dist/ceph/index.js +1609 -0
  25. package/dist/ceph/index.js.map +38 -0
  26. package/dist/forgejo/index.js +4 -2
  27. package/dist/forgejo/index.js.map +3 -3
  28. package/dist/forgejo/team-member.d.ts +25 -0
  29. package/dist/forgejo/team-member.d.ts.map +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/proxmox/generated/constraints/index.d.ts +1 -1
  33. package/dist/proxmox/generated/constraints/index.d.ts.map +1 -1
  34. package/dist/proxmox/generated/constraints/pve-access.d.ts +1 -1
  35. package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +1 -1
  36. package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +1 -1
  37. package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +1 -1
  38. package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +1 -1
  39. package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +1 -1
  40. package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +1 -1
  41. package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +1 -1
  42. package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +1 -1
  43. package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +1 -1
  44. package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +1 -1
  45. package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +1 -1
  46. package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +1 -1
  47. package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +1 -1
  48. package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts +23 -0
  49. package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts.map +1 -0
  50. package/dist/proxmox/generated/constraints/pve-pools.d.ts +1 -1
  51. package/dist/proxmox/generated/constraints/pve-storage.d.ts +1 -1
  52. package/dist/proxmox/index.d.ts +14 -3
  53. package/dist/proxmox/index.d.ts.map +1 -1
  54. package/dist/proxmox/index.js +574 -133
  55. package/dist/proxmox/index.js.map +33 -23
  56. package/dist/proxmox/qemu-form.d.ts +27 -18
  57. package/dist/proxmox/qemu-form.d.ts.map +1 -1
  58. package/dist/proxmox/qemu-judge.d.ts +30 -0
  59. package/dist/proxmox/qemu-judge.d.ts.map +1 -0
  60. package/dist/proxmox/qemu-lifecycle.d.ts +16 -2
  61. package/dist/proxmox/qemu-lifecycle.d.ts.map +1 -1
  62. package/dist/proxmox/qemu-net.d.ts +9 -0
  63. package/dist/proxmox/qemu-net.d.ts.map +1 -0
  64. package/dist/proxmox/qemu-props.d.ts +112 -0
  65. package/dist/proxmox/qemu-props.d.ts.map +1 -0
  66. package/dist/proxmox/qemu-volume.d.ts +27 -0
  67. package/dist/proxmox/qemu-volume.d.ts.map +1 -0
  68. package/dist/proxmox/qemu.d.ts +14 -27
  69. package/dist/proxmox/qemu.d.ts.map +1 -1
  70. package/dist/proxmox/storage-download-errors.d.ts +5 -0
  71. package/dist/proxmox/storage-download-errors.d.ts.map +1 -0
  72. package/dist/proxmox/storage-download-identity.d.ts +27 -0
  73. package/dist/proxmox/storage-download-identity.d.ts.map +1 -0
  74. package/dist/proxmox/storage-download-lifecycle.d.ts +97 -0
  75. package/dist/proxmox/storage-download-lifecycle.d.ts.map +1 -0
  76. package/dist/proxmox/storage-download-props.d.ts +36 -0
  77. package/dist/proxmox/storage-download-props.d.ts.map +1 -0
  78. package/dist/proxmox/storage-download-task.d.ts +17 -0
  79. package/dist/proxmox/storage-download-task.d.ts.map +1 -0
  80. package/dist/proxmox/storage-download.d.ts +44 -0
  81. package/dist/proxmox/storage-download.d.ts.map +1 -0
  82. package/dist/talos/credentials-write.d.ts +46 -0
  83. package/dist/talos/credentials-write.d.ts.map +1 -0
  84. package/dist/talos/credentials.d.ts +51 -9
  85. package/dist/talos/credentials.d.ts.map +1 -1
  86. package/dist/talos/fake-process.d.ts +56 -0
  87. package/dist/talos/fake-process.d.ts.map +1 -0
  88. package/dist/talos/index.d.ts +19 -4
  89. package/dist/talos/index.d.ts.map +1 -1
  90. package/dist/talos/index.js +357 -173
  91. package/dist/talos/index.js.map +16 -10
  92. package/dist/talos/kubeconfig-attrs.d.ts +4 -0
  93. package/dist/talos/kubeconfig-attrs.d.ts.map +1 -0
  94. package/dist/talos/kubeconfig.d.ts +41 -23
  95. package/dist/talos/kubeconfig.d.ts.map +1 -1
  96. package/dist/talos/machine-config-fixtures.d.ts +60 -0
  97. package/dist/talos/machine-config-fixtures.d.ts.map +1 -0
  98. package/dist/talos/machine-config-poll.d.ts +43 -0
  99. package/dist/talos/machine-config-poll.d.ts.map +1 -0
  100. package/dist/talos/machine-config-read.d.ts +49 -0
  101. package/dist/talos/machine-config-read.d.ts.map +1 -0
  102. package/dist/talos/resource.d.ts +18 -0
  103. package/dist/talos/resource.d.ts.map +1 -1
  104. package/dist/talos/talos-bootstrap-etcd.d.ts +38 -0
  105. package/dist/talos/talos-bootstrap-etcd.d.ts.map +1 -0
  106. package/dist/talos/talos-bootstrap.d.ts +87 -0
  107. package/dist/talos/talos-bootstrap.d.ts.map +1 -1
  108. package/dist/talos/talos-cluster-health.d.ts +43 -1
  109. package/dist/talos/talos-cluster-health.d.ts.map +1 -1
  110. package/dist/talos/talos-errors.d.ts +68 -0
  111. package/dist/talos/talos-errors.d.ts.map +1 -0
  112. package/dist/talos/talos-machine-config.d.ts +51 -15
  113. package/dist/talos/talos-machine-config.d.ts.map +1 -1
  114. package/dist/talos/talosctl.d.ts +7 -0
  115. package/dist/talos/talosctl.d.ts.map +1 -1
  116. package/dist/talos/values.d.ts +23 -3
  117. package/dist/talos/values.d.ts.map +1 -1
  118. package/dist/telemetry/fake-collector.d.ts +36 -0
  119. package/dist/telemetry/fake-collector.d.ts.map +1 -0
  120. package/dist/telemetry/index.d.ts +11 -0
  121. package/dist/telemetry/index.d.ts.map +1 -0
  122. package/dist/telemetry/index.js +223 -0
  123. package/dist/telemetry/index.js.map +12 -0
  124. package/dist/telemetry/layer.d.ts +86 -0
  125. package/dist/telemetry/layer.d.ts.map +1 -0
  126. package/dist/telemetry/redact-serialization.d.ts +21 -0
  127. package/dist/telemetry/redact-serialization.d.ts.map +1 -0
  128. package/dist/telemetry/redact.d.ts +46 -0
  129. package/dist/telemetry/redact.d.ts.map +1 -0
  130. package/dist/unifi/acl-rule-drift.d.ts +22 -0
  131. package/dist/unifi/acl-rule-drift.d.ts.map +1 -0
  132. package/dist/unifi/acl-rule-form.d.ts +90 -0
  133. package/dist/unifi/acl-rule-form.d.ts.map +1 -0
  134. package/dist/unifi/acl-rule-ordering-form.d.ts +49 -0
  135. package/dist/unifi/acl-rule-ordering-form.d.ts.map +1 -0
  136. package/dist/unifi/acl-rule-ordering.d.ts +54 -0
  137. package/dist/unifi/acl-rule-ordering.d.ts.map +1 -0
  138. package/dist/unifi/acl-rule.d.ts +49 -0
  139. package/dist/unifi/acl-rule.d.ts.map +1 -0
  140. package/dist/unifi/dns-policy-drift.d.ts +21 -0
  141. package/dist/unifi/dns-policy-drift.d.ts.map +1 -0
  142. package/dist/unifi/dns-policy-form.d.ts +97 -0
  143. package/dist/unifi/dns-policy-form.d.ts.map +1 -0
  144. package/dist/unifi/dns-policy.d.ts +55 -0
  145. package/dist/unifi/dns-policy.d.ts.map +1 -0
  146. package/dist/unifi/drift.d.ts +27 -0
  147. package/dist/unifi/drift.d.ts.map +1 -0
  148. package/dist/unifi/fake-unifi.d.ts +5 -1
  149. package/dist/unifi/fake-unifi.d.ts.map +1 -1
  150. package/dist/unifi/firewall-policy-drift.d.ts +26 -0
  151. package/dist/unifi/firewall-policy-drift.d.ts.map +1 -0
  152. package/dist/unifi/firewall-policy-form.d.ts +102 -0
  153. package/dist/unifi/firewall-policy-form.d.ts.map +1 -0
  154. package/dist/unifi/firewall-policy-ordering-form.d.ts +66 -0
  155. package/dist/unifi/firewall-policy-ordering-form.d.ts.map +1 -0
  156. package/dist/unifi/firewall-policy-ordering.d.ts +57 -0
  157. package/dist/unifi/firewall-policy-ordering.d.ts.map +1 -0
  158. package/dist/unifi/firewall-policy.d.ts +57 -0
  159. package/dist/unifi/firewall-policy.d.ts.map +1 -0
  160. package/dist/unifi/firewall-zone-form.d.ts +6 -0
  161. package/dist/unifi/firewall-zone-form.d.ts.map +1 -1
  162. package/dist/unifi/firewall-zone.d.ts +1 -1
  163. package/dist/unifi/firewall-zone.d.ts.map +1 -1
  164. package/dist/unifi/index.d.ts +11 -3
  165. package/dist/unifi/index.d.ts.map +1 -1
  166. package/dist/unifi/index.js +725 -60
  167. package/dist/unifi/index.js.map +31 -9
  168. package/dist/unifi/network-drift.d.ts +26 -0
  169. package/dist/unifi/network-drift.d.ts.map +1 -0
  170. package/dist/unifi/network-form.d.ts +40 -7
  171. package/dist/unifi/network-form.d.ts.map +1 -1
  172. package/dist/unifi/network.d.ts +1 -0
  173. package/dist/unifi/network.d.ts.map +1 -1
  174. package/dist/unifi/paginate.d.ts +24 -0
  175. package/dist/unifi/paginate.d.ts.map +1 -0
  176. package/dist/unifi/providers.d.ts +1 -1
  177. package/dist/unifi/providers.d.ts.map +1 -1
  178. package/dist/unifi/resource.d.ts +36 -1
  179. package/dist/unifi/resource.d.ts.map +1 -1
  180. package/dist/unifi/scan-source.d.ts +11 -0
  181. package/dist/unifi/scan-source.d.ts.map +1 -0
  182. package/dist/unifi/traffic-matching-list-drift.d.ts +22 -0
  183. package/dist/unifi/traffic-matching-list-drift.d.ts.map +1 -0
  184. package/dist/unifi/traffic-matching-list-form.d.ts +56 -0
  185. package/dist/unifi/traffic-matching-list-form.d.ts.map +1 -0
  186. package/dist/unifi/traffic-matching-list.d.ts +51 -0
  187. package/dist/unifi/traffic-matching-list.d.ts.map +1 -0
  188. package/dist/unifi/wifi-broadcast-drift.d.ts +22 -0
  189. package/dist/unifi/wifi-broadcast-drift.d.ts.map +1 -0
  190. package/dist/unifi/wifi-broadcast-form.d.ts +127 -0
  191. package/dist/unifi/wifi-broadcast-form.d.ts.map +1 -0
  192. package/dist/unifi/wifi-broadcast.d.ts +73 -0
  193. package/dist/unifi/wifi-broadcast.d.ts.map +1 -0
  194. package/docs/telemetry-spike.md +94 -0
  195. package/docs/telemetry.md +109 -0
  196. package/docs/unifi-acl-rule.md +122 -0
  197. package/docs/unifi-dns-policy.md +75 -0
  198. package/docs/unifi-firewall-policy.md +109 -0
  199. package/docs/unifi-traffic-matching-list.md +82 -0
  200. package/docs/unifi-wifi.md +120 -0
  201. package/docs/unifi.md +64 -3
  202. package/package.json +10 -2
  203. package/src/ceph/auth-entity.ts +74 -0
  204. package/src/ceph/ceph-argv-config.ts +78 -0
  205. package/src/ceph/ceph-argv.test.ts +226 -0
  206. package/src/ceph/ceph-argv.ts +189 -0
  207. package/src/ceph/ceph-auth-form.test.ts +103 -0
  208. package/src/ceph/ceph-auth-form.ts +107 -0
  209. package/src/ceph/ceph-auth-parse.test.ts +155 -0
  210. package/src/ceph/ceph-auth-parse.ts +121 -0
  211. package/src/ceph/ceph-auth-reconcile-secrecy.test.ts +82 -0
  212. package/src/ceph/ceph-auth-reconcile.test.ts +233 -0
  213. package/src/ceph/ceph-auth-reconcile.ts +119 -0
  214. package/src/ceph/ceph-config-options.test.ts +44 -0
  215. package/src/ceph/ceph-config-options.ts +36 -0
  216. package/src/ceph/ceph-key-leak-assert.ts +35 -0
  217. package/src/ceph/ceph-no-output-logging.test.ts +52 -0
  218. package/src/ceph/ceph-transport.test.ts +125 -0
  219. package/src/ceph/ceph-transport.ts +117 -0
  220. package/src/ceph/fake-ceph-dial.ts +55 -0
  221. package/src/ceph/index.ts +12 -0
  222. package/src/forgejo/team-member.test.ts +14 -0
  223. package/src/forgejo/team-member.ts +28 -1
  224. package/src/index.ts +1 -0
  225. package/src/proxmox/constraints-required.test.ts +1 -0
  226. package/src/proxmox/generated/constraints/index.ts +3 -1
  227. package/src/proxmox/generated/constraints/pve-access.ts +1 -1
  228. package/src/proxmox/generated/constraints/pve-cluster-backup.ts +1 -1
  229. package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +1 -1
  230. package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +1 -1
  231. package/src/proxmox/generated/constraints/pve-cluster-ha.ts +1 -1
  232. package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +1 -1
  233. package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +1 -1
  234. package/src/proxmox/generated/constraints/pve-cluster-replication.ts +1 -1
  235. package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +1 -1
  236. package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +1 -1
  237. package/src/proxmox/generated/constraints/pve-nodes-disks.ts +1 -1
  238. package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +1 -1
  239. package/src/proxmox/generated/constraints/pve-nodes-network.ts +1 -1
  240. package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +1 -1
  241. package/src/proxmox/generated/constraints/pve-nodes-storage.ts +30 -0
  242. package/src/proxmox/generated/constraints/pve-pools.ts +1 -1
  243. package/src/proxmox/generated/constraints/pve-storage.ts +1 -1
  244. package/src/proxmox/index.test.ts +11 -6
  245. package/src/proxmox/index.ts +19 -3
  246. package/src/proxmox/qemu-form.ts +49 -13
  247. package/src/proxmox/qemu-identity.test.ts +30 -0
  248. package/src/proxmox/qemu-judge.test.ts +65 -0
  249. package/src/proxmox/qemu-judge.ts +84 -0
  250. package/src/proxmox/qemu-lifecycle.test.ts +78 -19
  251. package/src/proxmox/qemu-lifecycle.ts +131 -32
  252. package/src/proxmox/qemu-net.test.ts +48 -0
  253. package/src/proxmox/qemu-net.ts +78 -0
  254. package/src/proxmox/qemu-ownership.test.ts +44 -0
  255. package/src/proxmox/qemu-props.test.ts +87 -0
  256. package/src/proxmox/qemu-props.ts +157 -0
  257. package/src/proxmox/qemu-volume.test.ts +229 -0
  258. package/src/proxmox/qemu-volume.ts +178 -0
  259. package/src/proxmox/qemu.ts +15 -29
  260. package/src/proxmox/storage-download-errors.ts +7 -0
  261. package/src/proxmox/storage-download-identity.test.ts +54 -0
  262. package/src/proxmox/storage-download-identity.ts +50 -0
  263. package/src/proxmox/storage-download-lifecycle.ts +226 -0
  264. package/src/proxmox/storage-download-ownership.test.ts +50 -0
  265. package/src/proxmox/storage-download-props.ts +38 -0
  266. package/src/proxmox/storage-download-task.ts +72 -0
  267. package/src/proxmox/storage-download.test.ts +216 -0
  268. package/src/proxmox/storage-download.ts +58 -0
  269. package/src/talos/credentials-write.test.ts +81 -0
  270. package/src/talos/credentials-write.ts +95 -0
  271. package/src/talos/credentials.test.ts +157 -0
  272. package/src/talos/credentials.ts +160 -90
  273. package/src/talos/fake-process.ts +94 -0
  274. package/src/talos/index.ts +39 -4
  275. package/src/talos/kubeconfig-attrs.ts +55 -0
  276. package/src/talos/kubeconfig.test.ts +256 -0
  277. package/src/talos/kubeconfig.ts +170 -90
  278. package/src/talos/machine-config-fixtures.ts +86 -0
  279. package/src/talos/machine-config-poll.test.ts +57 -0
  280. package/src/talos/machine-config-poll.ts +82 -0
  281. package/src/talos/machine-config-read.ts +118 -0
  282. package/src/talos/resource.ts +19 -0
  283. package/src/talos/secrets-gate.test.ts +63 -0
  284. package/src/talos/talos-bootstrap-etcd.ts +71 -0
  285. package/src/talos/talos-bootstrap.test.ts +218 -0
  286. package/src/talos/talos-bootstrap.ts +151 -45
  287. package/src/talos/talos-cluster-health.test.ts +143 -0
  288. package/src/talos/talos-cluster-health.ts +91 -28
  289. package/src/talos/talos-errors.ts +85 -0
  290. package/src/talos/talos-machine-config.test.ts +189 -0
  291. package/src/talos/talos-machine-config.ts +102 -88
  292. package/src/talos/talosctl.ts +8 -0
  293. package/src/talos/values.test.ts +34 -6
  294. package/src/talos/values.ts +41 -7
  295. package/src/telemetry/fake-collector.ts +59 -0
  296. package/src/telemetry/http-client-sealing.test.ts +82 -0
  297. package/src/telemetry/index.ts +10 -0
  298. package/src/telemetry/layer.test.ts +204 -0
  299. package/src/telemetry/layer.ts +152 -0
  300. package/src/telemetry/redact-serialization.ts +178 -0
  301. package/src/telemetry/redact.test.ts +124 -0
  302. package/src/telemetry/redact.ts +147 -0
  303. package/src/unifi/acl-rule-drift.ts +57 -0
  304. package/src/unifi/acl-rule-form.ts +135 -0
  305. package/src/unifi/acl-rule-ordering-form.ts +79 -0
  306. package/src/unifi/acl-rule-ordering.test.ts +141 -0
  307. package/src/unifi/acl-rule-ordering.ts +70 -0
  308. package/src/unifi/acl-rule.test.ts +183 -0
  309. package/src/unifi/acl-rule.ts +61 -0
  310. package/src/unifi/discriminated-filter-decode.test.ts +134 -0
  311. package/src/unifi/dns-policy-drift.ts +48 -0
  312. package/src/unifi/dns-policy-form.ts +142 -0
  313. package/src/unifi/dns-policy.test.ts +154 -0
  314. package/src/unifi/dns-policy.ts +67 -0
  315. package/src/unifi/drift.test.ts +78 -0
  316. package/src/unifi/drift.ts +56 -0
  317. package/src/unifi/errors-and-secrets.test.ts +127 -0
  318. package/src/unifi/fake-unifi.ts +5 -1
  319. package/src/unifi/firewall-policy-drift.test.ts +151 -0
  320. package/src/unifi/firewall-policy-drift.ts +55 -0
  321. package/src/unifi/firewall-policy-filter-roundtrip.test.ts +143 -0
  322. package/src/unifi/firewall-policy-form.ts +146 -0
  323. package/src/unifi/firewall-policy-ordering-form.ts +111 -0
  324. package/src/unifi/firewall-policy-ordering.test.ts +185 -0
  325. package/src/unifi/firewall-policy-ordering.ts +87 -0
  326. package/src/unifi/firewall-policy.test.ts +183 -0
  327. package/src/unifi/firewall-policy.ts +73 -0
  328. package/src/unifi/firewall-zone-form.ts +31 -2
  329. package/src/unifi/firewall-zone.test.ts +21 -1
  330. package/src/unifi/firewall-zone.ts +1 -1
  331. package/src/unifi/get-only-guard.test.ts +165 -0
  332. package/src/unifi/index.ts +78 -1
  333. package/src/unifi/network-drift.test.ts +207 -0
  334. package/src/unifi/network-drift.ts +80 -0
  335. package/src/unifi/network-form.ts +11 -39
  336. package/src/unifi/network.test.ts +7 -82
  337. package/src/unifi/network.ts +6 -6
  338. package/src/unifi/paginate.test.ts +252 -0
  339. package/src/unifi/paginate.ts +141 -0
  340. package/src/unifi/providers.ts +19 -1
  341. package/src/unifi/resource.ts +73 -3
  342. package/src/unifi/scan-source.ts +64 -0
  343. package/src/unifi/traffic-matching-list-drift.ts +40 -0
  344. package/src/unifi/traffic-matching-list-form.ts +77 -0
  345. package/src/unifi/traffic-matching-list.test.ts +202 -0
  346. package/src/unifi/traffic-matching-list.ts +78 -0
  347. package/src/unifi/wifi-broadcast-details-call.test.ts +92 -0
  348. package/src/unifi/wifi-broadcast-drift.ts +69 -0
  349. package/src/unifi/wifi-broadcast-form.ts +211 -0
  350. package/src/unifi/wifi-broadcast-secrets.test.ts +145 -0
  351. package/src/unifi/wifi-broadcast.test.ts +155 -0
  352. package/src/unifi/wifi-broadcast.ts +90 -0
  353. package/src/unifi/write-op-reference.test.ts +190 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A fake OTLP/HTTP collector for the tests beside it — `Bun.serve` on an ephemeral 127.0.0.1 port,
3
+ * answering every POST with 200 and recording each request's path, headers and raw body bytes.
4
+ *
5
+ * ⛔ TEST-ONLY, and no live call ever leaves this process: nothing here reaches a real Victoria
6
+ * endpoint, satisfying the workflow's "no live calls to Victoria from tests" rule. A consumer's
7
+ * own integration test points `telemetryLayer` at ITS running Victoria; this file exists so this
8
+ * package's tests never need one.
9
+ * ★ 200 UNCONDITIONALLY. `OtlpExporter.make` treats any non-2xx as a transient failure and retries
10
+ * three times with backoff (OtlpExporter.ts `policy`/`retryTransient`) — a fake that ever answered
11
+ * with an error would make a test wait through that retry schedule for no reason.
12
+ * ⛔ RAW BYTES, NOT PARSED JSON (redteam PR 302 CRITICAL item 1, 2026-09-26): `telemetryLayer` now
13
+ * sends `OtlpSerialization.layerProtobuf`, not JSON, so there is no body to `JSON.parse`. `sawText`
14
+ * below is what every test needing to inspect content uses instead — it works unchanged for either
15
+ * encoding, since protobuf's string fields are raw UTF-8 with no escaping: a leaked literal (a
16
+ * secret, a span name, `<redacted>`) always appears as a contiguous byte run in the raw body,
17
+ * exactly like it would in JSON, with no OTLP protobuf decoder needed to find it.
18
+ */
19
+ export interface CollectedRequest {
20
+ readonly path: string;
21
+ readonly headers: Headers;
22
+ readonly raw: Uint8Array;
23
+ }
24
+ export interface FakeCollector {
25
+ readonly url: string;
26
+ readonly requests: CollectedRequest[];
27
+ /** `requests` filtered to POSTs at exactly this path (e.g. `/v1/traces`). */
28
+ at(path: string): CollectedRequest[];
29
+ /** True when `text`'s bytes appear anywhere in ANY collected request's raw body. ASCII text (a
30
+ * secret, a hostname, `<redacted>`) decodes to itself regardless of what surrounds it in an
31
+ * otherwise-binary protobuf buffer — see this file's own header for why that's sound. */
32
+ sawText(text: string): boolean;
33
+ stop(): void;
34
+ }
35
+ export declare const fakeCollector: () => FakeCollector;
36
+ //# sourceMappingURL=fake-collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-collector.d.ts","sourceRoot":"","sources":["../../src/telemetry/fake-collector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IACtC,6EAA6E;IAC7E,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAAC;IACrC;;6FAEyF;IACzF,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,IAAI,IAAI,IAAI,CAAC;CACd;AAID,eAAO,MAAM,aAAa,QAAO,aAoBhC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `@homeflare/alchemy/telemetry` — an OTLP tracing/logging/metrics Layer for a stack's own
3
+ * `providers`, off unless a consumer gives it real endpoints. See docs/telemetry.md for how a site
4
+ * file wires it up and docs/telemetry-spike.md for what a stack's `providers` Layer can and cannot
5
+ * capture (measured against alchemy 2.0.0-beta.79's own engine).
6
+ */
7
+ export { telemetryLayer } from './layer.ts';
8
+ export type { TelemetryEndpoints, TelemetryOptions } from './layer.ts';
9
+ export { DROP, REDACTED, redactAttribute, wrapTracer } from './redact.ts';
10
+ export type { RedactionPolicy } from './redact.ts';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,223 @@
1
+ // src/telemetry/layer.ts
2
+ import * as Effect2 from "effect/Effect";
3
+ import * as Layer2 from "effect/Layer";
4
+ import * as Logger from "effect/Logger";
5
+ import * as LogLevel from "effect/LogLevel";
6
+ import * as Tracer from "effect/Tracer";
7
+ import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
8
+ import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpTracer } from "effect/unstable/observability";
9
+
10
+ // src/telemetry/redact-serialization.ts
11
+ import * as Effect from "effect/Effect";
12
+ import * as Layer from "effect/Layer";
13
+ import { OtlpSerialization } from "effect/unstable/observability";
14
+
15
+ // src/telemetry/redact.ts
16
+ var REDACTED = "<redacted>";
17
+ var DROP = Symbol.for("homeflare/alchemy/telemetry/drop-attribute");
18
+ var HEADER_ATTRIBUTE = /^http\.(?:request|response)\.header\./;
19
+ var redactSegments = (value, denylist) => value.split("/").map((segment) => denylist.has(segment) ? REDACTED : segment).join("/");
20
+ var redactAttribute = (key, value, policy) => {
21
+ if (HEADER_ATTRIBUTE.test(key))
22
+ return DROP;
23
+ if (key === "url.query")
24
+ return DROP;
25
+ if (typeof value !== "string")
26
+ return value;
27
+ const denylist = new Set(policy.denylist ?? []);
28
+ if (denylist.size === 0 && key !== "url.full" && key !== "server.address")
29
+ return value;
30
+ if (key === "server.address") {
31
+ try {
32
+ const url = new URL(value);
33
+ return denylist.has(url.hostname) ? REDACTED : value;
34
+ } catch {
35
+ return denylist.has(value) ? REDACTED : value;
36
+ }
37
+ }
38
+ if (key === "url.path")
39
+ return redactSegments(value, denylist);
40
+ if (key === "url.full") {
41
+ try {
42
+ const url = new URL(value);
43
+ const host = denylist.has(url.hostname) ? REDACTED : url.host;
44
+ return `${url.protocol}//${host}${redactSegments(url.pathname, denylist)}`;
45
+ } catch {
46
+ return value;
47
+ }
48
+ }
49
+ return value;
50
+ };
51
+ var redactAttributes = (attributes, policy) => {
52
+ const out = {};
53
+ for (const [key, value] of Object.entries(attributes)) {
54
+ const redacted = redactAttribute(key, value, policy);
55
+ if (redacted !== DROP)
56
+ out[key] = redacted;
57
+ }
58
+ return out;
59
+ };
60
+ var wrapTracer = (inner, policy) => ({
61
+ context: inner.context,
62
+ span(options) {
63
+ const real = inner.span(options);
64
+ return {
65
+ _tag: "Span",
66
+ spanId: real.spanId,
67
+ traceId: real.traceId,
68
+ parent: real.parent,
69
+ annotations: real.annotations,
70
+ get name() {
71
+ return real.name;
72
+ },
73
+ get status() {
74
+ return real.status;
75
+ },
76
+ get attributes() {
77
+ return real.attributes;
78
+ },
79
+ get links() {
80
+ return real.links;
81
+ },
82
+ sampled: real.sampled,
83
+ kind: real.kind,
84
+ end: (endTime, exit) => {
85
+ real.end(endTime, exit);
86
+ },
87
+ attribute: (key, value) => {
88
+ const redacted = redactAttribute(key, value, policy);
89
+ if (redacted !== DROP)
90
+ real.attribute(key, redacted);
91
+ },
92
+ event: (name, startTime, attributes) => {
93
+ real.event(name, startTime, attributes ? redactAttributes(attributes, policy) : attributes);
94
+ },
95
+ addLinks: (links) => {
96
+ real.addLinks(links);
97
+ }
98
+ };
99
+ }
100
+ });
101
+
102
+ // src/telemetry/redact-serialization.ts
103
+ var URL_PATTERN = /https?:\/\/[^\s"'<>)]+/g;
104
+ var scrubUrl = (raw, denylist) => {
105
+ try {
106
+ const url = new URL(raw);
107
+ const host = denylist.has(url.hostname) ? REDACTED : url.host;
108
+ const path = url.pathname.split("/").map((segment) => denylist.has(segment) ? REDACTED : segment).join("/");
109
+ return `${url.protocol}//${host}${path}`;
110
+ } catch {
111
+ return raw;
112
+ }
113
+ };
114
+ var scrubText = (text, policy) => {
115
+ const denylist = policy.denylist ?? [];
116
+ const denylistSet = new Set(denylist);
117
+ let out = text.replace(URL_PATTERN, (match) => scrubUrl(match, denylistSet));
118
+ for (const token of denylist) {
119
+ if (token.length === 0)
120
+ continue;
121
+ out = out.split(token).join(REDACTED);
122
+ }
123
+ return out;
124
+ };
125
+ var scrubValue = (value, policy) => {
126
+ if (typeof value.stringValue === "string")
127
+ return { ...value, stringValue: scrubText(value.stringValue, policy) };
128
+ if (value.arrayValue !== undefined) {
129
+ return {
130
+ ...value,
131
+ arrayValue: { values: value.arrayValue.values.map((v) => scrubValue(v, policy)) }
132
+ };
133
+ }
134
+ if (value.kvlistValue !== undefined) {
135
+ return {
136
+ ...value,
137
+ kvlistValue: {
138
+ values: value.kvlistValue.values.map((kv) => ({
139
+ key: kv.key,
140
+ value: scrubValue(kv.value, policy)
141
+ }))
142
+ }
143
+ };
144
+ }
145
+ return value;
146
+ };
147
+ var scrubKeyValues = (attributes, policy) => attributes.map((kv) => ({ key: kv.key, value: scrubValue(kv.value, policy) }));
148
+ var redactTraceData = (data, policy) => ({
149
+ resourceSpans: data.resourceSpans.map((resourceSpan) => ({
150
+ ...resourceSpan,
151
+ scopeSpans: resourceSpan.scopeSpans.map((scopeSpan) => ({
152
+ ...scopeSpan,
153
+ spans: scopeSpan.spans.map((span) => ({
154
+ ...span,
155
+ attributes: scrubKeyValues(span.attributes, policy),
156
+ events: span.events.map((event) => ({
157
+ ...event,
158
+ attributes: scrubKeyValues(event.attributes, policy),
159
+ name: scrubText(event.name, policy)
160
+ })),
161
+ links: span.links.map((link) => ({
162
+ ...link,
163
+ attributes: scrubKeyValues(link.attributes, policy)
164
+ })),
165
+ name: scrubText(span.name, policy),
166
+ status: span.status.message === undefined ? span.status : { ...span.status, message: scrubText(span.status.message, policy) }
167
+ }))
168
+ }))
169
+ }))
170
+ });
171
+ var scrubLogRecord = (record, policy) => ({
172
+ ...record,
173
+ attributes: scrubKeyValues(record.attributes, policy),
174
+ ...record.body !== undefined ? { body: scrubValue(record.body, policy) } : {}
175
+ });
176
+ var redactLogsData = (data, policy) => ({
177
+ resourceLogs: data.resourceLogs.map((resourceLog) => ({
178
+ ...resourceLog,
179
+ scopeLogs: resourceLog.scopeLogs.map((scopeLog) => ({
180
+ ...scopeLog,
181
+ ...scopeLog.logRecords !== undefined ? { logRecords: scopeLog.logRecords.map((record) => scrubLogRecord(record, policy)) } : {}
182
+ }))
183
+ }))
184
+ });
185
+ var redactedSerialization = (policy) => Layer.effect(OtlpSerialization.OtlpSerialization, Effect.map(OtlpSerialization.OtlpSerialization, (inner) => ({
186
+ logs: (data) => inner.logs(redactLogsData(data, policy)),
187
+ metrics: inner.metrics,
188
+ traces: (data) => inner.traces(redactTraceData(data, policy))
189
+ }))).pipe(Layer.provide(OtlpSerialization.layerProtobuf));
190
+
191
+ // src/telemetry/layer.ts
192
+ var resourceOf = (options) => ({
193
+ serviceName: options.serviceName,
194
+ ...options.serviceVersion !== undefined ? { serviceVersion: options.serviceVersion } : {},
195
+ ...options.resourceAttributes !== undefined ? { attributes: options.resourceAttributes } : {}
196
+ });
197
+ var tracerLayer = (url, options, exportInterval) => Layer2.effect(Tracer.Tracer, Effect2.map(OtlpTracer.make({ exportInterval, resource: resourceOf(options), url }), (inner) => wrapTracer(inner, options.redaction ?? {}))).pipe(Layer2.provideMerge(OtlpExporter.layerFlusher));
198
+ var loggerLayer = (url, options, exportInterval) => {
199
+ const floor = options.logsMinimumLevel ?? "Info";
200
+ return Logger.layer([
201
+ Effect2.map(OtlpLogger.make({ exportInterval, resource: resourceOf(options), url }), (inner) => Logger.make((record) => LogLevel.isGreaterThanOrEqualTo(record.logLevel, floor) ? inner.log(record) : undefined))
202
+ ], { mergeWithExisting: true }).pipe(Layer2.provideMerge(OtlpExporter.layerFlusher));
203
+ };
204
+ var telemetryLayer = (options) => {
205
+ const { traces, logs, metrics } = options.endpoints;
206
+ if (traces === undefined && logs === undefined && metrics === undefined)
207
+ return Layer2.empty;
208
+ const exportInterval = options.exportInterval ?? "1 second";
209
+ const tracer = traces === undefined ? Layer2.empty : tracerLayer(traces, options, exportInterval);
210
+ const logger = logs === undefined ? Layer2.empty : loggerLayer(logs, options, exportInterval);
211
+ const meter = metrics === undefined ? Layer2.empty : OtlpMetrics.layer({ exportInterval, resource: resourceOf(options), url: metrics });
212
+ return Layer2.mergeAll(tracer, logger, meter).pipe(Layer2.provide(redactedSerialization(options.redaction ?? {})), Layer2.provide(FetchHttpClient.layer));
213
+ };
214
+ export {
215
+ DROP,
216
+ REDACTED,
217
+ redactAttribute,
218
+ telemetryLayer,
219
+ wrapTracer
220
+ };
221
+
222
+ //# debugId=D5C073A7ECCA33DE64756E2164756E21
223
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/telemetry/layer.ts", "../src/telemetry/redact-serialization.ts", "../src/telemetry/redact.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * A Layer factory for OTLP tracing/logging/metrics, built for a stack's own `providers` Layer\n * rather than the CLI-wide install alchemy's own `Telemetry/Layer.ts` does (`src/Telemetry/\n * Layer.ts:13-15`, hard-coded to `otel.alchemy.run` with no override). Nothing here calls that\n * endpoint or knows it exists.\n *\n * ⛔ NO DEFAULTS THAT POINT ANYWHERE. `endpoints` is `{}` by default and `telemetryLayer` resolves\n * to `Layer.empty` when every entry is `undefined` — see layer.test.ts's own \"nothing sent\n * when unconfigured\". A consumer wires actual hosts from ITS OWN site config; see docs/telemetry.md.\n * ★ SEALED TRANSPORT — the caddy lesson (kit PR 293, admin.ts's own ⛔): merging a layer that still\n * requires `HttpClient.HttpClient` into a stack's `providers` would let this module's own\n * `FetchHttpClient` shadow a sibling fetch-based provider's client once both are merged into the\n * same `Layer.mergeAll`. `Layer.provide` (not `Layer.provideMerge`) twice below feeds\n * `FetchHttpClient`/`OtlpSerialization` to the OTLP layers and returns ONLY their own output —\n * `HttpClient.HttpClient` never appears in what `telemetryLayer` hands back. See\n * http-client-sealing.test.ts.\n * ★ PER-SIGNAL URLS, NOT ONE BASE URL. effect's own `Otlp.layer`/`layerJson` take a single\n * `baseUrl` and post to `<baseUrl>/v1/{traces,metrics,logs}` — fine when one collector serves all\n * three, wrong for Victoria's split binaries (VictoriaTraces/Logs/Metrics on three different\n * ports/paths — docs/telemetry.md has the exact paths). `OtlpTracer`/`OtlpLogger`/`OtlpMetrics`\n * each take their own `url` and post to it verbatim (OtlpExporter.ts `make`: `HttpClientRequest.\n * post(options.url, …)`, no suffix appended) — exactly the shape three independent endpoints need.\n * ⛔ PROTOBUF, NOT JSON (redteam PR 302 CRITICAL item 1, 2026-09-26, measured against the pinned\n * binaries in homeflare-mini's `src/obs`): VictoriaLogs 1.52.0 and VictoriaMetrics 1.151.0 both\n * answer OTLP/HTTP JSON with `400 \"json encoding isn't supported for opentelemetry format\"` —\n * effect's exporter treats a 400 as non-transient, drops the batch silently and disables itself for\n * 60s at debug-only log level, so a JSON-encoded consumer sees traces (VictoriaTraces alone\n * tolerates JSON) but never logs or metrics, with no visible error. `redactedSerialization` below\n * provides `OtlpSerialization.layerProtobuf`, which all three ingested in the same measurement.\n */\nimport type * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport * as Logger from 'effect/Logger';\nimport * as LogLevel from 'effect/LogLevel';\nimport * as Tracer from 'effect/Tracer';\nimport * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';\nimport { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpTracer } from 'effect/unstable/observability';\nimport { redactedSerialization } from './redact-serialization.ts';\nimport { type RedactionPolicy, wrapTracer } from './redact.ts';\n\n/** Full OTLP/HTTP endpoint per signal — e.g. `http://victoria.example.test:10428/insert/opentelemetry/v1/traces`. Any entry left `undefined` sends nothing for that signal. */\nexport interface TelemetryEndpoints {\n readonly traces?: string;\n readonly logs?: string;\n readonly metrics?: string;\n}\n\nexport interface TelemetryOptions {\n readonly endpoints: TelemetryEndpoints;\n /** OTLP `service.name` — required, since an unnamed service in a shared Victoria instance is unusable. */\n readonly serviceName: string;\n readonly serviceVersion?: string;\n /** Extra OTLP resource attributes (e.g. `{ \"deployment.environment\": \"prod\" }`). */\n readonly resourceAttributes?: Record<string, unknown>;\n readonly redaction?: RedactionPolicy;\n /**\n * How often a signal flushes its buffer.\n * @default '1 second' — short on purpose: alchemy invocations are CLI runs, often shorter than a\n * typical OTLP batch interval, so the export must fire well before the process could exit. The\n * scope's own shutdown flush (`Exporter.layerFlusher`) is the backstop for whatever this interval\n * misses; docs/telemetry-spike.md measured both paths.\n */\n readonly exportInterval?: Duration.Input;\n /**\n * Floor below which a log record never reaches the OTLP logger.\n * @default 'Info' — redteam PR 302 IMPORTANT item 4: the `alchemy` CLI sets the GLOBAL minimum\n * log level to Debug (`GlobalLog.ts`), and `OtlpLogger` has no floor of its own, so without one\n * every Debug-level record (a provider's own connection-string/config dump, annotation noise)\n * would ship — once `redactedSerialization` fixed item 1's silent-JSON-failure, they would\n * actually start arriving. This floor applies ONLY to what THIS layer exports; a consumer's\n * console/file loggers keep whatever level the CLI's own global setting gives them, since\n * `logsMinimumLevel` filters after `Logger.layer`'s `mergeWithExisting: true` has already added\n * this logger alongside them, not by lowering that global setting.\n */\n readonly logsMinimumLevel?: LogLevel.LogLevel;\n}\n\n// ⚠️ `exactOptionalPropertyTypes` — omit a key entirely rather than set it `undefined`, or the\n// OTLP layers' own `{ readonly attributes?: X }` (no `| undefined` in their own signatures)\n// rejects the object.\nconst resourceOf = (options: TelemetryOptions) => ({\n serviceName: options.serviceName,\n ...(options.serviceVersion !== undefined ? { serviceVersion: options.serviceVersion } : {}),\n ...(options.resourceAttributes !== undefined ? { attributes: options.resourceAttributes } : {}),\n});\n\n/**\n * `OtlpTracer.layer`'s own composition (OtlpTracer.ts: `flow(make, Layer.effect(Tracer.Tracer),\n * Layer.provideMerge(Exporter.layerFlusher))`), with `wrapTracer` inserted between `make` and\n * `Layer.effect` so every span this tracer creates is redacted before anything else can see it.\n */\nconst tracerLayer = (url: string, options: TelemetryOptions, exportInterval: Duration.Input) =>\n Layer.effect(\n Tracer.Tracer,\n Effect.map(OtlpTracer.make({ exportInterval, resource: resourceOf(options), url }), (inner) =>\n wrapTracer(inner, options.redaction ?? {}),\n ),\n ).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));\n\n/**\n * `OtlpLogger.layer`'s own composition (`Logger.layer([make(options)], { mergeWithExisting: true })\n * .pipe(Layer.provideMerge(Exporter.layerFlusher))`), with a level floor inserted between `make` and\n * `Logger.layer` — see `TelemetryOptions.logsMinimumLevel`'s own doc for why.\n */\nconst loggerLayer = (url: string, options: TelemetryOptions, exportInterval: Duration.Input) => {\n const floor = options.logsMinimumLevel ?? 'Info';\n return Logger.layer(\n [\n Effect.map(OtlpLogger.make({ exportInterval, resource: resourceOf(options), url }), (inner) =>\n Logger.make((record) =>\n LogLevel.isGreaterThanOrEqualTo(record.logLevel, floor) ? inner.log(record) : undefined,\n ),\n ),\n ],\n { mergeWithExisting: true },\n ).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));\n};\n\n/**\n * The Layer a consumer stack merges into its own `providers` — see docs/telemetry.md for how a\n * site file wires real hosts in, and docs/telemetry-spike.md for what a stack's `providers` Layer does and does\n * not carry a span for.\n *\n * ⚠️ FIXED THREE-ARG `Layer.mergeAll`, NOT AN ARRAY `.reduce`. Every signal slot is filled — with\n * the real per-signal layer when its endpoint is set, `Layer.empty` when it is not — instead of\n * building a variable-length array and folding it with `Layer.merge`. `Layer<in ROut, …>`\n * declares its success channel CONTRAVARIANT, and once a fold's accumulator needs an explicit\n * annotation to type-check under this repo's `exactOptionalPropertyTypes`, TS's overload\n * resolution for `Layer.provide`/`Layer.merge` starts inferring `unknown` for that channel and\n * then fails `unknown` against `never` (measured 2026-09-26, TS2769). `Layer.mergeAll`'s own\n * fixed-arity overload — the exact shape alchemy's own `Telemetry/Layer.ts` uses for its three\n * signals — infers cleanly with no annotation at all.\n */\nexport const telemetryLayer = (options: TelemetryOptions): Layer.Layer<never> => {\n const { traces, logs, metrics } = options.endpoints;\n if (traces === undefined && logs === undefined && metrics === undefined) return Layer.empty;\n // Default declared here, not in the options' type, so every call site above stays a concrete\n // `Duration.Input` — `exactOptionalPropertyTypes` rejects passing `undefined` through to the\n // OTLP layers' own `exportInterval?: Duration.Input` (no `| undefined` in their signatures).\n const exportInterval: Duration.Input = options.exportInterval ?? '1 second';\n const tracer = traces === undefined ? Layer.empty : tracerLayer(traces, options, exportInterval);\n const logger = logs === undefined ? Layer.empty : loggerLayer(logs, options, exportInterval);\n const meter =\n metrics === undefined\n ? Layer.empty\n : OtlpMetrics.layer({ exportInterval, resource: resourceOf(options), url: metrics });\n return Layer.mergeAll(tracer, logger, meter).pipe(\n Layer.provide(redactedSerialization(options.redaction ?? {})),\n Layer.provide(FetchHttpClient.layer),\n );\n};\n",
6
+ "/**\n * Free-text OTLP redaction applied at `OtlpSerialization` — the ONE place every span/log field\n * reaches the wire, right before HTTP body encoding. `redact.ts`'s `wrapTracer` only sees what a\n * caller passes to `Span.attribute()`/`Span.event()`; two real fields never go through either hook:\n *\n * - `status.message` and the `exception.*` event attributes `OtlpTracer`'s own `makeOtlpSpan` builds\n * from `Cause.prettyErrors(exit.cause)` — computed at export time, inside effect's tracer, AFTER\n * `wrapTracer.end()` has already handed the exit straight to `real.end()`.\n * - a log line inside a span, turned into a span event by effect's default `tracerLogger` — the log\n * message becomes the event NAME (not an attribute `wrapTracer.event()` redacts), and its\n * `effect.cause` attribute is free text `redactAttribute`'s key-based rules never recognize.\n *\n * Redteam finding (PR 302, 2026-09-26): a provider GET that 404'd exported `exception.message =\n * \"... 404 GET http://host/secret-site-abc123/probe?token=SECRET123\"` unredacted — the query, the\n * denylisted path segment and the denylisted host all went out. A `logWarning` inside a span leaked\n * the same way through the event name.\n *\n * ⚠️ SUBSTRING MATCH, NOT SEGMENT MATCH. `redactAttribute` compares a denylist entry against a whole\n * `/`-bounded URL segment on purpose (`redact.ts`'s own ⚠️: `\"db\"` must not blank `\"db1.example.\n * test\"`). Free text has no segment boundaries — a log message or a formatted `Cause` embeds a\n * token in prose — so `scrubText` matches a denylist entry as a literal substring ANYWHERE in the\n * string. This is strictly broader than the structured rules, so it also closes the `server.\n * address` bare-hostname and legacy `http.url` gaps (redteam MINOR item 5) as a side effect: a\n * denylisted hostname is a substring match wherever it appears, URL-shaped or not. The tradeoff a\n * caller accepts by naming a short, common word in `denylist` is over-redaction, never a leak.\n */\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport { OtlpSerialization } from 'effect/unstable/observability';\nimport type { OtlpLogger, OtlpResource, OtlpTracer } from 'effect/unstable/observability';\nimport { REDACTED, type RedactionPolicy } from './redact.ts';\n\nconst URL_PATTERN = /https?:\\/\\/[^\\s\"'<>)]+/g;\n\n/** Strips an embedded URL's query/hash and blanks its denylisted host/path segments — `redact.ts`'s\n * `url.full` case, reused here because a URL can appear inside prose, not just as its own attribute. */\nconst scrubUrl = (raw: string, denylist: ReadonlySet<string>): string => {\n try {\n const url = new URL(raw);\n const host = denylist.has(url.hostname) ? REDACTED : url.host;\n const path = url.pathname\n .split('/')\n .map((segment) => (denylist.has(segment) ? REDACTED : segment))\n .join('/');\n return `${url.protocol}//${host}${path}`;\n } catch {\n return raw; // Matched the URL_PATTERN regex but `URL` still rejects it — leave verbatim.\n }\n};\n\n/** Redacts one piece of free text: every embedded URL loses its query string and denylisted\n * segments, then every denylist entry is blanked as a substring anywhere else in the text. */\nexport const scrubText = (text: string, policy: RedactionPolicy): string => {\n const denylist = policy.denylist ?? [];\n const denylistSet = new Set(denylist);\n let out = text.replace(URL_PATTERN, (match) => scrubUrl(match, denylistSet));\n for (const token of denylist) {\n if (token.length === 0) continue; // an empty entry would blank every character boundary\n out = out.split(token).join(REDACTED);\n }\n return out;\n};\n\n/** Walks one OTLP `AnyValue`'s string leaves (`stringValue`, and recursively through `arrayValue`/\n * `kvlistValue`) — the union effect's OTLP encoders use for every attribute and log body value. */\nconst scrubValue = (\n value: OtlpResource.AnyValue,\n policy: RedactionPolicy,\n): OtlpResource.AnyValue => {\n if (typeof value.stringValue === 'string')\n return { ...value, stringValue: scrubText(value.stringValue, policy) };\n if (value.arrayValue !== undefined) {\n return {\n ...value,\n arrayValue: { values: value.arrayValue.values.map((v) => scrubValue(v, policy)) },\n };\n }\n if (value.kvlistValue !== undefined) {\n return {\n ...value,\n kvlistValue: {\n values: value.kvlistValue.values.map((kv) => ({\n key: kv.key,\n value: scrubValue(kv.value, policy),\n })),\n },\n };\n }\n return value; // boolValue / intValue / doubleValue / bytesValue — never free text.\n};\n\nconst scrubKeyValues = (\n attributes: ReadonlyArray<OtlpResource.KeyValue>,\n policy: RedactionPolicy,\n): Array<OtlpResource.KeyValue> =>\n attributes.map((kv) => ({ key: kv.key, value: scrubValue(kv.value, policy) }));\n\n/** `TraceData` after every span's name, attributes, events (including the `exception.*` ones\n * `makeOtlpSpan` builds from the exit), status message and link attributes have been scrubbed. */\nexport const redactTraceData = (\n data: OtlpTracer.TraceData,\n policy: RedactionPolicy,\n): OtlpTracer.TraceData => ({\n resourceSpans: data.resourceSpans.map((resourceSpan) => ({\n ...resourceSpan,\n scopeSpans: resourceSpan.scopeSpans.map((scopeSpan) => ({\n ...scopeSpan,\n spans: scopeSpan.spans.map((span) => ({\n ...span,\n attributes: scrubKeyValues(span.attributes, policy),\n events: span.events.map((event) => ({\n ...event,\n attributes: scrubKeyValues(event.attributes, policy),\n name: scrubText(event.name, policy),\n })),\n links: span.links.map((link) => ({\n ...link,\n attributes: scrubKeyValues(link.attributes, policy),\n })),\n name: scrubText(span.name, policy),\n status:\n span.status.message === undefined\n ? span.status\n : { ...span.status, message: scrubText(span.status.message, policy) },\n })),\n })),\n })),\n});\n\ntype LogRecord = NonNullable<\n OtlpLogger.LogsData['resourceLogs'][number]['scopeLogs'][number]['logRecords']\n>[number];\n\n// ⚠️ `exactOptionalPropertyTypes` — `body` is `AnyValue?` with no `| undefined` in its own\n// signature (matches `layer.ts`'s own `resourceOf` note), so an absent one is OMITTED, not set to\n// `undefined`, or this object-literal return fails to type-check against `ILogRecord`.\nconst scrubLogRecord = (record: LogRecord, policy: RedactionPolicy): LogRecord => ({\n ...record,\n attributes: scrubKeyValues(record.attributes, policy),\n ...(record.body !== undefined ? { body: scrubValue(record.body, policy) } : {}),\n});\n\n/** `LogsData` after every log record's attributes and body have been scrubbed — the fix for redteam\n * item 4's \"logs ship unredacted\": `log.error` (`Cause.pretty`) and the message body are free text. */\nexport const redactLogsData = (\n data: OtlpLogger.LogsData,\n policy: RedactionPolicy,\n): OtlpLogger.LogsData => ({\n resourceLogs: data.resourceLogs.map((resourceLog) => ({\n ...resourceLog,\n scopeLogs: resourceLog.scopeLogs.map((scopeLog) => ({\n ...scopeLog,\n // Same `exactOptionalPropertyTypes` reasoning as `scrubLogRecord` — omit `logRecords`\n // entirely rather than assign it `undefined` when the source had none.\n ...(scopeLog.logRecords !== undefined\n ? { logRecords: scopeLog.logRecords.map((record) => scrubLogRecord(record, policy)) }\n : {}),\n })),\n })),\n});\n\n/**\n * Wraps the real `OtlpSerialization` so `telemetryLayer` gets this free-text pass on every signal\n * with no separate wiring at each call site — `layer.ts` provides this in place of a bare\n * `OtlpSerialization.layerProtobuf`. Metrics pass through untouched: they carry numeric data points\n * keyed by `Metric.Metric.AttributeSet`, not provider-supplied free text.\n */\nexport const redactedSerialization = (\n policy: RedactionPolicy,\n): Layer.Layer<OtlpSerialization.OtlpSerialization> =>\n Layer.effect(\n OtlpSerialization.OtlpSerialization,\n Effect.map(OtlpSerialization.OtlpSerialization, (inner) => ({\n logs: (data: OtlpLogger.LogsData) => inner.logs(redactLogsData(data, policy)),\n metrics: inner.metrics,\n traces: (data: OtlpTracer.TraceData) => inner.traces(redactTraceData(data, policy)),\n })),\n ).pipe(Layer.provide(OtlpSerialization.layerProtobuf));\n",
7
+ "/**\n * URL/header redaction for outgoing spans — applied at the `Tracer.Tracer` boundary so it holds for\n * EVERY span source (a provider's own `Effect.withSpan`, effect's `HttpClient` client spans, and\n * alchemy's own plan/apply/resource spans), not just the ones a caller remembers to annotate.\n *\n * ★ WHY THE TRACER, NOT AN HTTPCLIENT MIDDLEWARE. effect's `HttpClient.make` (src/unstable/http/\n * HttpClient.ts) hard-codes `span.attribute(\"url.full\", …)`, `\"url.query\"` and\n * `http.request.header.<name>` on the span it creates — there is no hook to stop it recording\n * them. The one seam every span (HttpClient's included) passes through is `Tracer.span()`\n * returning a `Tracer.Span`, so `wrapTracer` intercepts `Span.attribute()` there instead.\n * ⛔ NEVER RECORD HEADERS. A header NAME can itself be sensitive (`x-api-key`, a session cookie\n * name), so every `http.{request,response}.header.*` key is DROPPED, not value-redacted.\n * ⚠️ QUERY STRINGS ARE STRIPPED WHOLE, not redacted per-parameter: an allowlist of \"safe\" params\n * would silently leak the next secret param a provider adds. `url.query` is dropped outright and\n * `url.full`/`url.path` never carry a `?…` past this point.\n */\nimport type * as Tracer from 'effect/Tracer';\n\n/** Host or path segments to blank wherever they appear in a URL attribute — exact-segment match. */\nexport interface RedactionPolicy {\n /**\n * e.g. a UniFi console id embedded in a path (`/proxy/network/api/site/<id>/...`) or a private\n * hostname a consumer never wants leaving its own process. Compared against the full hostname and\n * against each `/`-separated path segment — never a substring match, so `\"db\"` cannot accidentally\n * blank `\"db1.example.test\"`.\n */\n readonly denylist?: ReadonlyArray<string>;\n}\n\nexport const REDACTED = '<redacted>';\n\n/** Returned by `redactAttribute` to mean \"drop this attribute entirely — do not forward it\". */\nexport const DROP: unique symbol = Symbol.for('homeflare/alchemy/telemetry/drop-attribute');\n\nconst HEADER_ATTRIBUTE = /^http\\.(?:request|response)\\.header\\./;\n\nconst redactSegments = (value: string, denylist: ReadonlySet<string>): string =>\n value\n .split('/')\n .map((segment) => (denylist.has(segment) ? REDACTED : segment))\n .join('/');\n\n/**\n * Applies `policy` to one span attribute. Returns `DROP` when the key must never be recorded at\n * all (every header); otherwise returns the value to record, with URLs stripped of their query\n * string and hash and their denylisted host/path segments blanked. A value this function does not\n * recognize (not a URL-shaped key, or not a string) passes through unchanged — alchemy's own\n * engine attributes (`resource.fqn`, `plan.action`, …) are never URLs and are never touched.\n */\nexport const redactAttribute = (key: string, value: unknown, policy: RedactionPolicy): unknown => {\n if (HEADER_ATTRIBUTE.test(key)) return DROP;\n if (key === 'url.query') return DROP;\n if (typeof value !== 'string') return value;\n const denylist = new Set(policy.denylist ?? []);\n if (denylist.size === 0 && key !== 'url.full' && key !== 'server.address') return value;\n // ⚠️ MEASURED 2026-09-26 (docs/telemetry-spike.md): effect's own `HttpClient.make` sets\n // `server.address` to `url.origin` (`http://host:port`), NOT a bare hostname — a denylist entry\n // naming just a hostname would never match the raw value, so this parses it out the same way\n // `url.full` does.\n // ⛔ FIX (redteam PR 302 MINOR item 5, 2026-09-26): the OTel semantic convention's own default for\n // `server.address` IS a bare hostname with no scheme (`new URL(value)` throws on that), and other\n // instrumentation could emit exactly that even though effect's `HttpClient` doesn't today —\n // `redactAttribute('server.address', 'db1.example.test', { denylist: ['db1.example.test'] })`\n // returned the value verbatim before this fallback. Compare the raw string directly when it\n // isn't a parseable URL, instead of giving up.\n if (key === 'server.address') {\n try {\n const url = new URL(value);\n return denylist.has(url.hostname) ? REDACTED : value;\n } catch {\n return denylist.has(value) ? REDACTED : value;\n }\n }\n if (key === 'url.path') return redactSegments(value, denylist);\n if (key === 'url.full') {\n try {\n const url = new URL(value);\n const host = denylist.has(url.hostname) ? REDACTED : url.host;\n return `${url.protocol}//${host}${redactSegments(url.pathname, denylist)}`;\n } catch {\n // Not a URL we can parse (a relative path effect couldn't resolve) — leave it verbatim\n // rather than guess; `url.full` is the one HttpClient always sets from a resolved `URL`, so\n // this branch is a defensive fallback, not the expected path.\n return value;\n }\n }\n return value;\n};\n\nconst redactAttributes = (\n attributes: Record<string, unknown>,\n policy: RedactionPolicy,\n): Record<string, unknown> => {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(attributes)) {\n const redacted = redactAttribute(key, value, policy);\n if (redacted !== DROP) out[key] = redacted;\n }\n return out;\n};\n\n/**\n * Wraps a `Tracer.Tracer` so every span it creates redacts its attributes and event attributes\n * through `redactAttribute`/`redactAttributes` before they reach the real span (and therefore\n * before OTLP serialization ever sees them). Everything else — timing, status, links, the span's\n * own identity — passes straight through to `inner`.\n */\nexport const wrapTracer = (inner: Tracer.Tracer, policy: RedactionPolicy): Tracer.Tracer => ({\n context: inner.context,\n span(options): Tracer.Span {\n const real = inner.span(options);\n return {\n _tag: 'Span',\n spanId: real.spanId,\n traceId: real.traceId,\n parent: real.parent,\n annotations: real.annotations,\n get name() {\n return real.name;\n },\n get status() {\n return real.status;\n },\n get attributes() {\n return real.attributes;\n },\n get links() {\n return real.links;\n },\n sampled: real.sampled,\n kind: real.kind,\n end: (endTime, exit) => {\n real.end(endTime, exit);\n },\n attribute: (key, value) => {\n const redacted = redactAttribute(key, value, policy);\n if (redacted !== DROP) real.attribute(key, redacted);\n },\n event: (name, startTime, attributes) => {\n real.event(name, startTime, attributes ? redactAttributes(attributes, policy) : attributes);\n },\n addLinks: (links) => {\n real.addLinks(links);\n },\n };\n },\n});\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "D5C073A7ECCA33DE64756E2164756E21",
11
+ "names": []
12
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * A Layer factory for OTLP tracing/logging/metrics, built for a stack's own `providers` Layer
3
+ * rather than the CLI-wide install alchemy's own `Telemetry/Layer.ts` does (`src/Telemetry/
4
+ * Layer.ts:13-15`, hard-coded to `otel.alchemy.run` with no override). Nothing here calls that
5
+ * endpoint or knows it exists.
6
+ *
7
+ * ⛔ NO DEFAULTS THAT POINT ANYWHERE. `endpoints` is `{}` by default and `telemetryLayer` resolves
8
+ * to `Layer.empty` when every entry is `undefined` — see layer.test.ts's own "nothing sent
9
+ * when unconfigured". A consumer wires actual hosts from ITS OWN site config; see docs/telemetry.md.
10
+ * ★ SEALED TRANSPORT — the caddy lesson (kit PR 293, admin.ts's own ⛔): merging a layer that still
11
+ * requires `HttpClient.HttpClient` into a stack's `providers` would let this module's own
12
+ * `FetchHttpClient` shadow a sibling fetch-based provider's client once both are merged into the
13
+ * same `Layer.mergeAll`. `Layer.provide` (not `Layer.provideMerge`) twice below feeds
14
+ * `FetchHttpClient`/`OtlpSerialization` to the OTLP layers and returns ONLY their own output —
15
+ * `HttpClient.HttpClient` never appears in what `telemetryLayer` hands back. See
16
+ * http-client-sealing.test.ts.
17
+ * ★ PER-SIGNAL URLS, NOT ONE BASE URL. effect's own `Otlp.layer`/`layerJson` take a single
18
+ * `baseUrl` and post to `<baseUrl>/v1/{traces,metrics,logs}` — fine when one collector serves all
19
+ * three, wrong for Victoria's split binaries (VictoriaTraces/Logs/Metrics on three different
20
+ * ports/paths — docs/telemetry.md has the exact paths). `OtlpTracer`/`OtlpLogger`/`OtlpMetrics`
21
+ * each take their own `url` and post to it verbatim (OtlpExporter.ts `make`: `HttpClientRequest.
22
+ * post(options.url, …)`, no suffix appended) — exactly the shape three independent endpoints need.
23
+ * ⛔ PROTOBUF, NOT JSON (redteam PR 302 CRITICAL item 1, 2026-09-26, measured against the pinned
24
+ * binaries in homeflare-mini's `src/obs`): VictoriaLogs 1.52.0 and VictoriaMetrics 1.151.0 both
25
+ * answer OTLP/HTTP JSON with `400 "json encoding isn't supported for opentelemetry format"` —
26
+ * effect's exporter treats a 400 as non-transient, drops the batch silently and disables itself for
27
+ * 60s at debug-only log level, so a JSON-encoded consumer sees traces (VictoriaTraces alone
28
+ * tolerates JSON) but never logs or metrics, with no visible error. `redactedSerialization` below
29
+ * provides `OtlpSerialization.layerProtobuf`, which all three ingested in the same measurement.
30
+ */
31
+ import type * as Duration from 'effect/Duration';
32
+ import * as Layer from 'effect/Layer';
33
+ import * as LogLevel from 'effect/LogLevel';
34
+ import { type RedactionPolicy } from './redact.ts';
35
+ /** Full OTLP/HTTP endpoint per signal — e.g. `http://victoria.example.test:10428/insert/opentelemetry/v1/traces`. Any entry left `undefined` sends nothing for that signal. */
36
+ export interface TelemetryEndpoints {
37
+ readonly traces?: string;
38
+ readonly logs?: string;
39
+ readonly metrics?: string;
40
+ }
41
+ export interface TelemetryOptions {
42
+ readonly endpoints: TelemetryEndpoints;
43
+ /** OTLP `service.name` — required, since an unnamed service in a shared Victoria instance is unusable. */
44
+ readonly serviceName: string;
45
+ readonly serviceVersion?: string;
46
+ /** Extra OTLP resource attributes (e.g. `{ "deployment.environment": "prod" }`). */
47
+ readonly resourceAttributes?: Record<string, unknown>;
48
+ readonly redaction?: RedactionPolicy;
49
+ /**
50
+ * How often a signal flushes its buffer.
51
+ * @default '1 second' — short on purpose: alchemy invocations are CLI runs, often shorter than a
52
+ * typical OTLP batch interval, so the export must fire well before the process could exit. The
53
+ * scope's own shutdown flush (`Exporter.layerFlusher`) is the backstop for whatever this interval
54
+ * misses; docs/telemetry-spike.md measured both paths.
55
+ */
56
+ readonly exportInterval?: Duration.Input;
57
+ /**
58
+ * Floor below which a log record never reaches the OTLP logger.
59
+ * @default 'Info' — redteam PR 302 IMPORTANT item 4: the `alchemy` CLI sets the GLOBAL minimum
60
+ * log level to Debug (`GlobalLog.ts`), and `OtlpLogger` has no floor of its own, so without one
61
+ * every Debug-level record (a provider's own connection-string/config dump, annotation noise)
62
+ * would ship — once `redactedSerialization` fixed item 1's silent-JSON-failure, they would
63
+ * actually start arriving. This floor applies ONLY to what THIS layer exports; a consumer's
64
+ * console/file loggers keep whatever level the CLI's own global setting gives them, since
65
+ * `logsMinimumLevel` filters after `Logger.layer`'s `mergeWithExisting: true` has already added
66
+ * this logger alongside them, not by lowering that global setting.
67
+ */
68
+ readonly logsMinimumLevel?: LogLevel.LogLevel;
69
+ }
70
+ /**
71
+ * The Layer a consumer stack merges into its own `providers` — see docs/telemetry.md for how a
72
+ * site file wires real hosts in, and docs/telemetry-spike.md for what a stack's `providers` Layer does and does
73
+ * not carry a span for.
74
+ *
75
+ * ⚠️ FIXED THREE-ARG `Layer.mergeAll`, NOT AN ARRAY `.reduce`. Every signal slot is filled — with
76
+ * the real per-signal layer when its endpoint is set, `Layer.empty` when it is not — instead of
77
+ * building a variable-length array and folding it with `Layer.merge`. `Layer<in ROut, …>`
78
+ * declares its success channel CONTRAVARIANT, and once a fold's accumulator needs an explicit
79
+ * annotation to type-check under this repo's `exactOptionalPropertyTypes`, TS's overload
80
+ * resolution for `Layer.provide`/`Layer.merge` starts inferring `unknown` for that channel and
81
+ * then fails `unknown` against `never` (measured 2026-09-26, TS2769). `Layer.mergeAll`'s own
82
+ * fixed-arity overload — the exact shape alchemy's own `Telemetry/Layer.ts` uses for its three
83
+ * signals — infers cleanly with no annotation at all.
84
+ */
85
+ export declare const telemetryLayer: (options: TelemetryOptions) => Layer.Layer<never>;
86
+ //# sourceMappingURL=layer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layer.d.ts","sourceRoot":"","sources":["../../src/telemetry/layer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAK5C,OAAO,EAAE,KAAK,eAAe,EAAc,MAAM,aAAa,CAAC;AAE/D,+KAA+K;AAC/K,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,0GAA0G;IAC1G,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,oFAAoF;IACpF,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtD,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IACzC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC;CAC/C;AA2CD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,YAAa,gBAAgB,KAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAiB3E,CAAC"}
@@ -0,0 +1,21 @@
1
+ import * as Layer from 'effect/Layer';
2
+ import { OtlpSerialization } from 'effect/unstable/observability';
3
+ import type { OtlpLogger, OtlpTracer } from 'effect/unstable/observability';
4
+ import { type RedactionPolicy } from './redact.ts';
5
+ /** Redacts one piece of free text: every embedded URL loses its query string and denylisted
6
+ * segments, then every denylist entry is blanked as a substring anywhere else in the text. */
7
+ export declare const scrubText: (text: string, policy: RedactionPolicy) => string;
8
+ /** `TraceData` after every span's name, attributes, events (including the `exception.*` ones
9
+ * `makeOtlpSpan` builds from the exit), status message and link attributes have been scrubbed. */
10
+ export declare const redactTraceData: (data: OtlpTracer.TraceData, policy: RedactionPolicy) => OtlpTracer.TraceData;
11
+ /** `LogsData` after every log record's attributes and body have been scrubbed — the fix for redteam
12
+ * item 4's "logs ship unredacted": `log.error` (`Cause.pretty`) and the message body are free text. */
13
+ export declare const redactLogsData: (data: OtlpLogger.LogsData, policy: RedactionPolicy) => OtlpLogger.LogsData;
14
+ /**
15
+ * Wraps the real `OtlpSerialization` so `telemetryLayer` gets this free-text pass on every signal
16
+ * with no separate wiring at each call site — `layer.ts` provides this in place of a bare
17
+ * `OtlpSerialization.layerProtobuf`. Metrics pass through untouched: they carry numeric data points
18
+ * keyed by `Metric.Metric.AttributeSet`, not provider-supplied free text.
19
+ */
20
+ export declare const redactedSerialization: (policy: RedactionPolicy) => Layer.Layer<OtlpSerialization.OtlpSerialization>;
21
+ //# sourceMappingURL=redact-serialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-serialization.d.ts","sourceRoot":"","sources":["../../src/telemetry/redact-serialization.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAgB,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC1F,OAAO,EAAY,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAoB7D;8FAC8F;AAC9F,eAAO,MAAM,SAAS,SAAU,MAAM,UAAU,eAAe,KAAG,MASjE,CAAC;AAoCF;kGACkG;AAClG,eAAO,MAAM,eAAe,SACpB,UAAU,CAAC,SAAS,UAClB,eAAe,KACtB,UAAU,CAAC,SAyBZ,CAAC;AAeH;uGACuG;AACvG,eAAO,MAAM,cAAc,SACnB,UAAU,CAAC,QAAQ,UACjB,eAAe,KACtB,UAAU,CAAC,QAYZ,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,WACxB,eAAe,KACtB,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAC,iBAAiB,CAQM,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * URL/header redaction for outgoing spans — applied at the `Tracer.Tracer` boundary so it holds for
3
+ * EVERY span source (a provider's own `Effect.withSpan`, effect's `HttpClient` client spans, and
4
+ * alchemy's own plan/apply/resource spans), not just the ones a caller remembers to annotate.
5
+ *
6
+ * ★ WHY THE TRACER, NOT AN HTTPCLIENT MIDDLEWARE. effect's `HttpClient.make` (src/unstable/http/
7
+ * HttpClient.ts) hard-codes `span.attribute("url.full", …)`, `"url.query"` and
8
+ * `http.request.header.<name>` on the span it creates — there is no hook to stop it recording
9
+ * them. The one seam every span (HttpClient's included) passes through is `Tracer.span()`
10
+ * returning a `Tracer.Span`, so `wrapTracer` intercepts `Span.attribute()` there instead.
11
+ * ⛔ NEVER RECORD HEADERS. A header NAME can itself be sensitive (`x-api-key`, a session cookie
12
+ * name), so every `http.{request,response}.header.*` key is DROPPED, not value-redacted.
13
+ * ⚠️ QUERY STRINGS ARE STRIPPED WHOLE, not redacted per-parameter: an allowlist of "safe" params
14
+ * would silently leak the next secret param a provider adds. `url.query` is dropped outright and
15
+ * `url.full`/`url.path` never carry a `?…` past this point.
16
+ */
17
+ import type * as Tracer from 'effect/Tracer';
18
+ /** Host or path segments to blank wherever they appear in a URL attribute — exact-segment match. */
19
+ export interface RedactionPolicy {
20
+ /**
21
+ * e.g. a UniFi console id embedded in a path (`/proxy/network/api/site/<id>/...`) or a private
22
+ * hostname a consumer never wants leaving its own process. Compared against the full hostname and
23
+ * against each `/`-separated path segment — never a substring match, so `"db"` cannot accidentally
24
+ * blank `"db1.example.test"`.
25
+ */
26
+ readonly denylist?: ReadonlyArray<string>;
27
+ }
28
+ export declare const REDACTED = "<redacted>";
29
+ /** Returned by `redactAttribute` to mean "drop this attribute entirely — do not forward it". */
30
+ export declare const DROP: unique symbol;
31
+ /**
32
+ * Applies `policy` to one span attribute. Returns `DROP` when the key must never be recorded at
33
+ * all (every header); otherwise returns the value to record, with URLs stripped of their query
34
+ * string and hash and their denylisted host/path segments blanked. A value this function does not
35
+ * recognize (not a URL-shaped key, or not a string) passes through unchanged — alchemy's own
36
+ * engine attributes (`resource.fqn`, `plan.action`, …) are never URLs and are never touched.
37
+ */
38
+ export declare const redactAttribute: (key: string, value: unknown, policy: RedactionPolicy) => unknown;
39
+ /**
40
+ * Wraps a `Tracer.Tracer` so every span it creates redacts its attributes and event attributes
41
+ * through `redactAttribute`/`redactAttributes` before they reach the real span (and therefore
42
+ * before OTLP serialization ever sees them). Everything else — timing, status, links, the span's
43
+ * own identity — passes straight through to `inner`.
44
+ */
45
+ export declare const wrapTracer: (inner: Tracer.Tracer, policy: RedactionPolicy) => Tracer.Tracer;
46
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/telemetry/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAC;AAE7C,oGAAoG;AACpG,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC3C;AAED,eAAO,MAAM,QAAQ,eAAe,CAAC;AAErC,gGAAgG;AAChG,eAAO,MAAM,IAAI,EAAE,OAAO,MAAiE,CAAC;AAU5F;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,QAAS,MAAM,SAAS,OAAO,UAAU,eAAe,KAAG,OAsCtF,CAAC;AAcF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,UAAW,MAAM,CAAC,MAAM,UAAU,eAAe,KAAG,MAAM,CAAC,MAuChF,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `Unifi.AclRule`'s `matches` AND `driftOf` (B6) — both built on the ONE field list below, the
3
+ * same `fieldDrift`-is-the-only-comparison shape `network-drift.ts`'s header explains (MEDIUM-4,
4
+ * red team, 2026-09-26): `matches(attrs, props) === (driftOf(live, props).length === 0)` is true
5
+ * BY CONSTRUCTION.
6
+ *
7
+ * ⚠️ `stripNullish: true` ON EVERY FIELD BY DEFAULT (`drift.ts`'s `defaultEqual`) — same "UniFi's
8
+ * JSON answers `null`, a declaration omits" tolerance `network-drift.ts` documents.
9
+ * ⛔ `enforcingDeviceFilter`/`protocolFilter` RUN THROUGH `acl-rule-form.ts`'s normalizers on BOTH
10
+ * sides before comparing — never applied to only `live` or only `declared` (that specific gap is
11
+ * what MEDIUM-4's own mutant found in `network-drift.ts`; see its header).
12
+ */
13
+ import type * as aclRules from '@distilled.cloud/unifi-network/access_control_acl_rules';
14
+ import { type AclRuleAttributes, type AclRuleProps } from './acl-rule-form.ts';
15
+ export declare const matches: (attributes: AclRuleAttributes, props: AclRuleProps) => boolean;
16
+ /**
17
+ * Per-field live-vs-declared drift straight from one live read — the task's own API
18
+ * (`driftOf(live, props)`), so `homeflare-network`'s pre-import drift check never has to derive
19
+ * `attributesOf` itself just to call this.
20
+ */
21
+ export declare const driftOf: (live: aclRules.ACLRule, props: AclRuleProps) => import("./drift.ts").FieldDrift[];
22
+ //# sourceMappingURL=acl-rule-drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acl-rule-drift.d.ts","sourceRoot":"","sources":["../../src/unifi/acl-rule-drift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,KAAK,QAAQ,MAAM,yDAAyD,CAAC;AAEzF,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAIlB,MAAM,oBAAoB,CAAC;AA2B5B,eAAO,MAAM,OAAO,eAAgB,iBAAiB,SAAS,YAAY,KAAG,OACjC,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,OAAO,SAAU,QAAQ,CAAC,OAAO,SAAS,YAAY,sCACrB,CAAC"}