@homeflare/alchemy 0.15.0 → 0.19.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 (512) hide show
  1. package/README.md +29 -0
  2. package/dist/linux/directory-lifecycle.d.ts +50 -0
  3. package/dist/linux/directory-lifecycle.d.ts.map +1 -0
  4. package/dist/linux/directory.d.ts +19 -0
  5. package/dist/linux/directory.d.ts.map +1 -0
  6. package/dist/linux/fake-linux-host.d.ts +35 -0
  7. package/dist/linux/fake-linux-host.d.ts.map +1 -0
  8. package/dist/linux/fake-systemd.d.ts +34 -0
  9. package/dist/linux/fake-systemd.d.ts.map +1 -0
  10. package/dist/linux/index.d.ts +27 -0
  11. package/dist/linux/index.d.ts.map +1 -0
  12. package/dist/linux/index.js +1554 -0
  13. package/dist/linux/index.js.map +37 -0
  14. package/dist/linux/providers.d.ts +17 -0
  15. package/dist/linux/providers.d.ts.map +1 -0
  16. package/dist/linux/region.d.ts +43 -0
  17. package/dist/linux/region.d.ts.map +1 -0
  18. package/dist/linux/remote-file-form.d.ts +47 -0
  19. package/dist/linux/remote-file-form.d.ts.map +1 -0
  20. package/dist/linux/remote-file-lifecycle.d.ts +36 -0
  21. package/dist/linux/remote-file-lifecycle.d.ts.map +1 -0
  22. package/dist/linux/remote-file-plan.d.ts +33 -0
  23. package/dist/linux/remote-file-plan.d.ts.map +1 -0
  24. package/dist/linux/remote-file.d.ts +28 -0
  25. package/dist/linux/remote-file.d.ts.map +1 -0
  26. package/dist/linux/ssh-command.d.ts +68 -0
  27. package/dist/linux/ssh-command.d.ts.map +1 -0
  28. package/dist/linux/ssh-runner.d.ts +22 -0
  29. package/dist/linux/ssh-runner.d.ts.map +1 -0
  30. package/dist/linux/ssh-scripts.d.ts +27 -0
  31. package/dist/linux/ssh-scripts.d.ts.map +1 -0
  32. package/dist/linux/systemctl.d.ts +67 -0
  33. package/dist/linux/systemctl.d.ts.map +1 -0
  34. package/dist/linux/unit-form.d.ts +78 -0
  35. package/dist/linux/unit-form.d.ts.map +1 -0
  36. package/dist/linux/unit-handlers.d.ts +15 -0
  37. package/dist/linux/unit-handlers.d.ts.map +1 -0
  38. package/dist/linux/unit-lifecycle.d.ts +44 -0
  39. package/dist/linux/unit-lifecycle.d.ts.map +1 -0
  40. package/dist/linux/unit-settle.d.ts +31 -0
  41. package/dist/linux/unit-settle.d.ts.map +1 -0
  42. package/dist/linux/unit.d.ts +35 -0
  43. package/dist/linux/unit.d.ts.map +1 -0
  44. package/dist/netbox/client.d.ts +52 -0
  45. package/dist/netbox/client.d.ts.map +1 -0
  46. package/dist/netbox/constraint-guard.d.ts +26 -0
  47. package/dist/netbox/constraint-guard.d.ts.map +1 -0
  48. package/dist/netbox/constraints.d.ts +76 -0
  49. package/dist/netbox/constraints.d.ts.map +1 -0
  50. package/dist/netbox/generated/constraints/index.d.ts +19 -0
  51. package/dist/netbox/generated/constraints/index.d.ts.map +1 -0
  52. package/dist/netbox/generated/constraints/netbox-ipam.d.ts +21 -0
  53. package/dist/netbox/generated/constraints/netbox-ipam.d.ts.map +1 -0
  54. package/dist/netbox/index.d.ts +17 -0
  55. package/dist/netbox/index.d.ts.map +1 -0
  56. package/dist/netbox/index.js +331 -0
  57. package/dist/netbox/index.js.map +18 -0
  58. package/dist/netbox/prefix-form.d.ts +54 -0
  59. package/dist/netbox/prefix-form.d.ts.map +1 -0
  60. package/dist/netbox/prefix.d.ts +80 -0
  61. package/dist/netbox/prefix.d.ts.map +1 -0
  62. package/dist/netbox/resource.d.ts +94 -0
  63. package/dist/netbox/resource.d.ts.map +1 -0
  64. package/dist/netbox/values.d.ts +24 -0
  65. package/dist/netbox/values.d.ts.map +1 -0
  66. package/dist/proxmox/api-token-form.d.ts.map +1 -1
  67. package/dist/proxmox/apply-endpoints.d.ts +30 -0
  68. package/dist/proxmox/apply-endpoints.d.ts.map +1 -0
  69. package/dist/proxmox/ceph-daemon-form.d.ts +29 -1
  70. package/dist/proxmox/ceph-daemon-form.d.ts.map +1 -1
  71. package/dist/proxmox/ceph-daemon.d.ts.map +1 -1
  72. package/dist/proxmox/ceph-flag.d.ts.map +1 -1
  73. package/dist/proxmox/ceph-fs.d.ts.map +1 -1
  74. package/dist/proxmox/ceph-osd-write.d.ts +19 -0
  75. package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
  76. package/dist/proxmox/ceph-pool.d.ts.map +1 -1
  77. package/dist/proxmox/constraints.d.ts +28 -5
  78. package/dist/proxmox/constraints.d.ts.map +1 -1
  79. package/dist/proxmox/generated/constraints/index.d.ts +1 -1
  80. package/dist/proxmox/generated/constraints/index.d.ts.map +1 -1
  81. package/dist/proxmox/generated/constraints/pbs-config.d.ts +6 -1
  82. package/dist/proxmox/generated/constraints/pbs-config.d.ts.map +1 -1
  83. package/dist/proxmox/generated/constraints/pve-access.d.ts +6 -1
  84. package/dist/proxmox/generated/constraints/pve-access.d.ts.map +1 -1
  85. package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +23 -0
  86. package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts.map +1 -0
  87. package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +23 -0
  88. package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts.map +1 -0
  89. package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +23 -0
  90. package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts.map +1 -0
  91. package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +23 -0
  92. package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts.map +1 -0
  93. package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +23 -0
  94. package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts.map +1 -0
  95. package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +23 -0
  96. package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts.map +1 -0
  97. package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +23 -0
  98. package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts.map +1 -0
  99. package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +23 -0
  100. package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts.map +1 -0
  101. package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +23 -0
  102. package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts.map +1 -0
  103. package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +23 -0
  104. package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts.map +1 -0
  105. package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +23 -0
  106. package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts.map +1 -0
  107. package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +23 -0
  108. package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts.map +1 -0
  109. package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +23 -0
  110. package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts.map +1 -0
  111. package/dist/proxmox/generated/constraints/pve-pools.d.ts +6 -1
  112. package/dist/proxmox/generated/constraints/pve-pools.d.ts.map +1 -1
  113. package/dist/proxmox/generated/constraints/pve-storage.d.ts +6 -1
  114. package/dist/proxmox/generated/constraints/pve-storage.d.ts.map +1 -1
  115. package/dist/proxmox/generated/pbs/access-users.d.ts +137 -0
  116. package/dist/proxmox/generated/pbs/access-users.d.ts.map +1 -0
  117. package/dist/proxmox/generated/pbs/access.d.ts +171 -0
  118. package/dist/proxmox/generated/pbs/access.d.ts.map +1 -0
  119. package/dist/proxmox/generated/pbs/admin-datastore-store.d.ts +252 -0
  120. package/dist/proxmox/generated/pbs/admin-datastore-store.d.ts.map +1 -0
  121. package/dist/proxmox/generated/pbs/admin-metrics.d.ts +147 -0
  122. package/dist/proxmox/generated/pbs/admin-metrics.d.ts.map +1 -0
  123. package/dist/proxmox/generated/pbs/admin.d.ts +217 -0
  124. package/dist/proxmox/generated/pbs/admin.d.ts.map +1 -0
  125. package/dist/proxmox/generated/pbs/backup.d.ts +114 -0
  126. package/dist/proxmox/generated/pbs/backup.d.ts.map +1 -0
  127. package/dist/proxmox/generated/pbs/config-access-pam.d.ts +218 -0
  128. package/dist/proxmox/generated/pbs/config-access-pam.d.ts.map +1 -0
  129. package/dist/proxmox/generated/pbs/config-access.d.ts +203 -0
  130. package/dist/proxmox/generated/pbs/config-access.d.ts.map +1 -0
  131. package/dist/proxmox/generated/pbs/config-datastore.d.ts +195 -0
  132. package/dist/proxmox/generated/pbs/config-datastore.d.ts.map +1 -0
  133. package/dist/proxmox/generated/pbs/config-media-pool.d.ts +207 -0
  134. package/dist/proxmox/generated/pbs/config-media-pool.d.ts.map +1 -0
  135. package/dist/proxmox/generated/pbs/config-notifications-matchers.d.ts +159 -0
  136. package/dist/proxmox/generated/pbs/config-notifications-matchers.d.ts.map +1 -0
  137. package/dist/proxmox/generated/pbs/config-notifications.d.ts +199 -0
  138. package/dist/proxmox/generated/pbs/config-notifications.d.ts.map +1 -0
  139. package/dist/proxmox/generated/pbs/config-remote.d.ts +175 -0
  140. package/dist/proxmox/generated/pbs/config-remote.d.ts.map +1 -0
  141. package/dist/proxmox/generated/pbs/config-sync.d.ts +145 -0
  142. package/dist/proxmox/generated/pbs/config-sync.d.ts.map +1 -0
  143. package/dist/proxmox/generated/pbs/config-tape.d.ts +200 -0
  144. package/dist/proxmox/generated/pbs/config-tape.d.ts.map +1 -0
  145. package/dist/proxmox/generated/pbs/config-verify.d.ts +77 -0
  146. package/dist/proxmox/generated/pbs/config-verify.d.ts.map +1 -0
  147. package/dist/proxmox/generated/pbs/config.d.ts +119 -0
  148. package/dist/proxmox/generated/pbs/config.d.ts.map +1 -0
  149. package/dist/proxmox/generated/pbs/nodes-node-dns.d.ts +178 -0
  150. package/dist/proxmox/generated/pbs/nodes-node-dns.d.ts.map +1 -0
  151. package/dist/proxmox/generated/pbs/nodes-node-services.d.ts +205 -0
  152. package/dist/proxmox/generated/pbs/nodes-node-services.d.ts.map +1 -0
  153. package/dist/proxmox/generated/pbs/nodes-node.d.ts +166 -0
  154. package/dist/proxmox/generated/pbs/nodes-node.d.ts.map +1 -0
  155. package/dist/proxmox/generated/pbs/nodes.d.ts +171 -0
  156. package/dist/proxmox/generated/pbs/nodes.d.ts.map +1 -0
  157. package/dist/proxmox/generated/pbs/ping.d.ts +18 -0
  158. package/dist/proxmox/generated/pbs/ping.d.ts.map +1 -0
  159. package/dist/proxmox/generated/pbs/pull.d.ts +37 -0
  160. package/dist/proxmox/generated/pbs/pull.d.ts.map +1 -0
  161. package/dist/proxmox/generated/pbs/push.d.ts +36 -0
  162. package/dist/proxmox/generated/pbs/push.d.ts.map +1 -0
  163. package/dist/proxmox/generated/pbs/reader.d.ts +41 -0
  164. package/dist/proxmox/generated/pbs/reader.d.ts.map +1 -0
  165. package/dist/proxmox/generated/pbs/root.d.ts +16 -0
  166. package/dist/proxmox/generated/pbs/root.d.ts.map +1 -0
  167. package/dist/proxmox/generated/pbs/status.d.ts +54 -0
  168. package/dist/proxmox/generated/pbs/status.d.ts.map +1 -0
  169. package/dist/proxmox/generated/pbs/tape-drive.d.ts +181 -0
  170. package/dist/proxmox/generated/pbs/tape-drive.d.ts.map +1 -0
  171. package/dist/proxmox/generated/pbs/tape-media.d.ts +126 -0
  172. package/dist/proxmox/generated/pbs/tape-media.d.ts.map +1 -0
  173. package/dist/proxmox/generated/pbs/tape.d.ts +90 -0
  174. package/dist/proxmox/generated/pbs/tape.d.ts.map +1 -0
  175. package/dist/proxmox/generated/pbs/version.d.ts +20 -0
  176. package/dist/proxmox/generated/pbs/version.d.ts.map +1 -0
  177. package/dist/proxmox/generated/pbs.d.ts +44 -662
  178. package/dist/proxmox/generated/pbs.d.ts.map +1 -1
  179. package/dist/proxmox/generated/pve/access-roles.d.ts +164 -0
  180. package/dist/proxmox/generated/pve/access-roles.d.ts.map +1 -0
  181. package/dist/proxmox/generated/pve/access-users.d.ts +152 -0
  182. package/dist/proxmox/generated/pve/access-users.d.ts.map +1 -0
  183. package/dist/proxmox/generated/pve/access.d.ts +221 -0
  184. package/dist/proxmox/generated/pve/access.d.ts.map +1 -0
  185. package/dist/proxmox/generated/pve/cluster-acme-plugins-id.d.ts +45 -0
  186. package/dist/proxmox/generated/pve/cluster-acme-plugins-id.d.ts.map +1 -0
  187. package/dist/proxmox/generated/pve/cluster-acme-plugins.d.ts +47 -0
  188. package/dist/proxmox/generated/pve/cluster-acme-plugins.d.ts.map +1 -0
  189. package/dist/proxmox/generated/pve/cluster-acme-tos.d.ts +240 -0
  190. package/dist/proxmox/generated/pve/cluster-acme-tos.d.ts.map +1 -0
  191. package/dist/proxmox/generated/pve/cluster-bulk-action.d.ts +200 -0
  192. package/dist/proxmox/generated/pve/cluster-bulk-action.d.ts.map +1 -0
  193. package/dist/proxmox/generated/pve/cluster-firewall-refs.d.ts +109 -0
  194. package/dist/proxmox/generated/pve/cluster-firewall-refs.d.ts.map +1 -0
  195. package/dist/proxmox/generated/pve/cluster-firewall.d.ts +225 -0
  196. package/dist/proxmox/generated/pve/cluster-firewall.d.ts.map +1 -0
  197. package/dist/proxmox/generated/pve/cluster-ha.d.ts +199 -0
  198. package/dist/proxmox/generated/pve/cluster-ha.d.ts.map +1 -0
  199. package/dist/proxmox/generated/pve/cluster-jobs.d.ts +183 -0
  200. package/dist/proxmox/generated/pve/cluster-jobs.d.ts.map +1 -0
  201. package/dist/proxmox/generated/pve/cluster-metrics.d.ts +110 -0
  202. package/dist/proxmox/generated/pve/cluster-metrics.d.ts.map +1 -0
  203. package/dist/proxmox/generated/pve/cluster-notifications-endpoints.d.ts +224 -0
  204. package/dist/proxmox/generated/pve/cluster-notifications-endpoints.d.ts.map +1 -0
  205. package/dist/proxmox/generated/pve/cluster-notifications.d.ts +91 -0
  206. package/dist/proxmox/generated/pve/cluster-notifications.d.ts.map +1 -0
  207. package/dist/proxmox/generated/pve/cluster-options.d.ts +85 -0
  208. package/dist/proxmox/generated/pve/cluster-options.d.ts.map +1 -0
  209. package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.d.ts +48 -0
  210. package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.d.ts.map +1 -0
  211. package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models.d.ts +45 -0
  212. package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models.d.ts.map +1 -0
  213. package/dist/proxmox/generated/pve/cluster-replication.d.ts +130 -0
  214. package/dist/proxmox/generated/pve/cluster-replication.d.ts.map +1 -0
  215. package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts +220 -0
  216. package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts.map +1 -0
  217. package/dist/proxmox/generated/pve/cluster-sdn-ipams.d.ts +229 -0
  218. package/dist/proxmox/generated/pve/cluster-sdn-ipams.d.ts.map +1 -0
  219. package/dist/proxmox/generated/pve/cluster-sdn-vnets-vnet.d.ts +229 -0
  220. package/dist/proxmox/generated/pve/cluster-sdn-vnets-vnet.d.ts.map +1 -0
  221. package/dist/proxmox/generated/pve/cluster-sdn-vnets.d.ts +51 -0
  222. package/dist/proxmox/generated/pve/cluster-sdn-vnets.d.ts.map +1 -0
  223. package/dist/proxmox/generated/pve/cluster-sdn-zones.d.ts +225 -0
  224. package/dist/proxmox/generated/pve/cluster-sdn-zones.d.ts.map +1 -0
  225. package/dist/proxmox/generated/pve/cluster-sdn.d.ts +202 -0
  226. package/dist/proxmox/generated/pve/cluster-sdn.d.ts.map +1 -0
  227. package/dist/proxmox/generated/pve/cluster.d.ts +69 -0
  228. package/dist/proxmox/generated/pve/cluster.d.ts.map +1 -0
  229. package/dist/proxmox/generated/pve/nodes-node-ceph.d.ts +167 -0
  230. package/dist/proxmox/generated/pve/nodes-node-ceph.d.ts.map +1 -0
  231. package/dist/proxmox/generated/pve/nodes-node-config.d.ts +43 -0
  232. package/dist/proxmox/generated/pve/nodes-node-config.d.ts.map +1 -0
  233. package/dist/proxmox/generated/pve/nodes-node-disks.d.ts +225 -0
  234. package/dist/proxmox/generated/pve/nodes-node-disks.d.ts.map +1 -0
  235. package/dist/proxmox/generated/pve/nodes-node-firewall.d.ts +145 -0
  236. package/dist/proxmox/generated/pve/nodes-node-firewall.d.ts.map +1 -0
  237. package/dist/proxmox/generated/pve/nodes-node-hardware.d.ts +195 -0
  238. package/dist/proxmox/generated/pve/nodes-node-hardware.d.ts.map +1 -0
  239. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.d.ts +191 -0
  240. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.d.ts.map +1 -0
  241. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.d.ts +33 -0
  242. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.d.ts.map +1 -0
  243. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.d.ts +56 -0
  244. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.d.ts.map +1 -0
  245. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-resize.d.ts +28 -0
  246. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-resize.d.ts.map +1 -0
  247. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.d.ts +186 -0
  248. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.d.ts.map +1 -0
  249. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid.d.ts +191 -0
  250. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid.d.ts.map +1 -0
  251. package/dist/proxmox/generated/pve/nodes-node-network.d.ts +150 -0
  252. package/dist/proxmox/generated/pve/nodes-node-network.d.ts.map +1 -0
  253. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-config.d.ts +301 -0
  254. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-config.d.ts.map +1 -0
  255. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.d.ts +170 -0
  256. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.d.ts.map +1 -0
  257. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.d.ts +144 -0
  258. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.d.ts.map +1 -0
  259. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.d.ts +34 -0
  260. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.d.ts.map +1 -0
  261. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.d.ts +137 -0
  262. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.d.ts.map +1 -0
  263. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-status.d.ts +179 -0
  264. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-status.d.ts.map +1 -0
  265. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid.d.ts +143 -0
  266. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid.d.ts.map +1 -0
  267. package/dist/proxmox/generated/pve/nodes-node-qemu.d.ts +158 -0
  268. package/dist/proxmox/generated/pve/nodes-node-qemu.d.ts.map +1 -0
  269. package/dist/proxmox/generated/pve/nodes-node-replication.d.ts +187 -0
  270. package/dist/proxmox/generated/pve/nodes-node-replication.d.ts.map +1 -0
  271. package/dist/proxmox/generated/pve/nodes-node-services.d.ts +115 -0
  272. package/dist/proxmox/generated/pve/nodes-node-services.d.ts.map +1 -0
  273. package/dist/proxmox/generated/pve/nodes-node-storage-storage.d.ts +202 -0
  274. package/dist/proxmox/generated/pve/nodes-node-storage-storage.d.ts.map +1 -0
  275. package/dist/proxmox/generated/pve/nodes-node-storage.d.ts +40 -0
  276. package/dist/proxmox/generated/pve/nodes-node-storage.d.ts.map +1 -0
  277. package/dist/proxmox/generated/pve/nodes-node-subscription.d.ts +173 -0
  278. package/dist/proxmox/generated/pve/nodes-node-subscription.d.ts.map +1 -0
  279. package/dist/proxmox/generated/pve/nodes-node-vzdump.d.ts +97 -0
  280. package/dist/proxmox/generated/pve/nodes-node-vzdump.d.ts.map +1 -0
  281. package/dist/proxmox/generated/pve/nodes-node.d.ts +207 -0
  282. package/dist/proxmox/generated/pve/nodes-node.d.ts.map +1 -0
  283. package/dist/proxmox/generated/pve/nodes.d.ts +236 -0
  284. package/dist/proxmox/generated/pve/nodes.d.ts.map +1 -0
  285. package/dist/proxmox/generated/pve/pools.d.ts +82 -0
  286. package/dist/proxmox/generated/pve/pools.d.ts.map +1 -0
  287. package/dist/proxmox/generated/pve/storage.d.ts +159 -0
  288. package/dist/proxmox/generated/pve/storage.d.ts.map +1 -0
  289. package/dist/proxmox/generated/pve/version.d.ts +21 -0
  290. package/dist/proxmox/generated/pve/version.d.ts.map +1 -0
  291. package/dist/proxmox/generated/pve.d.ts +69 -4766
  292. package/dist/proxmox/generated/pve.d.ts.map +1 -1
  293. package/dist/proxmox/index.js +996 -372
  294. package/dist/proxmox/index.js.map +48 -32
  295. package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -1
  296. package/dist/proxmox/metric-server-form.d.ts +11 -0
  297. package/dist/proxmox/metric-server-form.d.ts.map +1 -1
  298. package/dist/proxmox/metric-server.d.ts.map +1 -1
  299. package/dist/proxmox/network-apply.d.ts.map +1 -1
  300. package/dist/proxmox/node-network-form.d.ts +15 -0
  301. package/dist/proxmox/node-network-form.d.ts.map +1 -1
  302. package/dist/proxmox/node-network.d.ts.map +1 -1
  303. package/dist/proxmox/notification-target-endpoint.d.ts +26 -0
  304. package/dist/proxmox/notification-target-endpoint.d.ts.map +1 -0
  305. package/dist/proxmox/notification-target-form.d.ts +7 -0
  306. package/dist/proxmox/notification-target-form.d.ts.map +1 -1
  307. package/dist/proxmox/notification-target.d.ts.map +1 -1
  308. package/dist/proxmox/pbs-notification-target-endpoint.d.ts +29 -0
  309. package/dist/proxmox/pbs-notification-target-endpoint.d.ts.map +1 -0
  310. package/dist/proxmox/pbs-notification-target-lifecycle.d.ts.map +1 -1
  311. package/dist/proxmox/qemu.d.ts +10 -0
  312. package/dist/proxmox/qemu.d.ts.map +1 -1
  313. package/dist/proxmox/resource-guard.d.ts +15 -0
  314. package/dist/proxmox/resource-guard.d.ts.map +1 -0
  315. package/dist/proxmox/resource-spec.d.ts +15 -4
  316. package/dist/proxmox/resource-spec.d.ts.map +1 -1
  317. package/dist/proxmox/resource.d.ts +8 -0
  318. package/dist/proxmox/resource.d.ts.map +1 -1
  319. package/dist/proxmox/sdn-apply.d.ts.map +1 -1
  320. package/dist/proxmox/sdn-subnet.d.ts.map +1 -1
  321. package/dist/proxmox/zfs-pool-write.d.ts +11 -0
  322. package/dist/proxmox/zfs-pool-write.d.ts.map +1 -1
  323. package/dist/proxmox/zfs-pool.d.ts.map +1 -1
  324. package/docs/linux-host-measured.md +56 -0
  325. package/docs/linux-host.md +186 -0
  326. package/docs/netbox.md +103 -0
  327. package/package.json +9 -1
  328. package/src/linux/directory-lifecycle.ts +220 -0
  329. package/src/linux/directory.test.ts +132 -0
  330. package/src/linux/directory.ts +71 -0
  331. package/src/linux/fake-linux-host.ts +197 -0
  332. package/src/linux/fake-systemd.ts +103 -0
  333. package/src/linux/index.ts +32 -0
  334. package/src/linux/providers.ts +26 -0
  335. package/src/linux/region.test.ts +89 -0
  336. package/src/linux/region.ts +118 -0
  337. package/src/linux/remote-file-form.ts +96 -0
  338. package/src/linux/remote-file-lifecycle.test.ts +191 -0
  339. package/src/linux/remote-file-lifecycle.ts +161 -0
  340. package/src/linux/remote-file-plan.ts +154 -0
  341. package/src/linux/remote-file.ts +88 -0
  342. package/src/linux/ssh-command.test.ts +140 -0
  343. package/src/linux/ssh-command.ts +99 -0
  344. package/src/linux/ssh-runner.ts +188 -0
  345. package/src/linux/ssh-scripts.ts +105 -0
  346. package/src/linux/systemctl.ts +145 -0
  347. package/src/linux/unit-form.ts +183 -0
  348. package/src/linux/unit-handlers.ts +73 -0
  349. package/src/linux/unit-lifecycle.test.ts +220 -0
  350. package/src/linux/unit-lifecycle.ts +193 -0
  351. package/src/linux/unit-settle.ts +110 -0
  352. package/src/linux/unit.ts +81 -0
  353. package/src/netbox/client.test.ts +67 -0
  354. package/src/netbox/client.ts +146 -0
  355. package/src/netbox/constraint-guard.ts +57 -0
  356. package/src/netbox/constraints.test.ts +114 -0
  357. package/src/netbox/constraints.ts +152 -0
  358. package/src/netbox/generated/constraints/index.ts +23 -0
  359. package/src/netbox/generated/constraints/netbox-ipam.ts +32 -0
  360. package/src/netbox/index.ts +34 -0
  361. package/src/netbox/prefix-form.test.ts +127 -0
  362. package/src/netbox/prefix-form.ts +84 -0
  363. package/src/netbox/prefix.ts +136 -0
  364. package/src/netbox/resource.ts +208 -0
  365. package/src/netbox/values.test.ts +66 -0
  366. package/src/netbox/values.ts +40 -0
  367. package/src/proxmox/api-token-form.ts +5 -0
  368. package/src/proxmox/apply-endpoints.ts +34 -0
  369. package/src/proxmox/ceph-daemon-form.ts +56 -2
  370. package/src/proxmox/ceph-daemon.ts +10 -21
  371. package/src/proxmox/ceph-flag.ts +1 -0
  372. package/src/proxmox/ceph-fs.ts +1 -0
  373. package/src/proxmox/ceph-osd-write.ts +30 -4
  374. package/src/proxmox/ceph-pool.ts +10 -0
  375. package/src/proxmox/constraints-dialect.test.ts +165 -0
  376. package/src/proxmox/constraints-forms.test.ts +220 -0
  377. package/src/proxmox/constraints-host-forms.test.ts +132 -0
  378. package/src/proxmox/constraints-live.test.ts +155 -0
  379. package/src/proxmox/constraints-node-forms.test.ts +188 -0
  380. package/src/proxmox/constraints-required.test.ts +172 -0
  381. package/src/proxmox/constraints.test.ts +8 -63
  382. package/src/proxmox/constraints.ts +31 -6
  383. package/src/proxmox/generated/constraints/index.ts +27 -3
  384. package/src/proxmox/generated/constraints/pbs-config.ts +63 -1
  385. package/src/proxmox/generated/constraints/pve-access.ts +15 -3
  386. package/src/proxmox/generated/constraints/pve-cluster-backup.ts +74 -0
  387. package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +27 -0
  388. package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +35 -0
  389. package/src/proxmox/generated/constraints/pve-cluster-ha.ts +55 -0
  390. package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +59 -0
  391. package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +80 -0
  392. package/src/proxmox/generated/constraints/pve-cluster-replication.ts +43 -0
  393. package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +90 -0
  394. package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +57 -0
  395. package/src/proxmox/generated/constraints/pve-nodes-disks.ts +31 -0
  396. package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +82 -0
  397. package/src/proxmox/generated/constraints/pve-nodes-network.ts +77 -0
  398. package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +125 -0
  399. package/src/proxmox/generated/constraints/pve-pools.ts +6 -1
  400. package/src/proxmox/generated/constraints/pve-storage.ts +8 -3
  401. package/src/proxmox/generated/pbs/access-users.ts +137 -0
  402. package/src/proxmox/generated/pbs/access.ts +232 -0
  403. package/src/proxmox/generated/pbs/admin-datastore-store.ts +238 -0
  404. package/src/proxmox/generated/pbs/admin-metrics.ts +147 -0
  405. package/src/proxmox/generated/pbs/admin.ts +225 -0
  406. package/src/proxmox/generated/pbs/backup.ts +121 -0
  407. package/src/proxmox/generated/pbs/config-access-pam.ts +228 -0
  408. package/src/proxmox/generated/pbs/config-access.ts +228 -0
  409. package/src/proxmox/generated/pbs/config-datastore.ts +217 -0
  410. package/src/proxmox/generated/pbs/config-media-pool.ts +232 -0
  411. package/src/proxmox/generated/pbs/config-notifications-matchers.ts +187 -0
  412. package/src/proxmox/generated/pbs/config-notifications.ts +221 -0
  413. package/src/proxmox/generated/pbs/config-remote.ts +192 -0
  414. package/src/proxmox/generated/pbs/config-sync.ts +167 -0
  415. package/src/proxmox/generated/pbs/config-tape.ts +226 -0
  416. package/src/proxmox/generated/pbs/config-verify.ts +87 -0
  417. package/src/proxmox/generated/pbs/config.ts +135 -0
  418. package/src/proxmox/generated/pbs/nodes-node-dns.ts +229 -0
  419. package/src/proxmox/generated/pbs/nodes-node-services.ts +181 -0
  420. package/src/proxmox/generated/pbs/nodes-node.ts +244 -0
  421. package/src/proxmox/generated/pbs/nodes.ts +160 -0
  422. package/src/proxmox/generated/pbs/ping.ts +16 -0
  423. package/src/proxmox/generated/pbs/pull.ts +37 -0
  424. package/src/proxmox/generated/pbs/push.ts +36 -0
  425. package/src/proxmox/generated/pbs/reader.ts +41 -0
  426. package/src/proxmox/generated/pbs/root.ts +16 -0
  427. package/src/proxmox/generated/pbs/status.ts +50 -0
  428. package/src/proxmox/generated/pbs/tape-drive.ts +220 -0
  429. package/src/proxmox/generated/pbs/tape-media.ts +133 -0
  430. package/src/proxmox/generated/pbs/tape.ts +92 -0
  431. package/src/proxmox/generated/pbs/version.ts +16 -0
  432. package/src/proxmox/generated/pbs.ts +44 -787
  433. package/src/proxmox/generated/pve/access-roles.ts +168 -0
  434. package/src/proxmox/generated/pve/access-users.ts +155 -0
  435. package/src/proxmox/generated/pve/access.ts +217 -0
  436. package/src/proxmox/generated/pve/cluster-acme-plugins-id.ts +367 -0
  437. package/src/proxmox/generated/pve/cluster-acme-plugins.ts +366 -0
  438. package/src/proxmox/generated/pve/cluster-acme-tos.ts +232 -0
  439. package/src/proxmox/generated/pve/cluster-bulk-action.ts +227 -0
  440. package/src/proxmox/generated/pve/cluster-firewall-refs.ts +110 -0
  441. package/src/proxmox/generated/pve/cluster-firewall.ts +236 -0
  442. package/src/proxmox/generated/pve/cluster-ha.ts +203 -0
  443. package/src/proxmox/generated/pve/cluster-jobs.ts +190 -0
  444. package/src/proxmox/generated/pve/cluster-metrics.ts +116 -0
  445. package/src/proxmox/generated/pve/cluster-notifications-endpoints.ts +244 -0
  446. package/src/proxmox/generated/pve/cluster-notifications.ts +99 -0
  447. package/src/proxmox/generated/pve/cluster-options.ts +189 -0
  448. package/src/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.ts +272 -0
  449. package/src/proxmox/generated/pve/cluster-qemu-custom-cpu-models.ts +268 -0
  450. package/src/proxmox/generated/pve/cluster-replication.ts +127 -0
  451. package/src/proxmox/generated/pve/cluster-sdn-fabrics.ts +218 -0
  452. package/src/proxmox/generated/pve/cluster-sdn-ipams.ts +229 -0
  453. package/src/proxmox/generated/pve/cluster-sdn-vnets-vnet.ts +245 -0
  454. package/src/proxmox/generated/pve/cluster-sdn-vnets.ts +49 -0
  455. package/src/proxmox/generated/pve/cluster-sdn-zones.ts +224 -0
  456. package/src/proxmox/generated/pve/cluster-sdn.ts +201 -0
  457. package/src/proxmox/generated/pve/cluster.ts +71 -0
  458. package/src/proxmox/generated/pve/nodes-node-ceph.ts +167 -0
  459. package/src/proxmox/generated/pve/nodes-node-config.ts +56 -0
  460. package/src/proxmox/generated/pve/nodes-node-disks.ts +237 -0
  461. package/src/proxmox/generated/pve/nodes-node-firewall.ts +239 -0
  462. package/src/proxmox/generated/pve/nodes-node-hardware.ts +215 -0
  463. package/src/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.ts +232 -0
  464. package/src/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.ts +1059 -0
  465. package/src/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.ts +49 -0
  466. package/src/proxmox/generated/pve/nodes-node-lxc-vmid-resize.ts +285 -0
  467. package/src/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.ts +184 -0
  468. package/src/proxmox/generated/pve/nodes-node-lxc-vmid.ts +240 -0
  469. package/src/proxmox/generated/pve/nodes-node-network.ts +234 -0
  470. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-config.ts +441 -0
  471. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.ts +215 -0
  472. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.ts +145 -0
  473. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.ts +664 -0
  474. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.ts +188 -0
  475. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-status.ts +233 -0
  476. package/src/proxmox/generated/pve/nodes-node-qemu-vmid.ts +187 -0
  477. package/src/proxmox/generated/pve/nodes-node-qemu.ts +204 -0
  478. package/src/proxmox/generated/pve/nodes-node-replication.ts +187 -0
  479. package/src/proxmox/generated/pve/nodes-node-services.ts +221 -0
  480. package/src/proxmox/generated/pve/nodes-node-storage-storage.ts +226 -0
  481. package/src/proxmox/generated/pve/nodes-node-storage.ts +40 -0
  482. package/src/proxmox/generated/pve/nodes-node-subscription.ts +168 -0
  483. package/src/proxmox/generated/pve/nodes-node-vzdump.ts +96 -0
  484. package/src/proxmox/generated/pve/nodes-node.ts +211 -0
  485. package/src/proxmox/generated/pve/nodes.ts +235 -0
  486. package/src/proxmox/generated/pve/pools.ts +78 -0
  487. package/src/proxmox/generated/pve/storage.ts +213 -0
  488. package/src/proxmox/generated/pve/version.ts +21 -0
  489. package/src/proxmox/generated/pve.ts +69 -7403
  490. package/src/proxmox/lxc-interior.test.ts +17 -1
  491. package/src/proxmox/lxc-lifecycle.ts +48 -9
  492. package/src/proxmox/metric-server-form.ts +10 -0
  493. package/src/proxmox/metric-server.ts +11 -2
  494. package/src/proxmox/network-apply.ts +4 -0
  495. package/src/proxmox/node-network-form.ts +19 -0
  496. package/src/proxmox/node-network.ts +9 -3
  497. package/src/proxmox/notification-target-endpoint.ts +46 -0
  498. package/src/proxmox/notification-target-form.ts +11 -0
  499. package/src/proxmox/notification-target.ts +5 -2
  500. package/src/proxmox/pbs-notification-target-endpoint.ts +45 -0
  501. package/src/proxmox/pbs-notification-target-lifecycle.ts +16 -14
  502. package/src/proxmox/qemu.ts +9 -1
  503. package/src/proxmox/resource-guard.ts +61 -0
  504. package/src/proxmox/resource-spec.ts +16 -4
  505. package/src/proxmox/resource.ts +27 -27
  506. package/src/proxmox/sdn-apply.ts +4 -0
  507. package/src/proxmox/sdn-subnet.ts +5 -0
  508. package/src/proxmox/zfs-pool-write.ts +12 -0
  509. package/src/proxmox/zfs-pool.ts +2 -1
  510. package/dist/proxmox/generated/constraints/pve-cluster.d.ts +0 -18
  511. package/dist/proxmox/generated/constraints/pve-cluster.d.ts.map +0 -1
  512. package/src/proxmox/generated/constraints/pve-cluster.ts +0 -174
@@ -15,9 +15,34 @@ import * as Effect from 'effect/Effect';
15
15
  import { readOsd } from './ceph-osd-tree.ts';
16
16
  import type { CephOsdProps } from './ceph-osd.ts';
17
17
  import { pve } from './client.ts';
18
+ import { guardForm } from './constraint-guard.ts';
19
+ import type { EndpointKey } from './constraints.ts';
20
+
21
+ /**
22
+ * The vendor rules the create form is checked against at plan time.
23
+ *
24
+ * ⛔ DECLARED HERE RATHER THAN ON A SPEC BECAUSE THIS FAMILY HAS NO SPEC — `ceph-osd.ts` writes
25
+ * all five handlers by hand (the ⛔ there says why), so it cannot inherit the shared guard and
26
+ * would otherwise be the one family that writes to Ceph with nothing checking the body.
27
+ * ⚠️ `dev` IS REQUIRED AND THE REFUSAL BELOW ALREADY COVERS IT, with a message about a missing
28
+ * replica that is far more useful than `dev: required`. The table is still worth checking: it
29
+ * also carries `db_dev_size` ≥ 1, `wal_dev_size` ≥ 0.5 and `osds-per-device` ≥ 1, and nothing
30
+ * else in this package knows those numbers.
31
+ * ⚠️ THERE IS NO UPDATE KEY: PVE registers no PUT under `ceph/osd`, and an OSD is not editable.
32
+ */
33
+ export const OSD_CREATE_ENDPOINT: EndpointKey = 'pve:POST /nodes/{node}/ceph/osd';
18
34
 
19
35
  const describe = (props: CephOsdProps) => `osd.${String(props.osdid)} on ${props.node}`;
20
36
 
37
+ /**
38
+ * The create body. ★ Exported so the constraint proof can run the REAL form.
39
+ * ⚠️ `dev` is `string | undefined` on the props and non-optional here: `createOsd` refuses first.
40
+ */
41
+ export const createOsdForm = (props: CephOsdProps, dev: string): Record<string, string> => ({
42
+ dev,
43
+ ...(props.device_class === undefined ? {} : { 'crush-device-class': props.device_class }),
44
+ });
45
+
21
46
  export const createOsd = Effect.fn(function* (news: CephOsdProps) {
22
47
  if (news.dev === undefined) {
23
48
  return yield* Effect.die(
@@ -30,10 +55,11 @@ export const createOsd = Effect.fn(function* (news: CephOsdProps) {
30
55
  ),
31
56
  );
32
57
  }
33
- yield* pve(news.target, 'provision', 'POST', `nodes/${news.node}/ceph/osd`, {
34
- dev: news.dev,
35
- ...(news.device_class === undefined ? {} : { 'crush-device-class': news.device_class }),
36
- });
58
+ const form = createOsdForm(news, news.dev);
59
+ // ⛔ BEFORE THE POST, BECAUSE THE POST ZAPS A BLOCK DEVICE. A vendor rule broken here is worth
60
+ // refusing at plan rather than discovering from a 400 after `ceph-volume` has run.
61
+ yield* guardForm(OSD_CREATE_ENDPOINT, form, true);
62
+ yield* pve(news.target, 'provision', 'POST', `nodes/${news.node}/ceph/osd`, form);
37
63
  const created = yield* readOsd(news);
38
64
  if (created === undefined) {
39
65
  return yield* Effect.die(
@@ -101,6 +101,11 @@ const spec: PveSpec<CephPoolProps, CephPoolAttributes> = {
101
101
  },
102
102
  collection,
103
103
  createForm: createBody,
104
+ /** The vendor rules both forms are checked against at plan time — resource-spec.ts. */
105
+ endpoint: {
106
+ create: 'pve:POST /nodes/{node}/ceph/pool',
107
+ update: 'pve:PUT /nodes/{node}/ceph/pool/{name}',
108
+ },
104
109
  /**
105
110
  * ⛔ DECLARING WHAT IS LIVE MUST PLAN noop, AND EVERY OMISSION HERE IS WHY. Out, each with its
106
111
  * reason on the prop it belongs to: `pg_num` (the autoscaler rewrites it), `application` (the
@@ -152,6 +157,10 @@ export const ProxmoxCephPoolProvider = () =>
152
157
  const live = yield* ops.read(news);
153
158
  let upid: string | undefined;
154
159
  if (live === undefined) {
160
+ // ⛔ THE GUARD `ops.reconcile` WOULD HAVE RUN, RESTORED. This handler replaces it
161
+ // wholesale, so without these two calls an ADOPTED pool — whose diff answer Alchemy
162
+ // discards — would reach the cluster with nothing having checked its body.
163
+ yield* ops.guardCreate(news, true);
155
164
  yield* confirmAbsent(news, spec.collection(news), object(news));
156
165
  upid = yield* pve<string>(
157
166
  news.target,
@@ -170,6 +179,7 @@ export const ProxmoxCephPoolProvider = () =>
170
179
  */
171
180
  const form = formToSend(spec.matches, live, news, updateBody(news));
172
181
  if (form !== undefined) {
182
+ yield* ops.guardUpdate(news);
173
183
  upid = yield* pve<string>(news.target, 'provision', 'PUT', object(news), form);
174
184
  }
175
185
  }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * The two rule kinds the first generation dropped, and the false positive neither may cause.
3
+ *
4
+ * 🔴 ADVERSARIAL REVIEW OF THE 2026-09-22 CONSTRAINT GUARD, SAME DAY. The guard caught the
5
+ * 128-character comment, and two whole rule kinds were passing through it unchecked:
6
+ * - PVE applies `m/^$pattern$/` (MEASURED in JSONSchema.pm:1636) and the table carried the
7
+ * UNANCHORED body, so `RegExp.test` searched: a firewall alias named `ok name!` matched on
8
+ * `ok`, passed plan, and was rejected by PVE with the 400 this feature exists to prevent.
9
+ * - An array parameter states its rules on `items`, and the emitter read only the parameter,
10
+ * so `target`, `associated-key` and `secondary-controllers` were tabled with no rule at all —
11
+ * while `violations` was already checking every element against that empty rule.
12
+ *
13
+ * ★ EVERY CASE HERE IS A MUTANT: it passes on the fixed generator and fails on the one that
14
+ * shipped. The last describe is the opposite risk and the more dangerous one — a rule that
15
+ * refuses a declaration the vendor ACCEPTS breaks a stack that was always legal.
16
+ */
17
+ import { describe, expect, test } from 'bun:test';
18
+ import { constraintsFor, formViolations } from './constraint-guard.ts';
19
+ import { matcherCreateForm, matcherUpdateForm } from './notification-matcher-form.ts';
20
+
21
+ const ALIAS = 'pve:POST /cluster/firewall/aliases';
22
+ const PBS_MATCHER = 'pbs:POST /config/notifications/matchers';
23
+ const PBS_MATCHER_PUT = 'pbs:PUT /config/notifications/matchers/{name}';
24
+ const PVE_MATCHER = 'pve:POST /cluster/notifications/matchers';
25
+ const BACKUP = 'pve:POST /cluster/backup';
26
+
27
+ describe('a PVE pattern is anchored the way PVE anchors it', () => {
28
+ /**
29
+ * ⛔ THE MUTANT. `[A-Za-z][A-Za-z0-9\-\_]+` unanchored matches the `ok` inside `ok name!`, so
30
+ * `RegExp.test` said yes and PVE said 400. Deleting the `^`/`$` in param-rules.ts brings this
31
+ * failure back exactly.
32
+ */
33
+ test('a name PVE rejects is refused, not matched on its prefix', () => {
34
+ expect(formViolations(ALIAS, { cidr: '10.0.0.0/24', name: 'ok-name' }, false)).toEqual([]);
35
+ expect(formViolations(ALIAS, { cidr: '10.0.0.0/24', name: 'ok name!' }, false)).toEqual([
36
+ 'name: must match [A-Za-z][A-Za-z0-9\\-\\_]+',
37
+ ]);
38
+ });
39
+
40
+ /** The violation quotes the VENDOR's spelling, so an operator can search PVE's own docs for it. */
41
+ test('the message is the vendor pattern, never the anchored translation', () => {
42
+ const rule = constraintsFor(ALIAS)['name'];
43
+ expect(rule?.pattern).toBe('^[A-Za-z][A-Za-z0-9\\-\\_]+\\n?$');
44
+ expect(rule?.patternSource).toBe('[A-Za-z][A-Za-z0-9\\-\\_]+');
45
+ });
46
+
47
+ /**
48
+ * ⚠️ PERL'S `$`, NOT JAVASCRIPT'S. `m/^x$/` matches `"x\n"`; a bare `$` in JavaScript does not.
49
+ * Without the `\n?` this line would be a refusal of a value PVE accepts — the false positive
50
+ * that is worse than the server 400 it replaces.
51
+ */
52
+ test('a trailing newline is accepted, because Perl accepts it', () => {
53
+ expect(formViolations(BACKUP, { starttime: '02:00\n' }, false)).toEqual([]);
54
+ expect(formViolations(BACKUP, { starttime: '02:00' }, false)).toEqual([]);
55
+ expect(formViolations(BACKUP, { starttime: 'at 02:00' }, false)).toEqual([
56
+ 'starttime: must match \\d{1,2}:\\d{1,2}',
57
+ ]);
58
+ });
59
+
60
+ /** ⚠️ PBS patterns already carry their own `^…$`; anchoring them again would invent a rule. */
61
+ test('a PBS pattern is left exactly as the vendor wrote it', () => {
62
+ const rule = constraintsFor(PBS_MATCHER)['name'];
63
+ expect(rule?.pattern).toBe('^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$');
64
+ expect(rule?.patternSource).toStartWith('/^');
65
+ });
66
+ });
67
+
68
+ describe("an array's rules come from its items, and reach every element", () => {
69
+ const fields = (targets: readonly string[]) => ({ name: 'default-matcher', targets });
70
+
71
+ test('PBS bounds each target name, and the table says the rule is per element', () => {
72
+ const rule = constraintsFor(PBS_MATCHER)['target'];
73
+ expect(rule).toMatchObject({ each: true, maxLength: 32, minLength: 2, type: 'array' });
74
+ });
75
+
76
+ test('one over-long element is refused although the parameter is a list', () => {
77
+ expect(formViolations(PBS_MATCHER, matcherCreateForm(fields(['mail-to-root'])), false)).toEqual(
78
+ [],
79
+ );
80
+ expect(formViolations(PBS_MATCHER, matcherCreateForm(fields(['m'.repeat(33)])), false)).toEqual(
81
+ ['target: at most 32 characters'],
82
+ );
83
+ // ⚠️ AND THE SHORT ONE TOO: `minLength` on an element is the bound a joined string would hide.
84
+ expect(formViolations(PBS_MATCHER, matcherCreateForm(fields(['m'])), false)).toEqual([
85
+ 'target: at least 2 characters',
86
+ ]);
87
+ });
88
+
89
+ /**
90
+ * ⛔ `delete` IS AN ARRAY OF AN ENUM ON PBS, and `matcherUpdateForm` fills it from its own
91
+ * `Deletable` union. This is the one place the two could drift: a member this package clears
92
+ * that PBS does not accept is a 400 the vendor's own enum can refuse at plan time.
93
+ */
94
+ test("the update form's cleared fields are all members of PBS's own delete enum", () => {
95
+ const form = matcherUpdateForm({ comment: '', name: 'default-matcher', targets: [] });
96
+ expect(form.delete).toEqual([
97
+ 'match-calendar',
98
+ 'match-field',
99
+ 'match-severity',
100
+ 'target',
101
+ 'comment',
102
+ ]);
103
+ expect(formViolations(PBS_MATCHER_PUT, form, false)).toEqual([]);
104
+ });
105
+
106
+ /** PVE spells the same list with `format: pve-configid`, which is a NAME and stays unchecked. */
107
+ test('a PVE element rule with only a format is recorded and not enforced', () => {
108
+ expect(constraintsFor(PVE_MATCHER)['target']).toMatchObject({
109
+ each: true,
110
+ format: 'pve-configid',
111
+ });
112
+ expect(formViolations(PVE_MATCHER, matcherCreateForm(fields(['m'.repeat(80)])), false)).toEqual(
113
+ [],
114
+ );
115
+ });
116
+ });
117
+
118
+ /**
119
+ * 🔴 `Proxmox.HaRule`'s TABLE WAS EMPTY AND NOTHING SAID SO. PVE spells `POST /cluster/ha/rules`
120
+ * as `allOf: [{properties: {rule}}, {oneOf: [node-affinity, resource-affinity]}]`, the reader
121
+ * asked for `parameters.properties`, got `undefined`, and emitted `{}` — which is exactly what
122
+ * an endpoint with no rules emits. A wired family's guard checked nothing, silently.
123
+ */
124
+ describe('a parameter schema wrapped in allOf/oneOf is read, not skipped', () => {
125
+ const CREATE = 'pve:POST /cluster/ha/rules';
126
+
127
+ test('the table is no longer empty, and carries the limits both branches agree on', () => {
128
+ const table = constraintsFor(CREATE);
129
+ expect(Object.keys(table).sort()).toEqual(['affinity', 'comment', 'resources', 'rule']);
130
+ expect(table['comment']).toMatchObject({ maxLength: 4096 });
131
+ expect(table['affinity']).toMatchObject({ enum: ['positive', 'negative'] });
132
+ });
133
+
134
+ /**
135
+ * ⛔ `nodes` AND `strict` EXIST ONLY ON THE node-affinity BRANCH, so enforcing them would refuse
136
+ * a resource-affinity rule for breaking a rule PVE never applies to it. `oneOf` intersects.
137
+ */
138
+ test('a rule only one branch states is NOT enforced', () => {
139
+ expect(constraintsFor(CREATE)['nodes']).toBeUndefined();
140
+ expect(constraintsFor(CREATE)['strict']).toBeUndefined();
141
+ });
142
+
143
+ /**
144
+ * ⚠️ `affinity` IS REQUIRED ON resource-affinity AND OPTIONAL ON node-affinity, so presence is
145
+ * not enforced. Dropping a disagreeing `optional` would have read as REQUIRED — absence means
146
+ * required in this schema — and refused every legal node-affinity rule.
147
+ */
148
+ test('presence is required only where every branch requires it', () => {
149
+ const table = constraintsFor(CREATE);
150
+ expect(table['rule']?.required).toBe(true);
151
+ expect(table['resources']?.required).toBe(true);
152
+ expect(table['affinity']?.required).toBeUndefined();
153
+ expect(formViolations(CREATE, { affinity: 'positive', resources: 'vm:100' }, true)).toEqual([
154
+ 'rule: required',
155
+ ]);
156
+ });
157
+
158
+ test('the live shape of a node-affinity rule has no violations', () => {
159
+ const form = { affinity: 'positive', nodes: 'n1:2,n2:1', resources: 'vm:100', rule: 'pin-100' };
160
+ expect(formViolations(CREATE, form, true)).toEqual([]);
161
+ expect(formViolations(CREATE, { ...form, comment: 'c'.repeat(4097) }, true)).toEqual([
162
+ 'comment: at most 4096 characters',
163
+ ]);
164
+ });
165
+ });
@@ -0,0 +1,220 @@
1
+ /**
2
+ * The cluster-scoped families' REAL create forms, against the vendor's own tables.
3
+ *
4
+ * ⛔ THE RISK THIS FEATURE CARRIES IS NOT THE ONE IT FIXES. A table that refuses a declaration the
5
+ * vendor would have ACCEPTED blocks a deploy that was always legal, and the operator cannot tell
6
+ * that from a genuine violation. So every family wired on 2026-09-22 gets the same proof: build
7
+ * the form the provider really builds, run it through `formViolations(key, form, true)` — the
8
+ * create check, presence included — and require an empty result.
9
+ *
10
+ * ⛔ THE FORM, NEVER A RETYPED COPY OF IT. Four families built their create form inline in a spec
11
+ * literal; each one gained an exported builder that the provider now uses, so this file and the
12
+ * provider cannot disagree. A test that retypes the form proves the test.
13
+ *
14
+ * ★ THE NODE-SCOPED HALF IS IN constraints-node-forms.test.ts — split for the 250-line cap, on the
15
+ * same seam the generated tables are split on (`/cluster` and `/access` here, `/nodes/{node}`
16
+ * there).
17
+ */
18
+ import { describe, expect, test } from 'bun:test';
19
+ import { shape as apiTokenForm } from './api-token-form.ts';
20
+ import { NETWORK_APPLY_ENDPOINT, SDN_APPLY_ENDPOINT } from './apply-endpoints.ts';
21
+ import { constraintsFor, formViolations } from './constraint-guard.ts';
22
+ import type { PveTarget } from './credentials.ts';
23
+ import { createBody as metricCreateForm } from './metric-server-form.ts';
24
+ import { createShape as targetCreateForm } from './notification-target-form.ts';
25
+ import { createForm as subnetCreateForm } from './sdn-subnet-form.ts';
26
+
27
+ const TARGET: PveTarget = { members: ['pve.test:8006'], mount: 'pve-test', scheme: 'pve' };
28
+
29
+ /** `formViolations` with presence — the create check, exactly as `resource-guard.ts` runs it. */
30
+ const onCreate = (key: string, form: Record<string, string | readonly string[]>) =>
31
+ formViolations(key, form, true);
32
+
33
+ describe('Proxmox.ApiToken plans clean against POST /access/users/{userid}/token/{tokenid}', () => {
34
+ /** ⚠️ `expire: 0` is "never", and PVE's `minimum: 0` accepts it — the bound the table carries. */
35
+ test('the estate-shaped token declaration has no violations', () => {
36
+ expect(
37
+ onCreate(
38
+ 'pve:POST /access/users/{userid}/token/{tokenid}',
39
+ apiTokenForm({
40
+ comment: 'Declared by Alchemy',
41
+ expire: 0,
42
+ privsep: false,
43
+ target: TARGET,
44
+ tokenid: 'apply',
45
+ userid: 'iac@pve',
46
+ }),
47
+ ),
48
+ ).toEqual([]);
49
+ });
50
+
51
+ /** ⛔ `{userid}` AND `{tokenid}` ARE PATH SEGMENTS. A table that kept them would refuse every
52
+ * create for a "missing" parameter that was never missing (codegen/emit.ts). */
53
+ test('neither path parameter is in the table', () => {
54
+ const table = constraintsFor('pve:POST /access/users/{userid}/token/{tokenid}');
55
+ expect(table['userid']).toBeUndefined();
56
+ expect(table['tokenid']).toBeUndefined();
57
+ });
58
+ });
59
+
60
+ describe('Proxmox.MetricServer plans clean against POST /cluster/metrics/server/{id}', () => {
61
+ /** The estate's own row: InfluxDB v2 over https, in a section named `macmini`. */
62
+ const declared = {
63
+ 'api-path-prefix': '/tb4/',
64
+ bucket: 'pve',
65
+ disable: false,
66
+ id: 'macmini',
67
+ influxdbproto: 'https',
68
+ 'max-body-size': 10_000_000,
69
+ organization: 'homeflare',
70
+ port: 443,
71
+ server: 'metrics.homeflare.dev',
72
+ target: TARGET,
73
+ type: 'influxdb',
74
+ 'verify-certificate': true,
75
+ } as const;
76
+
77
+ test('the live declaration has no violations', () => {
78
+ expect(onCreate('pve:POST /cluster/metrics/server/{id}', metricCreateForm(declared))).toEqual(
79
+ [],
80
+ );
81
+ });
82
+
83
+ /** ⛔ PVE MARKS THREE PARAMETERS REQUIRED AND THE CREATE FORM SENDS ALL THREE. `id` is the path. */
84
+ test('port, server and type are required and all three are sent', () => {
85
+ const table = constraintsFor('pve:POST /cluster/metrics/server/{id}');
86
+ expect(
87
+ Object.entries(table)
88
+ .filter(([, rule]) => rule.required)
89
+ .map(([name]) => name),
90
+ ).toEqual(['port', 'server', 'type']);
91
+ expect(Object.keys(metricCreateForm(declared))).toContain('type');
92
+ });
93
+
94
+ test('a port past PVE own maximum is refused rather than sent', () => {
95
+ expect(
96
+ onCreate(
97
+ 'pve:POST /cluster/metrics/server/{id}',
98
+ metricCreateForm({ ...declared, port: 70_000 }),
99
+ ),
100
+ ).toEqual(['port: at most 65536']);
101
+ });
102
+ });
103
+
104
+ describe('Proxmox.NotificationTarget plans clean per family', () => {
105
+ /** The estate's only target: the shipped `mail-to-root`, edited. sendmail requires only a name. */
106
+ test('the live sendmail declaration has no violations', () => {
107
+ expect(
108
+ onCreate(
109
+ 'pve:POST /cluster/notifications/endpoints/sendmail',
110
+ targetCreateForm({
111
+ comment: "Send mails to root@pam's email address",
112
+ disable: false,
113
+ mailto: ['someone@example.invalid'],
114
+ 'mailto-user': ['root@pam'],
115
+ name: 'mail-to-root',
116
+ target: TARGET,
117
+ type: 'sendmail',
118
+ }),
119
+ ),
120
+ ).toEqual([]);
121
+ });
122
+
123
+ test('an smtp target that declares its server and sender has no violations', () => {
124
+ expect(
125
+ onCreate(
126
+ 'pve:POST /cluster/notifications/endpoints/smtp',
127
+ targetCreateForm({
128
+ 'from-address': 'pve@example.invalid',
129
+ mode: 'tls',
130
+ name: 'smtp-relay',
131
+ port: 465,
132
+ server: 'smtp.example.invalid',
133
+ target: TARGET,
134
+ type: 'smtp',
135
+ }),
136
+ ),
137
+ ).toEqual([]);
138
+ });
139
+
140
+ /**
141
+ * ⛔ THE ONE FAMILY THAT CANNOT SATISFY ITS OWN CREATE, PROVEN RATHER THAN ASSERTED IN PROSE.
142
+ * PVE requires gotify's `token` and it is a write-only secret, so it is not a prop — props are
143
+ * persisted to the state store unencrypted. `notification-target.ts` has said so in a comment
144
+ * since the family was written; this is the comment made executable.
145
+ * ★ AND IT IS WHY THE CREATE CHECK IS CONDITIONAL. `resource-guard.ts` demands presence only
146
+ * when a create is really about to happen, so the documented workflow — create it out of band
147
+ * with its secret, then declare it — still plans clean. Asking to CREATE one fails at plan
148
+ * with the vendor's own word instead of a 400 half a deploy in.
149
+ */
150
+ test('a gotify create is refused for the token it may never carry', () => {
151
+ expect(
152
+ onCreate(
153
+ 'pve:POST /cluster/notifications/endpoints/gotify',
154
+ targetCreateForm({
155
+ name: 'gotify-house',
156
+ server: 'https://gotify.example.invalid',
157
+ target: TARGET,
158
+ type: 'gotify',
159
+ }),
160
+ ),
161
+ ).toEqual(['token: required']);
162
+ });
163
+ });
164
+
165
+ describe('Proxmox.SdnSubnet plans clean against POST /cluster/sdn/vnets/{vnet}/subnets', () => {
166
+ test('a declared subnet has no violations', () => {
167
+ expect(
168
+ onCreate(
169
+ 'pve:POST /cluster/sdn/vnets/{vnet}/subnets',
170
+ subnetCreateForm({
171
+ cidr: '192.0.2.0/24',
172
+ gateway: '192.0.2.1',
173
+ target: TARGET,
174
+ vnet: 'vnet0',
175
+ zone: 'zone0',
176
+ }),
177
+ ),
178
+ ).toEqual([]);
179
+ });
180
+
181
+ /** ⚠️ `subnet` and `type` are the two PVE marks required, and `createForm` sends both. */
182
+ test('subnet and type are required and both are sent', () => {
183
+ const table = constraintsFor('pve:POST /cluster/sdn/vnets/{vnet}/subnets');
184
+ expect(table['subnet']?.required).toBe(true);
185
+ expect(table['type']).toMatchObject({ enum: ['subnet'], required: true });
186
+ });
187
+ });
188
+
189
+ /**
190
+ * ★ AN ACTION ENDPOINT IS WIRED TOO, AND THE TABLE IS SUPPOSED TO BE EMPTY. Neither apply takes a
191
+ * body, so there is nothing for a length or a range to be wrong about — the key exists so that
192
+ * `codegen/constraints.ts` resolves it against the vendor schema at generation time. A PVE that
193
+ * moves or withdraws either one fails `bun run check` rather than an `ifreload -a` on three
194
+ * nodes at once. ⛔ An empty table is NOT the same as a missing one: a missing one throws.
195
+ */
196
+ describe('the two applies are wired although they carry no form', () => {
197
+ test('both tables exist and both are empty', () => {
198
+ expect(constraintsFor(NETWORK_APPLY_ENDPOINT)).toEqual({});
199
+ expect(constraintsFor(SDN_APPLY_ENDPOINT)).toEqual({});
200
+ });
201
+
202
+ test('a key the generator never tabled is a defect that names the command', () => {
203
+ expect(() => constraintsFor('pve:PUT /cluster/sdn/nope')).toThrow('bun codegen/constraints.ts');
204
+ });
205
+ });
206
+
207
+ /**
208
+ * ⛔ `Proxmox.CephFlag` HAS NO CREATE KEY, AND THAT IS THE VENDOR'S DOING. PVE registers only PUT
209
+ * on `/cluster/ceph/flags/{flag}`; `POST /cluster/ceph/flags` does not exist, which is why
210
+ * ceph-flag.ts documents its create branch as unreachable. Naming that POST would stop the
211
+ * generator — correctly — so the family declares the update alone.
212
+ */
213
+ describe('Proxmox.CephFlag is wired through its PUT alone', () => {
214
+ test('the update form passes, and value is the one rule PVE publishes', () => {
215
+ expect(formViolations('pve:PUT /cluster/ceph/flags/{flag}', { value: '1' }, false)).toEqual([]);
216
+ expect(constraintsFor('pve:PUT /cluster/ceph/flags/{flag}')).toEqual({
217
+ value: { required: true, type: 'boolean' },
218
+ });
219
+ });
220
+ });
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The host families' REAL create forms — interfaces and ZFS pools — and PBS's notification
3
+ * targets, which carry the very rule the 2026-09-22 incident was about.
4
+ *
5
+ * ★ SPLIT OUT OF constraints-node-forms.test.ts FOR THE 250-LINE CAP. That file is the Ceph and
6
+ * guest families; this one is what a host itself is made of, plus the PBS half.
7
+ * ⛔ SAME PROOF, SAME REASON: a table that refuses a declaration the vendor would have ACCEPTED is
8
+ * worse than the 400 it replaces, so each family's own builder goes through
9
+ * `formViolations(key, form, true)` and an empty result is required.
10
+ */
11
+ import { describe, expect, test } from 'bun:test';
12
+ import { constraintsFor, formViolations } from './constraint-guard.ts';
13
+ import type { PbsTarget, PveTarget } from './credentials.ts';
14
+ import { createBody as ifaceCreateForm } from './node-network-form.ts';
15
+ import { resolveGroups, targetForm } from './pbs-notification-target-form.ts';
16
+ import { createForm as zfsCreateForm } from './zfs-pool-write.ts';
17
+
18
+ const TARGET: PveTarget = { members: ['pve.test:8006'], mount: 'pve-test', scheme: 'pve' };
19
+ const PBS: PbsTarget = { api: 'https://pbs.test:8007/api2/json', mount: 'pbs-test', scheme: 'pbs' };
20
+
21
+ const onCreate = (key: string, form: Record<string, string | readonly string[]>) =>
22
+ formViolations(key, form, true);
23
+
24
+ describe('the host families plan clean against their own creates', () => {
25
+ /**
26
+ * 🔴 THE BUG THIS PROOF FOUND. `createForm` was `body`, which never sent `iface` — and PVE marks
27
+ * it REQUIRED on the POST while `{node}` is the only path parameter. Every NodeNetwork create
28
+ * this repository could have made would have 400ed; nothing caught it because C1's three nodes
29
+ * were all adopted, which takes the PUT path. See the 🔴 on `createBody`.
30
+ */
31
+ test('an interface create carries iface, which PVE requires in the body', () => {
32
+ const form = ifaceCreateForm({
33
+ autostart: true,
34
+ cidr: '192.0.2.10/24',
35
+ iface: 'vmbr1.42',
36
+ node: 'n2',
37
+ target: TARGET,
38
+ type: 'vlan',
39
+ 'vlan-raw-device': 'vmbr1',
40
+ });
41
+ expect(form['iface']).toBe('vmbr1.42');
42
+ expect(onCreate('pve:POST /nodes/{node}/network', form)).toEqual([]);
43
+ });
44
+
45
+ test('iface and type are the required pair, and iface is 2..20 characters', () => {
46
+ const table = constraintsFor('pve:POST /nodes/{node}/network');
47
+ expect(
48
+ Object.entries(table)
49
+ .filter(([, rule]) => rule.required)
50
+ .map(([name]) => name),
51
+ ).toEqual(['iface', 'type']);
52
+ expect(table['iface']).toMatchObject({ maxLength: 20, minLength: 2 });
53
+ });
54
+
55
+ /** ⚠️ The PUT takes `iface` in the path, so the update form is right to leave it out. */
56
+ test('the update form, without iface, passes the PUT table', () => {
57
+ expect(constraintsFor('pve:PUT /nodes/{node}/network/{iface}')['iface']).toBeUndefined();
58
+ });
59
+
60
+ test('a ZFS pool create passes, and its three required parameters are all sent', () => {
61
+ const form = zfsCreateForm({
62
+ ashift: 12,
63
+ compression: 'lz4',
64
+ devices: ['/dev/nvme0n1', '/dev/nvme1n1'],
65
+ name: 'rpool-data',
66
+ node: 'n2',
67
+ raidlevel: 'mirror',
68
+ target: TARGET,
69
+ });
70
+ expect(onCreate('pve:POST /nodes/{node}/disks/zfs', form)).toEqual([]);
71
+ for (const name of ['devices', 'name', 'raidlevel']) expect(form[name]).toBeDefined();
72
+ });
73
+
74
+ test('an ashift above PVE own maximum of 16 is refused', () => {
75
+ expect(
76
+ onCreate(
77
+ 'pve:POST /nodes/{node}/disks/zfs',
78
+ zfsCreateForm({
79
+ ashift: 17,
80
+ devices: ['/dev/nvme0n1'],
81
+ name: 'rpool-data',
82
+ node: 'n2',
83
+ raidlevel: 'single',
84
+ target: TARGET,
85
+ }),
86
+ ),
87
+ ).toEqual(['ashift: at most 16']);
88
+ });
89
+ });
90
+
91
+ /**
92
+ * 🔴 `Pbs.NotificationTarget` CARRIES THE INCIDENT'S OWN PARAMETER. `comment` is `maxLength: 128`
93
+ * with a no-control-characters pattern on all three PBS creates — the same rule, on the same
94
+ * product, that failed `POST /config/verify` half a deploy in. This family was missing from the
95
+ * list the sweep started from; it is wired with the rest.
96
+ */
97
+ describe('Pbs.NotificationTarget plans clean, and its comment is the 128 again', () => {
98
+ const declared = {
99
+ comment: 'PBS failure mail',
100
+ mailto: ['someone@example.invalid'],
101
+ name: 'mail-to-root',
102
+ target: PBS,
103
+ type: 'sendmail',
104
+ } as const;
105
+ const carry = { header: true, sealed: true } as const;
106
+
107
+ test('a sendmail target has no violations', () => {
108
+ const form = targetForm(declared, resolveGroups(declared, {}), carry, 'create');
109
+ expect(form['name']).toBe('mail-to-root');
110
+ expect(onCreate('pbs:POST /config/notifications/endpoints/sendmail', form)).toEqual([]);
111
+ });
112
+
113
+ test('a 129-character comment is refused, exactly as on POST /config/verify', () => {
114
+ const over = { ...declared, comment: 'c'.repeat(129) };
115
+ const form = targetForm(over, resolveGroups(over, {}), carry, 'create');
116
+ expect(onCreate('pbs:POST /config/notifications/endpoints/sendmail', form)).toEqual([
117
+ 'comment: at most 128 characters',
118
+ ]);
119
+ });
120
+
121
+ test('a webhook target sends the url and method PBS requires', () => {
122
+ const hook = {
123
+ method: 'post',
124
+ name: 'hook-house',
125
+ target: PBS,
126
+ type: 'webhook',
127
+ url: 'https://hooks.example.invalid/pbs',
128
+ } as const;
129
+ const form = targetForm(hook, resolveGroups(hook, {}), carry, 'create');
130
+ expect(onCreate('pbs:POST /config/notifications/endpoints/webhook', form)).toEqual([]);
131
+ });
132
+ });