<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>ProxmoxVEx Releases</title><description>Release notes and updates for ProxmoxVEx</description><link>https://proxmoxvex.com/</link><item><title>ProxmoxVEx — 1.2.690</title><link>https://proxmoxvex.com/changelog/#release--1-2-690-2026-10-26/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-690-2026-10-26/</guid><description>&lt;p&gt;Build 2026.10.26: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.689</title><link>https://proxmoxvex.com/changelog/#release--1-2-689-2026-10-26/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-689-2026-10-26/</guid><description>&lt;p&gt;Build 2026.10.26: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.688</title><link>https://proxmoxvex.com/changelog/#release--1-2-688-2026-10-26/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-688-2026-10-26/</guid><description>&lt;p&gt;Build 2026.10.26: Full build pipeline&lt;/p&gt;&lt;p&gt;fix (db): Postgres translation now handles parameterized&lt;/p&gt;&lt;p&gt;fix (vm-update-manager): complete auto-repair coverage on the scheduled&lt;/p&gt;</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.687</title><link>https://proxmoxvex.com/changelog/#release--1-2-687-2026-10-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-687-2026-10-25/</guid><description>&lt;p&gt;Build 2026.10.25: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 25 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.686</title><link>https://proxmoxvex.com/changelog/#release--1-2-686-2026-10-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-686-2026-10-25/</guid><description>&lt;p&gt;Build 2026.10.25: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 25 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.685</title><link>https://proxmoxvex.com/changelog/#release--1-2-685-2026-10-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-685-2026-10-25/</guid><description>&lt;p&gt;Build 2026.10.25: Full build pipeline&lt;/p&gt;&lt;p&gt;fix (vm-update-manager): failed applies now self-repair before failing.&lt;/p&gt;</description><pubDate>Sun, 25 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.684</title><link>https://proxmoxvex.com/changelog/#release--1-2-684-2026-10-22/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-684-2026-10-22/</guid><description>&lt;p&gt;Build 2026.10.22: vm-update-manager: deferred-update feedback and remedies&lt;/p&gt;&lt;p&gt;feat (vm-update-manager): explain *why* updates &quot;won&apos;t install&quot; instead of&lt;/p&gt;</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.683</title><link>https://proxmoxvex.com/changelog/#release--1-2-683-2026-10-22/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-683-2026-10-22/</guid><description>&lt;p&gt;Build 2026.10.22: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.682</title><link>https://proxmoxvex.com/changelog/#release--1-2-682-2026-10-22/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-682-2026-10-22/</guid><description>&lt;p&gt;Build 2026.10.22: Full build pipeline&lt;/p&gt;&lt;p&gt;fix (plugins): the boot seed-sync now replaces foreign-owned files — a&lt;/p&gt;</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.681</title><link>https://proxmoxvex.com/changelog/#release--1-2-681-2026-10-22/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-681-2026-10-22/</guid><description>&lt;p&gt;Build 2026.10.22: Build&lt;/p&gt;</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.680</title><link>https://proxmoxvex.com/changelog/#release--1-2-680-2026-10-22/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-680-2026-10-22/</guid><description>&lt;p&gt;Build 2026.10.22: Build&lt;/p&gt;&lt;p&gt;fix (plugins): preserve admin-edited `config.json` and manifest-declared&lt;/p&gt;&lt;p&gt;fix (plugins): `DELETE /api/plugins/&lt;id&gt;` now also drops the&lt;/p&gt;</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.679</title><link>https://proxmoxvex.com/changelog/#release--1-2-679-2026-10-22/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-679-2026-10-22/</guid><description>&lt;p&gt;Build 2026.10.22: Full build pipeline&lt;/p&gt;&lt;p&gt;fix (ids): annotate `_refresh_or_create`&apos;s `fields` dict as&lt;/p&gt;</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.678</title><link>https://proxmoxvex.com/changelog/#release--1-2-678-2026-10-22/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-678-2026-10-22/</guid><description>&lt;p&gt;Build 2026.10.22: Full build pipeline&lt;/p&gt;&lt;p&gt;test (ids): cover the converter hardening&apos;s rejection paths —&lt;/p&gt;</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.677</title><link>https://proxmoxvex.com/changelog/#release--1-2-677-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-677-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Build&lt;/p&gt;&lt;p&gt;fix (ids): the Suricata/Snort ruleset converter flattened imported&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.676</title><link>https://proxmoxvex.com/changelog/#release--1-2-676-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-676-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.675</title><link>https://proxmoxvex.com/changelog/#release--1-2-675-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-675-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.674</title><link>https://proxmoxvex.com/changelog/#release--1-2-674-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-674-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Full build pipeline&lt;/p&gt;&lt;p&gt;fix (compliance): the &quot;Open Settings → Compliance&quot; quick link did&lt;/p&gt;&lt;p&gt;fix (settings): array-consumed settings state (users, tenants, roles,&lt;/p&gt;&lt;p&gt;feat (compliance): redesigned the Compliance Dashboard hardening tab&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.673</title><link>https://proxmoxvex.com/changelog/#release--1-2-673-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-673-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Full build pipeline&lt;/p&gt;&lt;p&gt;feat (compliance): framework cards are now always fully scanned. The&lt;/p&gt;&lt;p&gt;feat (compliance): new &quot;Download all (PDF)&quot; button on the Compliance&lt;/p&gt;&lt;p&gt;feat (compliance): per-framework PDF report themes — every framework&lt;/p&gt;&lt;p&gt;feat (compliance): six new framework cards with backend mappings —&lt;/p&gt;&lt;p&gt;test (compliance): full integrity suite for the compliance framework&lt;/p&gt;&lt;p&gt;fix (clusters): the &quot;ProxLB VM Tags&quot; toggle flipped itself back off a&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.672</title><link>https://proxmoxvex.com/changelog/#release--1-2-672-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-672-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Full build pipeline&lt;/p&gt;&lt;p&gt;test (rockstor): cover the real-HTTP access-key transport — bearer&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.671</title><link>https://proxmoxvex.com/changelog/#release--1-2-671-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-671-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.670</title><link>https://proxmoxvex.com/changelog/#release--1-2-670-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-670-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Build&lt;/p&gt;&lt;p&gt;feat (rockstor): connections can now authenticate with a Rockstor&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.669</title><link>https://proxmoxvex.com/changelog/#release--1-2-669-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-669-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Full build pipeline&lt;/p&gt;&lt;p&gt;fix (console): VM/QEMU noVNC consoles hung on &quot;Connecting...&quot; while LXC&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.668</title><link>https://proxmoxvex.com/changelog/#release--1-2-668-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-668-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Build&lt;/p&gt;&lt;p&gt;fix (rockstor): native UI assets never invalidated — `ui_handler`&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.667</title><link>https://proxmoxvex.com/changelog/#release--1-2-667-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-667-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Full build pipeline&lt;/p&gt;&lt;p&gt;feat (integrations): the sidebar &quot;+&quot; add-integration picker now lists&lt;/p&gt;&lt;p&gt;remove (pbs): drop the duplicate `pbs-manager` plugin (v1.0.5) and the&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.666</title><link>https://proxmoxvex.com/changelog/#release--1-2-666-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-666-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Build&lt;/p&gt;&lt;p&gt;feat (rockstor): Access Keys support + richer appliance stats. New&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.665</title><link>https://proxmoxvex.com/changelog/#release--1-2-665-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-665-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Build&lt;/p&gt;&lt;p&gt;fix (rockstor): all `*_kib` columns (pool size/free/used, share&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.664</title><link>https://proxmoxvex.com/changelog/#release--1-2-664-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-664-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Build&lt;/p&gt;&lt;p&gt;fix (rockstor): `create_connection` no longer crashes on Postgres&lt;/p&gt;&lt;p&gt;`cur.lastrowid` convention, which the PG adapter backs with&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.663</title><link>https://proxmoxvex.com/changelog/#release--1-2-663-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-663-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Full build pipeline&lt;/p&gt;&lt;p&gt;tests (rockstor): fixed two test-harness defects exposed by the native&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.662</title><link>https://proxmoxvex.com/changelog/#release--1-2-662-2026-10-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-662-2026-10-21/</guid><description>&lt;p&gt;Build 2026.10.21: Full build pipeline&lt;/p&gt;&lt;p&gt;rockstor: converted from a `plugins/` plugin to a native integration&lt;/p&gt;&lt;p&gt;tests: `conftest` now drains leaked plugin job-queue workers at every&lt;/p&gt;&lt;p&gt;tests: `test_plugin_theme_bridge` reuses pytest-playwright&apos;s session&lt;/p&gt;&lt;p&gt;tests: `test_readonly_rootfs` updated for the every-boot plugin-volume&lt;/p&gt;</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.661</title><link>https://proxmoxvex.com/changelog/#release--1-2-661-2026-10-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-661-2026-10-20/</guid><description>&lt;p&gt;Build 2026.10.20: Full build pipeline&lt;/p&gt;&lt;p&gt;Disabled native integrations are now hidden from the dashboard sidebar&apos;s&lt;/p&gt;&lt;p&gt;plugins: `ensure_plugins_seeded` now reconciles shipped plugin dirs&lt;/p&gt;</description><pubDate>Tue, 20 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.660</title><link>https://proxmoxvex.com/changelog/#release--1-2-660-2026-10-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-660-2026-10-20/</guid><description>&lt;p&gt;Build 2026.10.20: Full build pipeline&lt;/p&gt;&lt;p&gt;rockstor: expanded e2e coverage to 12 tests — add-connection modal,&lt;/p&gt;</description><pubDate>Tue, 20 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.659</title><link>https://proxmoxvex.com/changelog/#release--1-2-659-2026-10-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-659-2026-10-20/</guid><description>&lt;p&gt;Build 2026.10.20: Full build pipeline&lt;/p&gt;&lt;p&gt;New `rockstor` storage plugin (v1.0.0, basic tier): manages Rockstor&lt;/p&gt;&lt;p&gt;ups-monitor: clearer error when the configured NUT UPS name is wrong&lt;/p&gt;&lt;p&gt;Docker image: added `net-snmp-tools` so the ups-monitor SNMP transport&lt;/p&gt;</description><pubDate>Tue, 20 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.658</title><link>https://proxmoxvex.com/changelog/#release--1-2-658-2026-10-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-658-2026-10-16/</guid><description>&lt;p&gt;Build 2026.10.16: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 16 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.657</title><link>https://proxmoxvex.com/changelog/#release--1-2-657-2026-10-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-657-2026-10-16/</guid><description>&lt;p&gt;Build 2026.10.16: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 16 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.656</title><link>https://proxmoxvex.com/changelog/#release--1-2-656-2026-10-15/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-656-2026-10-15/</guid><description>&lt;p&gt;Build 2026.10.15: Full build pipeline&lt;/p&gt;&lt;p&gt;ai-assistant: clear IDE type errors in the chat handlers (part of plugin&lt;/p&gt;&lt;p&gt;Fix ai-assistant chat always failing with &quot;Provider error — check&lt;/p&gt;&lt;p&gt;ups-monitor: clear IDE type errors in the job engine (part of plugin&lt;/p&gt;&lt;p&gt;ups-monitor: &quot;Test connection&quot; now reports success inline (plugin 1.1.5).&lt;/p&gt;&lt;p&gt;ups-monitor: live auto-refresh + next-check countdown (plugin 1.1.4).&lt;/p&gt;&lt;p&gt;Fix plugin jobs invisible in the main task tray: `list_plugin_tasks`&lt;/p&gt;&lt;p&gt;Fix manual &quot;Fire now&quot; trigger runs leaving no job/task trace: real&lt;/p&gt;&lt;p&gt;Fix task-tray log viewer 404ing for ALL plugin tasks: the drawer fetched&lt;/p&gt;&lt;p&gt;ups-monitor i18n: added next-check/countdown keys (en, de, es, fr).&lt;/p&gt;</description><pubDate>Thu, 15 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.655</title><link>https://proxmoxvex.com/changelog/#release--1-2-655-2026-10-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-655-2026-10-12/</guid><description>&lt;p&gt;Build 2026.10.12: Full build pipeline&lt;/p&gt;&lt;p&gt;Fix ai-assistant &quot;List models&quot; probing the wrong URL and failing&lt;/p&gt;&lt;p&gt;Fix ai-assistant model list not populating (1.0.2): fetched models were&lt;/p&gt;&lt;p&gt;Fix ai-assistant under-counting guests (1.0.3): the injected context&lt;/p&gt;&lt;p&gt;Add live-data tools to ai-assistant (1.0.4): the model can now emit a&lt;/p&gt;</description><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.654</title><link>https://proxmoxvex.com/changelog/#release--1-2-654-2026-10-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-654-2026-10-12/</guid><description>&lt;p&gt;Build 2026.10.12: Full build pipeline&lt;/p&gt;&lt;p&gt;Fix `executescript` crash on PostgreSQL deployments: `PGConnection` had&lt;/p&gt;</description><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.653</title><link>https://proxmoxvex.com/changelog/#release--1-2-653-2026-10-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-653-2026-10-12/</guid><description>&lt;p&gt;Build 2026.10.12: Full build pipeline&lt;/p&gt;&lt;p&gt;Fix stale plugin catalog resurrecting retired paid plugins: the plugins&lt;/p&gt;&lt;p&gt;Harden plugin deletion: `DELETE /api/plugins/&lt;id&gt;` now purges&lt;/p&gt;</description><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.652</title><link>https://proxmoxvex.com/changelog/#release--1-2-652-2026-10-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-652-2026-10-12/</guid><description>&lt;p&gt;Build 2026.10.12: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.651</title><link>https://proxmoxvex.com/changelog/#release--1-2-651-2026-10-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-651-2026-10-12/</guid><description>&lt;p&gt;Build 2026.10.12: Full build pipeline&lt;/p&gt;&lt;p&gt;De-flake e2e `networkidle` timeouts: `tests/e2e/server.py` now uses&lt;/p&gt;</description><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.650</title><link>https://proxmoxvex.com/changelog/#release--1-2-650-2026-10-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-650-2026-10-12/</guid><description>&lt;p&gt;Build 2026.10.12: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.649</title><link>https://proxmoxvex.com/changelog/#release--1-2-649-2026-10-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-649-2026-10-12/</guid><description>&lt;p&gt;Build 2026.10.12: Full build pipeline&lt;/p&gt;&lt;p&gt;UPS Monitor 1.1.3: triggers gained a `notify` action type that fires a&lt;/p&gt;&lt;p&gt;Compose: raised the postgres service limits (1.0→4.0 CPU, 1G→2G) and&lt;/p&gt;&lt;p&gt;UPS Monitor 1.1.2: fixed static-analysis errors in the trigger power&lt;/p&gt;</description><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.648</title><link>https://proxmoxvex.com/changelog/#release--1-2-648-2026-10-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-648-2026-10-10/</guid><description>&lt;p&gt;Build 2026.10.10: Full build pipeline&lt;/p&gt;&lt;p&gt;UPS Monitor trigger runs now record every guest (and node) they power&lt;/p&gt;</description><pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.647</title><link>https://proxmoxvex.com/changelog/#release--1-2-647-2026-10-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-647-2026-10-10/</guid><description>&lt;p&gt;Build 2026.10.10: Full build pipeline&lt;/p&gt;&lt;p&gt;Merged the standalone &quot;APC / Schneider Electric UPS&quot; plugin (`apc-ups`)&lt;/p&gt;&lt;p&gt;UPS Monitor gains configurable event triggers: on-battery, low&lt;/p&gt;</description><pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.646</title><link>https://proxmoxvex.com/changelog/#release--1-2-646-2026-10-09/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-646-2026-10-09/</guid><description>&lt;p&gt;Build 2026.10.09: Full build pipeline&lt;/p&gt;&lt;p&gt;Fix a pipeline failure in the UPS monitor unit tests by marking&lt;/p&gt;</description><pubDate>Fri, 09 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.645</title><link>https://proxmoxvex.com/changelog/#release--1-2-645-2026-10-09/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-645-2026-10-09/</guid><description>&lt;p&gt;Build 2026.10.09: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 09 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.644</title><link>https://proxmoxvex.com/changelog/#release--1-2-644-2026-10-09/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-644-2026-10-09/</guid><description>&lt;p&gt;Build 2026.10.09: Full build pipeline&lt;/p&gt;&lt;p&gt;UPS Monitor 1.0.6: silence the static type checker&apos;s `float(None)` flag&lt;/p&gt;&lt;p&gt;UPS Monitor can now add and manage UPS devices over NUT, apcupsd, APC&lt;/p&gt;&lt;p&gt;Fix UPS monitor unit-test typing by narrowing dynamic import specs/loaders&lt;/p&gt;&lt;p&gt;Bind the bundled PostgreSQL service to host loopback port 5433 so the&lt;/p&gt;</description><pubDate>Fri, 09 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.643</title><link>https://proxmoxvex.com/changelog/#release--1-2-643-2026-10-08/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-643-2026-10-08/</guid><description>&lt;p&gt;Build 2026.10.08: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.642</title><link>https://proxmoxvex.com/changelog/#release--1-2-642-2026-10-08/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-642-2026-10-08/</guid><description>&lt;p&gt;Build 2026.10.08: Full build pipeline&lt;/p&gt;&lt;p&gt;tests: conftest teardown now pops Flask request/app contexts leaked by&lt;/p&gt;</description><pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.641</title><link>https://proxmoxvex.com/changelog/#release--1-2-641-2026-10-08/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-641-2026-10-08/</guid><description>&lt;p&gt;Build 2026.10.08: Full build pipeline&lt;/p&gt;&lt;p&gt;fix(vm-template-marketplace): `_current_user()` returned the whole&lt;/p&gt;&lt;p&gt;tests: de-flake xdist worker crashes/pollution — mark&lt;/p&gt;</description><pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.640</title><link>https://proxmoxvex.com/changelog/#release--1-2-640-2026-10-08/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-640-2026-10-08/</guid><description>&lt;p&gt;Build 2026.10.08: Full build pipeline&lt;/p&gt;&lt;p&gt;tests: fix two order/parse-dependent failures — pin&lt;/p&gt;</description><pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.639</title><link>https://proxmoxvex.com/changelog/#release--1-2-639-2026-10-08/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-639-2026-10-08/</guid><description>&lt;p&gt;Build 2026.10.08: Full build pipeline&lt;/p&gt;&lt;p&gt;spec 122 (ai-assistant): new paid `ai-assistant` plugin — read-only&lt;/p&gt;&lt;p&gt;spec 121 (flow-telemetry): new paid `flow-telemetry` plugin — embedded&lt;/p&gt;&lt;p&gt;spec 120 (compliance-frameworks): new paid `compliance-frameworks`&lt;/p&gt;&lt;p&gt;spec 119 (virtual-datacenters): new paid `vdc-manager` plugin —&lt;/p&gt;&lt;p&gt;spec 118 (site-recovery-orchestration): new paid `site-recovery`&lt;/p&gt;&lt;p&gt;spec 117 (drs-workload-balancer): new paid `drs-balancer` plugin —&lt;/p&gt;&lt;p&gt;`v2v-migrator` 1.0.3 (pyright-hygiene pass): provider handlers narrow&lt;/p&gt;&lt;p&gt;spec 116 (cross-hypervisor-migration): new paid `v2v-migrator` plugin —&lt;/p&gt;</description><pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.638</title><link>https://proxmoxvex.com/changelog/#release--1-2-638-2026-10-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-638-2026-10-07/</guid><description>&lt;p&gt;Build 2026.10.07: Full build pipeline&lt;/p&gt;&lt;p&gt;spec 115 (azure-native-integration): native Azure connection type —&lt;/p&gt;&lt;p&gt;plugin overhaul tail wave: `hello_world` v1.0.3 (durable jobs + probe&lt;/p&gt;&lt;p&gt;`tape-library-manager` v1.1.3 overhaul: durable import jobs (queue,&lt;/p&gt;&lt;p&gt;`gpu-passthrough-catalog`: closed the two remaining checklist items&lt;/p&gt;&lt;p&gt;spec 128 (ux-parity-pack): per-user dashboard layouts on the overview&lt;/p&gt;&lt;p&gt;spec 128: tracked chunked uploads + asset library — resumable&lt;/p&gt;&lt;p&gt;spec 128: in-app API reference (Settings → API Reference) generated&lt;/p&gt;&lt;p&gt;spec 128: persisted health-score history — periodic snapshots with&lt;/p&gt;&lt;p&gt;spec 128: `vm-template-marketplace` v1.1.1 gains a Library tab —&lt;/p&gt;&lt;p&gt;spec 128: cleared static-analysis errors in the new modules — strict&lt;/p&gt;</description><pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.637</title><link>https://proxmoxvex.com/changelog/#release--1-2-637-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-637-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.636</title><link>https://proxmoxvex.com/changelog/#release--1-2-636-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-636-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.635</title><link>https://proxmoxvex.com/changelog/#release--1-2-635-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-635-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.634</title><link>https://proxmoxvex.com/changelog/#release--1-2-634-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-634-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.633</title><link>https://proxmoxvex.com/changelog/#release--1-2-633-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-633-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.632</title><link>https://proxmoxvex.com/changelog/#release--1-2-632-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-632-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.631</title><link>https://proxmoxvex.com/changelog/#release--1-2-631-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-631-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.630</title><link>https://proxmoxvex.com/changelog/#release--1-2-630-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-630-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.629</title><link>https://proxmoxvex.com/changelog/#release--1-2-629-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-629-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;&lt;p&gt;Workspace tooling: added `css.customData` to the root workspace&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.628</title><link>https://proxmoxvex.com/changelog/#release--1-2-628-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-628-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;&lt;p&gt;Workspace tooling: pinned the VS Code venv tasks to `python3.14`&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.627</title><link>https://proxmoxvex.com/changelog/#release--1-2-627-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-627-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;&lt;p&gt;Dependency maintenance sweep: Werkzeug 3.1.9, cryptography 50.0.2,&lt;/p&gt;&lt;p&gt;Build toolchain on Babel 8 (was 7): `web/Dev/build.sh` now writes a&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.626</title><link>https://proxmoxvex.com/changelog/#release--1-2-626-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-626-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.625</title><link>https://proxmoxvex.com/changelog/#release--1-2-625-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-625-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.624</title><link>https://proxmoxvex.com/changelog/#release--1-2-624-2026-10-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-624-2026-10-06/</guid><description>&lt;p&gt;Build 2026.10.06: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.623</title><link>https://proxmoxvex.com/changelog/#release--1-2-623-2026-10-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-623-2026-10-05/</guid><description>&lt;p&gt;Build 2026.10.05: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.622</title><link>https://proxmoxvex.com/changelog/#release--1-2-622-2026-10-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-622-2026-10-05/</guid><description>&lt;p&gt;Build 2026.10.05: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.621</title><link>https://proxmoxvex.com/changelog/#release--1-2-621-2026-10-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-621-2026-10-05/</guid><description>&lt;p&gt;Build 2026.10.05: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.620</title><link>https://proxmoxvex.com/changelog/#release--1-2-620-2026-10-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-620-2026-10-05/</guid><description>&lt;p&gt;Build 2026.10.05: Full build pipeline&lt;/p&gt;&lt;p&gt;Docs pipeline wiring: `scripts/full-pipeline.sh` gained a `docs-regen`&lt;/p&gt;&lt;p&gt;Docs: regenerated 29 plugin reference pages in `docs/plugins/` that&lt;/p&gt;</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.619</title><link>https://proxmoxvex.com/changelog/#release--1-2-619-2026-10-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-619-2026-10-05/</guid><description>&lt;p&gt;Build 2026.10.05: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.618</title><link>https://proxmoxvex.com/changelog/#release--1-2-618-2026-10-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-618-2026-10-05/</guid><description>&lt;p&gt;Build 2026.10.05: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.617</title><link>https://proxmoxvex.com/changelog/#release--1-2-617-2026-10-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-617-2026-10-05/</guid><description>&lt;p&gt;Build 2026.10.05: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.616</title><link>https://proxmoxvex.com/changelog/#release--1-2-616-2026-10-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-616-2026-10-05/</guid><description>&lt;p&gt;Build 2026.10.05: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.615</title><link>https://proxmoxvex.com/changelog/#release--1-2-615-2026-10-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-615-2026-10-05/</guid><description>&lt;p&gt;Build 2026.10.05: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.614</title><link>https://proxmoxvex.com/changelog/#release--1-2-614-2026-10-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-614-2026-10-05/</guid><description>&lt;p&gt;Build 2026.10.05: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.613</title><link>https://proxmoxvex.com/changelog/#release--1-2-613-2026-10-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-613-2026-10-04/</guid><description>&lt;p&gt;Build 2026.10.04: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.612</title><link>https://proxmoxvex.com/changelog/#release--1-2-612-2026-10-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-612-2026-10-04/</guid><description>&lt;p&gt;Build 2026.10.04: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.611</title><link>https://proxmoxvex.com/changelog/#release--1-2-611-2026-10-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-611-2026-10-04/</guid><description>&lt;p&gt;Build 2026.10.04: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.610</title><link>https://proxmoxvex.com/changelog/#release--1-2-610-2026-10-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-610-2026-10-04/</guid><description>&lt;p&gt;Build 2026.10.04: Full build pipeline&lt;/p&gt;&lt;p&gt;opnsense plugin 1.0.6: write `jobs.json` atomically (temp file +&lt;/p&gt;</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.609</title><link>https://proxmoxvex.com/changelog/#release--1-2-609-2026-10-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-609-2026-10-04/</guid><description>&lt;p&gt;Build 2026.10.04: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.608</title><link>https://proxmoxvex.com/changelog/#release--1-2-608-2026-10-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-608-2026-10-04/</guid><description>&lt;p&gt;Build 2026.10.04: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.607</title><link>https://proxmoxvex.com/changelog/#release--1-2-607-2026-10-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-607-2026-10-04/</guid><description>&lt;p&gt;Build 2026.10.04: Full build pipeline&lt;/p&gt;&lt;p&gt;i18n: filled every dangling `data-i18n`/`t()` key reference flagged by&lt;/p&gt;&lt;p&gt;i18n scanner: `--scan-code` now honours `i18n:ignore` on dangling key&lt;/p&gt;</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.606</title><link>https://proxmoxvex.com/changelog/#release--1-2-606-2026-10-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-606-2026-10-04/</guid><description>&lt;p&gt;Build 2026.10.04: Full build pipeline&lt;/p&gt;&lt;p&gt;Renamed the `ProxmoxVExSettings` label to plain &quot;Settings&quot; in all four&lt;/p&gt;</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.605</title><link>https://proxmoxvex.com/changelog/#release--1-2-605-2026-10-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-605-2026-10-03/</guid><description>&lt;p&gt;Build 2026.10.03: Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.604</title><link>https://proxmoxvex.com/changelog/#release--1-2-604-2026-10-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-604-2026-10-03/</guid><description>&lt;p&gt;Build 2026.10.03: Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.603</title><link>https://proxmoxvex.com/changelog/#release--1-2-603-2026-10-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-603-2026-10-03/</guid><description>&lt;p&gt;Build 2026.10.03: Full build pipeline&lt;/p&gt;&lt;p&gt;Fixed spec-108 deep link: `?settings_section=&lt;id&gt;` now resolves in the&lt;/p&gt;&lt;p&gt;`test_deeplink_restore::test_settings_section_implies_settings_tab`&lt;/p&gt;</description><pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.602</title><link>https://proxmoxvex.com/changelog/#release--1-2-602-2026-10-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-602-2026-10-03/</guid><description>&lt;p&gt;Build 2026.10.03: Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.601</title><link>https://proxmoxvex.com/changelog/#release--1-2-601-2026-10-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-601-2026-10-03/</guid><description>&lt;p&gt;Build 2026.10.03: Full build pipeline&lt;/p&gt;&lt;p&gt;Added a development-preview banner to the top of the app shell and&lt;/p&gt;&lt;p&gt;ceph-dashboard 1.0.4 + sdn-controller 1.0.4: manifests now declare&lt;/p&gt;</description><pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.600</title><link>https://proxmoxvex.com/changelog/#release--1-2-600-2026-10-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-600-2026-10-03/</guid><description>&lt;p&gt;Build 2026.10.03: Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.599</title><link>https://proxmoxvex.com/changelog/#release--1-2-599-2026-10-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-599-2026-10-03/</guid><description>&lt;p&gt;Build 2026.10.03: Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.598</title><link>https://proxmoxvex.com/changelog/#release--1-2-598-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-598-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.597</title><link>https://proxmoxvex.com/changelog/#release--1-2-597-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-597-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.596</title><link>https://proxmoxvex.com/changelog/#release--1-2-596-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-596-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Full build pipeline&lt;/p&gt;&lt;p&gt;i18n sync: fixed `ollama_sync.py` re-translating the same key every run&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.595</title><link>https://proxmoxvex.com/changelog/#release--1-2-595-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-595-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Build&lt;/p&gt;&lt;p&gt;Fix: Settings → Updates crashed with a React error boundary when the&lt;/p&gt;&lt;p&gt;Console modal: LXC guests now open on the xterm.js terminal tab by&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.594</title><link>https://proxmoxvex.com/changelog/#release--1-2-594-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-594-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Full build pipeline&lt;/p&gt;&lt;p&gt;Security (SEC-019): blanked the committed `pgx_` status-page `auth_key`&lt;/p&gt;&lt;p&gt;Security (SEC-004): upgraded `oauthlib` to the pinned `&gt;=4.0.0,&lt;5.0`&lt;/p&gt;&lt;p&gt;Security (SEC-022): added `scripts/check_plugin_config_secrets.py` +&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.593</title><link>https://proxmoxvex.com/changelog/#release--1-2-593-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-593-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.592</title><link>https://proxmoxvex.com/changelog/#release--1-2-592-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-592-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Full build pipeline&lt;/p&gt;&lt;p&gt;Release tooling: `bump-version.py::update_changelog` now sweeps stale&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.591</title><link>https://proxmoxvex.com/changelog/#release--1-2-591-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-591-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Build and serve&lt;/p&gt;&lt;p&gt;Ecosystem: `proxmoxvex-auth` central identity service live at&lt;/p&gt;&lt;p&gt;Plugins: fix vm-snapshot-policy UI calling `/api/api/*` (double prefix —&lt;/p&gt;&lt;p&gt;Settings: apiTokens tab renders a graceful &quot;plugin not enabled&quot; state&lt;/p&gt;&lt;p&gt;status_page: drop `frame-ancestors` from the meta CSP — browsers ignore&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.590</title><link>https://proxmoxvex.com/changelog/#release--1-2-590-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-590-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.589</title><link>https://proxmoxvex.com/changelog/#release--1-2-589-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-589-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.588</title><link>https://proxmoxvex.com/changelog/#release--1-2-588-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-588-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Full build pipeline&lt;/p&gt;&lt;p&gt;i18n: refined de/es/fr translations across 27 namespaces (core,&lt;/p&gt;&lt;p&gt;Release tooling: `bump-version.py`&apos;s `sync_public_repo` now appends the&lt;/p&gt;&lt;p&gt;Release tooling: `_unreleased_bullets` accumulates continuation lines,&lt;/p&gt;&lt;p&gt;Pipeline: `scripts/full-pipeline.sh` caps xdist workers via&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.587</title><link>https://proxmoxvex.com/changelog/#release--1-2-587-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-587-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Build&lt;/p&gt;&lt;p&gt;Security (ISS-030): shipped updater now verifies release authenticity.&lt;/p&gt;&lt;p&gt;Tests: fixed time-bomb in `test_status_page_plugin.py` — the&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.586</title><link>https://proxmoxvex.com/changelog/#release--1-2-586-2026-10-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-586-2026-10-02/</guid><description>&lt;p&gt;Build 2026.10.02: Full build pipeline&lt;/p&gt;&lt;p&gt;Docs: regenerated all 77 `docs/plugins/*.md` reference pages via&lt;/p&gt;&lt;p&gt;Docs: `docs/wiki.md` version header updated 1.2.143 → 1.2.585&lt;/p&gt;&lt;p&gt;`web/data/specs.json`: regenerated by landing build-specs with&lt;/p&gt;</description><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.585</title><link>https://proxmoxvex.com/changelog/#release--1-2-585-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-585-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Full build pipeline&lt;/p&gt;&lt;p&gt;Tests: updated 5 stale `test_i18n_locale_parity` assertions&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.584</title><link>https://proxmoxvex.com/changelog/#release--1-2-584-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-584-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.583</title><link>https://proxmoxvex.com/changelog/#release--1-2-583-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-583-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Full build pipeline&lt;/p&gt;&lt;p&gt;`web/i18n/ollama_sync.py`: translation memory — a SQLite DB&lt;/p&gt;&lt;p&gt;i18n: removed 33 corrupt keys from `native/docker_swarm` locale files —&lt;/p&gt;&lt;p&gt;vm-tag-enforcer — full gold-standard UI overhaul (tabs, stat cards,&lt;/p&gt;&lt;p&gt;i18n: shipped languages cut from 14 to 4 (en, de, es, fr). Removed&lt;/p&gt;&lt;p&gt;vm-resource-quotas — full gold-standard UI overhaul (6 tabs, stat&lt;/p&gt;&lt;p&gt;vm-rightsizing — full gold-standard UI overhaul (command bar, 4 tabs,&lt;/p&gt;&lt;p&gt;vm-hibernate-scheduler — full gold-standard UI overhaul (eyebrow, command&lt;/p&gt;&lt;p&gt;vm-migration-planner — full gold-standard UI overhaul (command bar, 6&lt;/p&gt;&lt;p&gt;`web/i18n/ollama_sync.py`: `--workers N` (env `OLLAMA_WORKERS`,&lt;/p&gt;&lt;p&gt;`web/i18n/ollama_sync.py`: live progress for every run mode — a&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.582</title><link>https://proxmoxvex.com/changelog/#release--1-2-582-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-582-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Console connect timeout + CSP noVNC loader fix&lt;/p&gt;&lt;p&gt;Converter shrink auto-size — blank &quot;Disk Size (GB)&quot; on shrink_lxc/\&lt;/p&gt;&lt;p&gt;Converter modal — required form fields are now validated client-side&lt;/p&gt;&lt;p&gt;CSP: noVNC loader no longer uses an injected `&lt;script type=&quot;module&quot;&gt;`&lt;/p&gt;&lt;p&gt;i18n: translated plugin namespaces (cert-manager, client-billing-portal,&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.580</title><link>https://proxmoxvex.com/changelog/#release--1-2-580-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-580-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Full build pipeline&lt;/p&gt;&lt;p&gt;Console connect timeout (&quot;consoles hang on Connecting…&quot;):&lt;/p&gt;&lt;p&gt;All three console transports (VM VNC via noVNC, node SSH shell,&lt;/p&gt;&lt;p&gt;`connectVnc()` polling-fallback failures (no/failed/empty&lt;/p&gt;&lt;p&gt;Demo sandbox: `POST /api/ws/token` is allowlisted (session-scoped&lt;/p&gt;&lt;p&gt;Demo cluster mock answers `vncproxy`/`termproxy` POSTs with real&lt;/p&gt;&lt;p&gt;New `tests/e2e/test_console.py` (6 tests) drives the console flows&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.579</title><link>https://proxmoxvex.com/changelog/#release--1-2-579-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-579-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.578</title><link>https://proxmoxvex.com/changelog/#release--1-2-578-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-578-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.577</title><link>https://proxmoxvex.com/changelog/#release--1-2-577-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-577-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.576</title><link>https://proxmoxvex.com/changelog/#release--1-2-576-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-576-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Full build pipeline&lt;/p&gt;&lt;p&gt;Security hardening (audit SEC-001..SEC-018):&lt;/p&gt;&lt;p&gt;API auth allowlist split into exact-match paths vs real subtree&lt;/p&gt;&lt;p&gt;Plugin proxy now requires `plugins.manage` on mutating (non-GET)&lt;/p&gt;&lt;p&gt;Demo `auto-login` validates `redirect` is a local path — open&lt;/p&gt;&lt;p&gt;Appliance creator no longer ships a default root password;&lt;/p&gt;&lt;p&gt;`update.sh` treats a missing `.asc` signature or missing&lt;/p&gt;&lt;p&gt;Licensing admin sessions now carry an absolute `expires_at`&lt;/p&gt;&lt;p&gt;Plugin artifact downloads accept the licence key via&lt;/p&gt;&lt;p&gt;Licensing CORS default narrowed; wildcard+credentials removed.&lt;/p&gt;&lt;p&gt;Support container runs as non-root `appuser` (uid 10001) with a&lt;/p&gt;&lt;p&gt;Support intake endpoints accept `X-License-Key` header (query/body&lt;/p&gt;&lt;p&gt;`oauthlib` floor pinned in requirements.&lt;/p&gt;&lt;p&gt;Landing site `_headers` gained X-Content-Type-Options, Referrer-&lt;/p&gt;&lt;p&gt;Tracked `.venv`/`.test-venv` artifacts removed from the git index;&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.575</title><link>https://proxmoxvex.com/changelog/#release--1-2-575-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-575-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Full build pipeline&lt;/p&gt;&lt;p&gt;In-plugin configuration UI: every plugin iframe now ships a shared&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.574</title><link>https://proxmoxvex.com/changelog/#release--1-2-574-2026-10-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-574-2026-10-01/</guid><description>&lt;p&gt;Build 2026.10.01: Full build pipeline&lt;/p&gt;&lt;p&gt;Console/policy hygiene on auth-proxied deployments: the web-manifest&lt;/p&gt;&lt;p&gt;Plugin capability audit (user-reported: no way to add a Dell iDRAC&lt;/p&gt;&lt;p&gt;BMC family (dell-idrac, hpe-ilo, lenovo-xclarity, redfish-bmc,&lt;/p&gt;&lt;p&gt;apc-ups: the UI was a read-only shell over a backend supporting&lt;/p&gt;&lt;p&gt;eaton-ipm: the UI called routes that didn&apos;t exist (`/pdus/*`,&lt;/p&gt;&lt;p&gt;raritan-pdu / servertech-pdu: added outlet edit (PATCH) modals.&lt;/p&gt;&lt;p&gt;nutanix-ahv / xcp-ng: added per-connection inventory refresh actions&lt;/p&gt;&lt;p&gt;veeam: added RPO rule create/delete to the Compliance panel.&lt;/p&gt;&lt;p&gt;Cross-plugin fix: the shared plugin-ui.css `.modal { display:none }`&lt;/p&gt;&lt;p&gt;Disk Usage Explorer fix — pressing Precompute could leave the tree&lt;/p&gt;&lt;p&gt;Converter resize safety — `shrink_vm`/`expand_vm`/`shrink_lxc`/&lt;/p&gt;</description><pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.573</title><link>https://proxmoxvex.com/changelog/#release--1-2-573-2026-09-30/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-573-2026-09-30/</guid><description>&lt;p&gt;Build 2026.09.30: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.572</title><link>https://proxmoxvex.com/changelog/#release--1-2-572-2026-09-30/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-572-2026-09-30/</guid><description>&lt;p&gt;Build 2026.09.30: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.571</title><link>https://proxmoxvex.com/changelog/#release--1-2-571-2026-09-30/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-571-2026-09-30/</guid><description>&lt;p&gt;Build 2026.09.30: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.570</title><link>https://proxmoxvex.com/changelog/#release--1-2-570-2026-09-30/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-570-2026-09-30/</guid><description>&lt;p&gt;Build 2026.09.30: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.569</title><link>https://proxmoxvex.com/changelog/#release--1-2-569-2026-09-30/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-569-2026-09-30/</guid><description>&lt;p&gt;Build 2026.09.30: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.568</title><link>https://proxmoxvex.com/changelog/#release--1-2-568-2026-09-30/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-568-2026-09-30/</guid><description>&lt;p&gt;Build 2026.09.30: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.567</title><link>https://proxmoxvex.com/changelog/#release--1-2-567-2026-09-30/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-567-2026-09-30/</guid><description>&lt;p&gt;Build 2026.09.30: Full build pipeline&lt;/p&gt;&lt;p&gt;Fixed durable-job regressions found by deployed-pipeline contract tests:&lt;/p&gt;&lt;p&gt;Contract tests updated to the durable-job contract: mutating routes return&lt;/p&gt;&lt;p&gt;Fixed copy-paste logger names in 11 plugins&apos; jobs.py (all said&lt;/p&gt;&lt;p&gt;e2e: carbon-footprint DELETE/settings assertions now poll for the async&lt;/p&gt;</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.566</title><link>https://proxmoxvex.com/changelog/#release--1-2-566-2026-09-30/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-566-2026-09-30/</guid><description>&lt;p&gt;Build 2026.09.30: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.565</title><link>https://proxmoxvex.com/changelog/#release--1-2-565-2026-09-30/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-565-2026-09-30/</guid><description>&lt;p&gt;Plugin ecosystem overhaul — all 74 non-VUM plugins brought to the VM&lt;/p&gt;&lt;p&gt;Spec Kit packages for every plugin under specs/plugin-overhaul/.&lt;/p&gt;&lt;p&gt;~700 new unit tests + comprehensive Playwright e2e coverage per plugin.&lt;/p&gt;&lt;p&gt;plugin_data_bridge: node disk + storage usage now merge from&lt;/p&gt;&lt;p&gt;plugin-theme.js/plugin-ux.js: normalized host theme names to the plugin&lt;/p&gt;&lt;p&gt;converter: SSH commands echo an `EXIT:&lt;rc&gt;` marker so remote command failure&lt;/p&gt;</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.564</title><link>https://proxmoxvex.com/changelog/#release--1-2-564-2026-09-30/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-564-2026-09-30/</guid><description>&lt;p&gt;Build 2026.09.30: Full build pipeline&lt;/p&gt;&lt;p&gt;Converter pre-flight — fixed the &quot;source workload does not exist or is&lt;/p&gt;&lt;p&gt;i18n tooling — ported the advanced translation-check pipeline onto the&lt;/p&gt;&lt;p&gt;Identity/security plugin wave — RBAC Explorer 1.0.3, Cert Manager 1.0.3,&lt;/p&gt;&lt;p&gt;Backup Verification Runner 1.0.3 — verify runs now perform a real&lt;/p&gt;&lt;p&gt;VM Update Manager 1.2.3 — light-theme fix: the plugin&apos;s light-palette CSS&lt;/p&gt;</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.563</title><link>https://proxmoxvex.com/changelog/#release--1-2-563-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-563-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.562</title><link>https://proxmoxvex.com/changelog/#release--1-2-562-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-562-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: VUM 1.2.2 deploy; test suite fixes&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.561</title><link>https://proxmoxvex.com/changelog/#release--1-2-561-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-561-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: VUM 1.2.2 WinRM hint; test isolation fixes&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.560</title><link>https://proxmoxvex.com/changelog/#release--1-2-560-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-560-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: VM Update Manager 1.2.2: WinRM setup hint + pywinrm dependency&lt;/p&gt;&lt;p&gt;VM Update Manager 1.2.2 — WinRM prerequisite hint in the guest form: selecting a Windows OS family shows an inline &apos;winrm quickconfig -q&apos; setup hint so the WinRM-enablement step isn&apos;t discoverable only after a failed connection.&lt;/p&gt;&lt;p&gt;VM Update Manager 1.2.1 — Windows support made deployable: pywinrm added to requirements.txt (the WinRM driver lazy-imported it but the image never installed it, so Windows guests failed at connect time); guest forms now auto-suggest the WinRM port (5985) when the OS family is Windows and restore 22 otherwise.&lt;/p&gt;&lt;p&gt;VM Update Manager 1.2.0 — Plugin jobs now surface in the main app&apos;s bottom Tasks tray (new core plugin_tasks registry merged into SSE broadcasts + cluster tasks API; clicking a plugin task shows its job log); History blade pagination (Load more) and Cancel for queued jobs; new Maintenance blade listing every guest&apos;s recurring schedule (cron/auto-apply/update scope/reboot policy/next run) with per-machine edit + enable/disable, and scheduled applies now inherit each machine&apos;s maintenance config; notifications bell with a failed-jobs inbox (badge, view/retry actions); update detail pane on the Updates blade (classification, per-machine current versions, pending-reboot flags, KB links, one-click scoped install); update rows now carry per-machine detail from the latest assessment.&lt;/p&gt;&lt;p&gt;VM Update Manager 1.1.0 — History blade filters (status/type/machine + free-text search, Azure run-history parity); Retry action on failed jobs (re-queues the same job type); new Reboot guest action — enabled when the guest reports a pending reboot, queues a real &apos;reboot&apos; job (driver.request_reboot + post-probe); Updates blade row multi-select with &quot;Install selected&quot; opening the wizard in include-list scope; &apos;View updates&apos; machine action jumping to a filtered Updates blade; stale-assessment badge on checks older than 7 days; localized job-type labels; job-log &apos;Status&apos; header fix; action menus flip upward near the viewport bottom so all items stay clickable.&lt;/p&gt;&lt;p&gt;VM Update Manager 1.0.10 — job-log Actions cells now show a muted &quot;No actions&quot; placeholder for terminal jobs with nothing actionable (Watch/Recover only exist for live/failed-apply rows); empty cells looked like rendering gaps in the Azure-style table.&lt;/p&gt;&lt;p&gt;VM Update Manager 1.0.9 — i18n self-heal: the plugin now fetches its own dictionaries with a cache-buster (bypassing stale browser-cached en.json and any already-registered parent namespace), re-registers them into the parent i18n bridge, and t() degrades missing keys to the fresh English dict instead of leaking raw key names (e.g. ustPending, wizNoMachines). Core web/i18n/i18n.js loadPluginNamespace also cache-busts + no-store fetches so all plugin namespaces revalidate.&lt;/p&gt;&lt;p&gt;VM Update Manager 1.0.8 — aggregated Azure-style Updates blade: the tab now lists each distinct pending update once with the machines still needing it (new GET updates endpoint backed by list_pending_updates), with machine/classification filters, name search, and per-update + bulk &quot;Install updates&quot; actions that open the one-time-update wizard prefilled with scope, include list, and machines. Fixes the badge-vs-tab mismatch where the badge counted all pending updates but the tab showed a single guest&apos;s (possibly empty) package list.&lt;/p&gt;&lt;p&gt;VM Update Manager 1.0.7 — guest reachability + OS detection hardening: every SSH/WinRM entry point TCP-probes first so offline guests fail fast with &apos;unreachable&apos; (stored as guest status, bucketed in Machines/Overview) instead of the misleading &apos;could not detect OS family&apos; after full SSH timeouts; _detect_os now uses a compound probe (os-release → /usr/lib/os-release → lsb_release → family release files → uname → Windows `ver` fallback) with a widened ID/ID_LIKE map (kali/raspbian/fedora/amzn/opensuse/arch/freebsd); detected family is persisted back to the guest record on successful checks and detected at bulk-add time.&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.559</title><link>https://proxmoxvex.com/changelog/#release--1-2-559-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-559-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: Full build pipeline&lt;/p&gt;&lt;p&gt;VM Update Manager 1.0.6 — Azure Update Manager parity: a 4-step &quot;Install one-time updates&quot; wizard blade (machine picker chips, update classification — all/security-only/include-list, include+exclude package/KB lists, maintenance reboot options — never / if-required / always — and a review step) replaces the confirm modal for both the command bar and the Machines install bar; apply options ride through the job payload into the engine, which reboots per policy after a successful apply; drivers gained `request_reboot` (shutdown -r / Restart-Computer) plus scoped apply commands (apt/apk/dnf/yum filtered upgrade sets, WUA KB/severity filtering). Visual refresh to Azure Fluent: blue accent, eyebrow header, filter-pill bar, Security/Other split columns with OS icon glyphs, Overview status-summary + recent-runs tables.&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.558</title><link>https://proxmoxvex.com/changelog/#release--1-2-558-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-558-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: Full build pipeline&lt;/p&gt;&lt;p&gt;Test harness: `_wait_until` in the vm-update-manager e2e suite now polls via `page.wait_for_timeout` instead of `time.sleep` — sync Playwright only dispatches `page.route` handlers while the main thread is inside a Playwright call, so a pure-Python sleep loop starved the dispatcher and intermittently trapped intercepted POSTs forever regardless of timeout length&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.557</title><link>https://proxmoxvex.com/changelog/#release--1-2-557-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-557-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.556</title><link>https://proxmoxvex.com/changelog/#release--1-2-556-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-556-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.555</title><link>https://proxmoxvex.com/changelog/#release--1-2-555-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-555-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.554</title><link>https://proxmoxvex.com/changelog/#release--1-2-554-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-554-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.553</title><link>https://proxmoxvex.com/changelog/#release--1-2-553-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-553-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: Full build pipeline&lt;/p&gt;&lt;p&gt;plugins/vm-update-manager 1.0.5: Azure Update Manager-style redesign. New Overview tab (stat cards, four SVG donut charts — update status / OS distribution / 30-day run status / pending-update security split — plus a coverage banner with an &quot;Enable now&quot; bulk-schedule action), Machines view with clickable stat tiles that filter the table, an &quot;Install updates now for N selected&quot; bar, group-by rows (OS family / status / cluster), and update-status + pending-reboot columns. History tab gains a run-detail summary card above the console. Command bar (Refresh / Check for updates / One-time update / Schedule updates / Settings) sits above the tabs on every view. Backend: `reboot_required` column probed on every check (Debian/RHEL flag files over SSH, CBS/WindowsUpdate/PendingFileRenameOperations registry keys over WinRM), `GET overview` aggregation endpoint, and `POST guests/schedule` bulk policy update (cron validated, cluster-access checked, capped at 200 ids). Tests: +5 unit tests (47 total) and +14 e2e tests (33 total) covering every new surface; e2e fixture deep-copies guest dicts to stop cross-test mutation.&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.552</title><link>https://proxmoxvex.com/changelog/#release--1-2-552-2026-09-29/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-552-2026-09-29/</guid><description>&lt;p&gt;Build 2026.09.29: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.551</title><link>https://proxmoxvex.com/changelog/#release--1-2-551-2026-09-28/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-551-2026-09-28/</guid><description>&lt;p&gt;Build 2026.09.28: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.550</title><link>https://proxmoxvex.com/changelog/#release--1-2-550-2026-09-28/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-550-2026-09-28/</guid><description>&lt;p&gt;Build 2026.09.28: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.549</title><link>https://proxmoxvex.com/changelog/#release--1-2-549-2026-09-28/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-549-2026-09-28/</guid><description>&lt;p&gt;Build 2026.09.28: Full build pipeline&lt;/p&gt;&lt;p&gt;Settings → Plugins: after a &quot;Check for updates&quot; run, plugins with a pending update are now pinned to the top of the plugin list so they are visible without scrolling. The reorder is temporary — the original server order returns once updates are applied or the modal is reopened.&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.548</title><link>https://proxmoxvex.com/changelog/#release--1-2-548-2026-09-28/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-548-2026-09-28/</guid><description>&lt;p&gt;Build 2026.09.28: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.547</title><link>https://proxmoxvex.com/changelog/#release--1-2-547-2026-09-28/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-547-2026-09-28/</guid><description>&lt;p&gt;Build 2026.09.28: Full build pipeline&lt;/p&gt;&lt;p&gt;Test harness: fixed a parallel-run flake where `_repoint_auth` iterated `sys.modules` live and crashed with &quot;dictionary changed size during iteration&quot; when xdist workers/gevent threads imported modules mid-iteration. The loop now iterates a snapshot (`list(sys.modules.values())`).&lt;/p&gt;&lt;p&gt;api-token-manager plugin bumped to 1.0.3 so the license-server update path could be exercised end-to-end (check-update → licensed download → SHA-256/integrity verification → unpack → unload/reload) on a live deployment.&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.546</title><link>https://proxmoxvex.com/changelog/#release--1-2-546-2026-09-28/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-546-2026-09-28/</guid><description>&lt;p&gt;Build 2026.09.28: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.545</title><link>https://proxmoxvex.com/changelog/#release--1-2-545-2026-09-28/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-545-2026-09-28/</guid><description>&lt;p&gt;Build 2026.09.28: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.544</title><link>https://proxmoxvex.com/changelog/#release--1-2-544-2026-09-28/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-544-2026-09-28/</guid><description>&lt;p&gt;Build 2026.09.28: Full build pipeline&lt;/p&gt;&lt;p&gt;Rolling Update Orchestrator: reboot is no longer mandatory. The template picker now offers five new presets alongside Safe and Fast — Soft update (evacuate + update + resume, no reboot), Conditional and Fast conditional (reboot only when the node reports `/var/run/reboot-required`), In-place (update only), and Drain only — backed by a server-side template catalog (`GET templates`), two new plan steps (`resume` exits maintenance without rebooting, `reboot_if_needed` probes the node over SSH and reboots only when required), and server-side step-name validation on plan creation. Plugin bumped to 1.0.2.&lt;/p&gt;&lt;p&gt;VM Update Manager UX overhaul (spec 111): the respond modal now detects common apt/dpkg/debconf prompts and offers labelled one-click answer buttons plus recent output context instead of a bare input; a live monitor on the Jobs tab shows every in-flight VM updating in parallel with its current stage; bulk Apply uses the new guest_ids endpoint so jobs run concurrently; and failed applies surface an ordered recovery plan — a checkable, reorderable fix catalog (index refresh, lock cleanup, dpkg repair, broken deps, Windows service/cache/image repair, retry) that the engine executes top-to-bottom, retrying the upgrade after each fix and stopping the moment updates install cleanly. Every check and apply now refreshes the distribution&apos;s package index first (apt-get update / apk update / dnf|yum makecache / a Windows Update scan) as a visible step.&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.543</title><link>https://proxmoxvex.com/changelog/#release--1-2-543-2026-09-28/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-543-2026-09-28/</guid><description>&lt;p&gt;Build 2026.09.28: Full build pipeline&lt;/p&gt;&lt;p&gt;PBS `ssl_verify` now defaults to off consistently (ISS-019): the model constructor read `config.get(&quot;ssl_verify&quot;, True)` while the save path, DB schema, and UI checkboxes all default False — an API-created PBS server verified TLS until its first re-save silently persisted 0. Aligned to `False` everywhere, matching the VMware surface; verification remains opt-in for self-signed homelab deployments.&lt;/p&gt;&lt;p&gt;Public release sync hardening (ISS-028): `scripts/bump-version.py`&apos;s `update_files` manifest regeneration now skips symlinks (dev-runtime artifacts such as `native/*/config.json` volume links no longer ship as dangling absolute paths in the tarball), the archive builder refuses symlinked manifest entries, and `sync_public_repo` re-signs the public repo&apos;s `version.json` via `sign-version.sh` after patching managed fields so `update.sh`&apos;s fatal-on-bad-signature metadata check never sees a stale `.asc`.&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.542</title><link>https://proxmoxvex.com/changelog/#release--1-2-542-2026-09-27/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-542-2026-09-27/</guid><description>&lt;p&gt;Build 2026.09.27: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.541</title><link>https://proxmoxvex.com/changelog/#release--1-2-541-2026-09-27/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-541-2026-09-27/</guid><description>&lt;p&gt;Build 2026.09.27: Full build pipeline&lt;/p&gt;&lt;p&gt;Backup SLA tab can now trigger backups directly: a &quot;Backup Now&quot; button in the tab header and per-row actions open a focus-trapped `SlaBackupModal` (guest picker, backup-capable target storage filtered to the guest&apos;s node incl. shared-storage node restrictions, vzdump mode, compression, notes) that posts to the existing `backups/create` endpoint and hands the UPID to the global backup-progress task surface. New i18n keys (`backupNow`, `backupNowDesc`, `startBackup`, `backupFailed`, `noBackupStorage`) added to all 14 core locales.&lt;/p&gt;</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.540</title><link>https://proxmoxvex.com/changelog/#release--1-2-540-2026-09-27/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-540-2026-09-27/</guid><description>&lt;p&gt;Build 2026.09.27: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.539</title><link>https://proxmoxvex.com/changelog/#release--1-2-539-2026-09-27/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-539-2026-09-27/</guid><description>&lt;p&gt;Build 2026.09.27: Full build pipeline&lt;/p&gt;&lt;p&gt;Test-infrastructure de-flake for the xdist suite: `tests/conftest.py`&apos;s `_integration_app` fixture reloaded `ProxmoxVEx.api.*` modules deepest-first, so `api.vms.*` consumers re-decorated the shared Blueprint from `api.vms.common` before the provider reloaded — reload crashes left later modules (incl. `api.stepup`) with stale auth bindings, and the silent variant orphaned routes onto the stale Blueprint (intermittent 404 &quot;Route not found&quot; in `test_api_token_scopes`/`test_request_validation`/`test_vms_status_batch`). The reload now identifies Blueprint providers statically (`bp.import_name == mod.__name__`), orders providers → consumers → package `__init__`s, retries assertion-failed modules, and verifies Blueprint identity convergence. `tests/test_stepup.py::TestStepupVerifyEndpoint` also hardened: plugin test utils permanently leak `auth.require_auth = &lt;pass-through&gt;`, and if `api.stepup` was imported while a fake was bound on the worker, its views never attach `request.session` so step-up elevation landed on an ephemeral dict — the `client` fixture now pins `require_auth` back to the real decorator and reloads `api.stepup` before registering its blueprint.&lt;/p&gt;</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.538</title><link>https://proxmoxvex.com/changelog/#release--1-2-538-2026-09-27/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-538-2026-09-27/</guid><description>&lt;p&gt;Build 2026.09.27: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.537</title><link>https://proxmoxvex.com/changelog/#release--1-2-537-2026-09-27/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-537-2026-09-27/</guid><description>&lt;p&gt;Build 2026.09.27: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.536</title><link>https://proxmoxvex.com/changelog/#release--1-2-536-2026-09-27/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-536-2026-09-27/</guid><description>&lt;p&gt;Build 2026.09.27: Full build pipeline&lt;/p&gt;&lt;p&gt;Native integration config saves failed with `Failed to save config` on the read-only deployment rootfs: every native module wrote `config.json` inside its own package dir (`native/&lt;id&gt;/config.json`), and the `persist_native_configs` symlink-into-volume redirect can&apos;t run there either (`os.symlink`/`os.unlink` under `/app/ProxmoxVEx/native/` hit `EROFS`). Added `config_persist.resolve_config_path(plugin_id, plugin_dir)`: `persist_native_configs` now binds `_VOLUME_ROOT` to `CONFIG_DIR/native` at startup (before `register_native` imports modules) and seeds each module&apos;s volume `config.json` from any real source-tree file, so modules that resolve `CONFIG_PATH` at import land on the writable volume path directly — no symlink needed. Modules falling back to the source dir still work on writable dev trees. Applied to all 10 file-backed integrations (active_directory, docker_swarm, netapp_storage (+`core/_helpers.py` reader), opnsense, pfsense, proxmox_backup_server, truenas, vmware_vcenter, vyos, zabbix) and `registry._native_config_path`. TrueNAS `alerts_state.json` (runtime-written alert-ack data) moved next to the resolved config with a one-time migration of the bundled file. Demo-mode sanitize now writes the *volume* file since that&apos;s what modules read. Regression tests in `tests/test_native_registry.py`: `test_resolves_to_volume_when_bound`, `test_resolve_config_path_fallback`, `test_persist_survives_readonly_module_dir`.&lt;/p&gt;&lt;p&gt;PBS view leaked `/* PBS view */` as visible text: a past i18n codemod converted the JSX comment at the top of the PBS detail view into `t(&apos;pbsView&apos;)`, so every locale shipped a `pbsView` key whose value was the literal comment text rendered above the PBS header. Restored `{/* PBS view */}` in `dashboard_panels.js` and removed the bogus `pbsView` key from all 14 core locales plus its `ALLOWED_MISSING` entry in `web/i18n/validate.py`.&lt;/p&gt;&lt;p&gt;Modal focus-steal fix: `useFocusTrap` in `web/src/ui.js` depended on the `onEscape` callback&apos;s identity, which changes on every parent re-render because every call site passes an inline arrow (e.g. `PbsEditModal`&apos;s `onClose={() =&gt; setShowAddPBS(false)}`). Each poll-driven re-render tore the trap down — cleanup restored focus to the pre-modal element (or `&lt;body&gt;` if it was detached), then the effect re-ran `first()?.focus()`, yanking focus to the first field. Most visible in the PBS edit modal where a disconnected server&apos;s reconnect/status churn re-renders constantly, making it impossible to type in any field except Name. `onEscape` is now kept in a ref so the trap only mounts/unmounts on `active` flips; cleanup also guards `isConnected` before restoring focus. Fixes all 8 useFocusTrap-wired modals.&lt;/p&gt;</description><pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.535</title><link>https://proxmoxvex.com/changelog/#release--1-2-535-2026-09-26/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-535-2026-09-26/</guid><description>&lt;p&gt;Build 2026.09.26: Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.534</title><link>https://proxmoxvex.com/changelog/#release--1-2-534-2026-09-26/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-534-2026-09-26/</guid><description>&lt;p&gt;Build 2026.09.26: Full build pipeline&lt;/p&gt;&lt;p&gt;Paid-plugin unpack EACCES fix: `PluginDeliveryService._unpack_artifact` used to `shutil.rmtree` the installed plugin before validating/extracting the new artifact, so any foreign-owned entries under `plugins/&lt;id&gt;` (e.g. root-owned `__pycache__`/`drivers`/`i18n` left in the plugins volume by `docker cp` syncs or a root-seeded image) failed the whole update with `Permission denied`. The unpack now extracts into a `.&lt;id&gt;.staging-*` sibling and swaps it in via `os.rename` — which only needs write permission on `plugins/` itself — then removes the old tree best-effort (unremovable leftovers are moved aside to `.&lt;id&gt;.old-*` and logged, not fatal). As a side effect the previously-installed version now survives a rejected/corrupt archive instead of being deleted before validation ran. `scripts/check-and-sync-plugin-code.sh` now restores `ProxmoxVEx:ProxmoxVEx` ownership across `/app/plugins` after `docker cp` syncs (docker cp writes as root; the chmod-only fixup left synced files and auto-created intermediate dirs root-owned), which also heals pre-existing foreign-owned leftovers. Regression tests: `test_unpack_artifact_replaces_existing`, `test_unpack_artifact_failure_preserves_existing`, `test_unpack_artifact_survives_unremovable_old_tree`. Live prod volume repaired (`chown -R`), 34 root-owned entries cleared.&lt;/p&gt;</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.533</title><link>https://proxmoxvex.com/changelog/#release--1-2-533-2026-09-26/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-533-2026-09-26/</guid><description>&lt;p&gt;Build 2026.09.26: Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.532</title><link>https://proxmoxvex.com/changelog/#release--1-2-532-2026-09-26/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-532-2026-09-26/</guid><description>&lt;p&gt;Build 2026.09.26: Full build pipeline&lt;/p&gt;&lt;p&gt;Pipeline fix: `scripts/full-pipeline.sh` regenerated the license-server `plugins.json` from manifests (phase `generate-plugin-directory`, and again inside `web/Dev/build.sh` during `frontend-build`) but never repackaged paid-plugin artifacts, so the run stripped `latest_version`/`latest_artifact_hash` from all 65 paid entries and the workspace task&apos;s licensing deploy synced that stripped catalog to `/opt`. The `/v1/plugins/&lt;id&gt;/updates` endpoint still resolves the latest via the manifest `version` field, so update checks kept working — but the deployed catalog diverged from the committed one, and any manifest bump without a repackage leaves `/updates` 404ing on the missing artifact dir. New `package-paid-plugins` phase runs `scripts/package_paid_plugins.py` after `frontend-build` — the last plugins.json rewrite — so the shipped catalog always carries complete update metadata and artifacts stay in lockstep with manifest versions.&lt;/p&gt;</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.531</title><link>https://proxmoxvex.com/changelog/#release--1-2-531-2026-09-26/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-531-2026-09-26/</guid><description>&lt;p&gt;Build 2026.09.26: Build&lt;/p&gt;&lt;p&gt;Native integration error states (spec 110): `/api/vyos/overview` and `/api/zabbix/overview` returned 503 with raw `requests` exception reprs as `payload.error` — the shared `native-dataview.js` frontend categorizes on that field (`unconfigured`/`auth`/`timeout`), so every connectivity failure rendered a generic card with a multi-line exception dump. New shared `ProxmoxVEx/native/errors.py::classify_error` maps exceptions to stable codes (`auth`/`tls`/`timeout`/`unreachable`/`api`/`dependency`/`internal`) + single-line `detail` — duck-typed so vendored `_src` clients work without importing `requests`. Adopted in vyos `_result`, all zabbix catch sites, and active_directory `ldap_client` (its `ldap3 not installed`/`requests not installed` literals now emit `dependency`). Frontend: `renderErrorState` gains `unreachable` (also aliases opnsense/pfsense/vmware&apos;s existing `upstream` code) and `tls` warn-cards; 4 new en i18n keys in all 6 NativeDataView module namespaces. Audit: all 6 NativeDataView modules already had `unconfigured` guards; the 5 bespoke-UI modules intentionally keep `error` as display text. Tests: `tests/unit/test_native_error_classify.py` (13).&lt;/p&gt;</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.530</title><link>https://proxmoxvex.com/changelog/#release--1-2-530-2026-09-26/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-530-2026-09-26/</guid><description>&lt;p&gt;Build 2026.09.26: Build&lt;/p&gt;&lt;p&gt;Plugin integrity false-positive fix: plugins that persist runtime state inside their own directory were flagged `tampered` and disabled on every restart — `verify_installed_files()` treats any on-disk file not listed in `integrity.json` (or any hash drift) as tampering, so `health-dashboard`&apos;s runtime `state.json` and `storage-health-monitor`&apos;s `data/*.json` writes self-flagged the plugins. Added a `mutable_paths` manifest field: `scripts/package_paid_plugins.py` ships mutable paths as seed data but excludes them from the hash set and records them in `integrity.json`; `PluginDeliveryService.verify_installed_files()` tolerates extras/drift under declared paths while still flagging undeclared files. Manifests: health-dashboard 1.0.2 (`[&quot;state.json&quot;]`), storage-health-monitor 1.0.2 (`[&quot;data/&quot;]`); artifacts repackaged. Regression tests in `tests/test_plugin_delivery.py::test_verify_installed_files_mutable_paths*`.&lt;/p&gt;</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.529</title><link>https://proxmoxvex.com/changelog/#release--1-2-529-2026-09-26/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-529-2026-09-26/</guid><description>&lt;p&gt;Build 2026.09.26: Build&lt;/p&gt;&lt;p&gt;Plugin deep-link cold-load fixes (spec 108): `?console_plugin=&lt;id&gt;`, `?tab=plugin:&lt;id&gt;`, `?tab=plugins` and `?settings_section=&lt;id&gt;` all silently no-op&apos;d on a cold load. Root causes: `enabledPlugins` was only fetched while the plugins tab was active, the plugin gallery + `plugin:&lt;id&gt;` iframe rendered only inside `ClusterPanel` (which requires a selected cluster), `PluginConsole` only mounted inside the gallery block, and `settings_section` never implied the settings tab. Now: a shared `fetchEnabledPlugins()` loader fires for any plugin surface (plugins tab, `plugin:&lt;id&gt;` tab, pending `console_plugin`), the pending-console stash sites kick the fetch so the resolver can open the modal, plugin surfaces render via a new `PluginSurface` component used both inside `ClusterPanel` and at dashboard level when no cluster is selected, `PluginConsole` is a root-level modal, and `?settings_section=` without `tab` opens `proxmoxvex-settings`. Regression tests: `tests/e2e/test_deeplink_restore.py` (6 cases incl. in-cluster regression + unknown-plugin-id state).&lt;/p&gt;&lt;p&gt;Plugin URL/error-handling fixes (spec 107): `rbac-explorer` UI fetched data from `/api/plugins/rbac-explorer/&lt;route&gt;` — the plugin API mount is `/api/plugins/&lt;id&gt;/api/&lt;route&gt;`, so every data call 404&apos;d; its `base` now includes the `/api/` segment (i18n stays on the non-api path). `api-token-manager` did a relative `fetch(&apos;i18n/en.json&apos;)` inside its iframe, which resolved under `.../api/` and 404&apos;d — now an absolute `/api/plugins/api-token-manager/i18n/en.json`. `status_page::_public_status` 500&apos;d the whole public status endpoint when `date(timestamp)` rows returned `datetime.date` objects (psycopg2) that were compared to an ISO-string cutoff, and when `get_clusters()` or a single cluster manager threw — the comparison is now string-normalised and both the cluster list and each per-cluster body are fault-isolated (a bad cluster degrades to `status:&quot;error&quot;`). Manifest bumps: rbac-explorer 1.0.1, api-token-manager 1.0.2, status_page 1.0.1; directory + paid artifacts regenerated; integrity drift 0. Tests: `tests/test_status_page_plugin.py` (date-object rows, exploding manager, broken bridge, bad key).&lt;/p&gt;&lt;p&gt;Release sync data-loss fix (spec 109): `scripts/bump-version.py::sync_public_repo` no longer runs `git rm -r -f -- .` on the public repo — that wipe destroyed `proxmox-vex-public` twice (tests/, docs/, deploy/, .github/ workflows, verify-release.sh, signing material) and overwrote the public repo&apos;s enhanced 2325-line updater with the main repo&apos;s 456-line basic copy. The sync now removes only the managed `dist/` path, treats the public repo&apos;s `update.sh` and `version.json` as canonical (patches managed fields in place — the public version.json uses an object-schema changelog), regenerates `dist/checksums.txt`, re-signs artifacts via `scripts/sign-release.sh` when the release key is available, and aborts before commit/push via `_assert_public_repo_intact` if required paths or updater markers (`verify_signature`, `--rollback`) are missing. Regression tests in `tests/test_bump_version.py::TestPublicRepoSync`.&lt;/p&gt;</description><pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.528</title><link>https://proxmoxvex.com/changelog/#release--1-2-528-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-528-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;&lt;p&gt;plugins: bump all 65 paid plugins by one patch version and repackage their license-server artifacts. Plugin files had drifted from the packaged `integrity.json` manifests (auto-header re-stamps + recent i18n/UX edits), so installed instances flagged them as tampered and disabled them — first reported on vm-update-manager. Fresh artifacts + matching manifests now let the plugin update channel deliver a clean install that passes verification. `AGENTS.md` now documents the bump-on-edit + repackage rule.&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.527</title><link>https://proxmoxvex.com/changelog/#release--1-2-527-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-527-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.526</title><link>https://proxmoxvex.com/changelog/#release--1-2-526-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-526-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.525</title><link>https://proxmoxvex.com/changelog/#release--1-2-525-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-525-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;&lt;p&gt;tests+utils: eliminate every global monkeypatch of shared stdlib/third-party modules. `monkeypatch.setattr(mod.time, &quot;sleep&quot;, …)` (and friends for `threading`, `gevent`, `paramiko`, `socket`, `os`, `shutil`, `subprocess`, `urllib`, `dns`, `importlib`, `requests`, `random`, `Path`) mutated the real module object, so unrelated daemon threads inherited test fakes mid-run and died with `PytestUnhandledThreadExceptionWarning` under xdist. All ~200 sites now use scoped helpers: `patch_module_attr`/`patch_module_sleep`/`patch_module_thread`/`module_shim` (a delegating `_ModuleAttrShim` that forwards everything except the overridden attrs) or direct module-attr patches. To make that possible, lazy function-local imports of always-loaded stdlib/third-party modules were hoisted to module level in `utils/concurrent.py` (time/threading/random/gevent joinall+Pool), `utils/ssh.py` (subprocess/socket/paramiko via `get_paramiko()`), `utils/ssh_pool.py` (new `get_paramiko()`), `utils/auth.py` (gevent get_hub+BoundedSemaphore, urllib.request), `utils/trusted_devices.py` (flask request), `core/dbcrypto.py` (get_hub/AsyncResult/BoundedSemaphore), `background/syslog_server.py` (gevent/get_hub/gsocket), `background/site_recovery.py`, `api/site_recovery.py`, `api/groups.py`, `api/realtime.py`, `api/templates_lib.py`, `api/push.py` (gevent/socket/urllib.parse), `ids/api.py` (get_hub), `constants.py` (shutil/subprocess), `demo/integration_mocks.py` (importlib). `Path` class-method fakes use Path-subclass or fake-path-object swaps since Path uses `__slots__`. Verified: `pytest -W error::pytest.PytestUnhandledThreadExceptionWarning` on the previously noisy files is clean, and every converted test still passes.&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.524</title><link>https://proxmoxvex.com/changelog/#release--1-2-524-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-524-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.523</title><link>https://proxmoxvex.com/changelog/#release--1-2-523-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-523-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.522</title><link>https://proxmoxvex.com/changelog/#release--1-2-522-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-522-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.521</title><link>https://proxmoxvex.com/changelog/#release--1-2-521-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-521-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;&lt;p&gt;web/contexts: fix `t is not defined` ReferenceError that crashed the entire app into the ErrorBoundary (&quot;Something went wrong.&quot;) on first render — the spec-104 i18n codemod wrote `aria-label={t(&apos;selectLanguage&apos;)}` in `LanguageSwitcher` but never added `t` to its `useTranslation()` destructure. Caught by the `test_no_undeclared_ui_references` bundle guard; was the root cause of all login-page e2e failures.&lt;/p&gt;&lt;p&gt;api/auth: login and session-restore can no longer 500 on a tour-pref lookup failure — `get_user_tour` calls in the `auth_login`/`auth_check` payloads now go through `_safe_user_tour()` (try/except → `{}`), matching the `_user_has_mfa_factor` pattern. Guided-tour state is non-critical enrichment and must never break auth.&lt;/p&gt;&lt;p&gt;plugins/incident-timeline: export the missing `window.__pvxT = pvxT` binding — the codemod rewrote render strings to `window.__pvxT(&apos;…&apos;)` but never assigned it, so every render helper threw `TypeError` and the events tab stayed on its initial &quot;No events.&quot; markup. Verified the only plugin with this defect.&lt;/p&gt;&lt;p&gt;tests: 63 component-existence tests updated to assert the i18n keys the codemod emitted (`t(&apos;key&apos;)`) instead of the replaced English literals; visual baselines regenerated — the Sep-19 baselines captured the pre-fix guided-tour bug (tour shown with infrastructure configured) plus a mid-load empty content area.&lt;/p&gt;&lt;p&gt;i18n (spec 104): close out the remaining English hardcoded UI strings across the whole frontend — ~2,900 transforms in `web/src` (JSX text children, `title`/`aria-label`/`placeholder`/`alt`/`label` attributes, `{&apos;literal&apos;}` children, toast/confirm/alert/`setErr`/`document.title`/`innerHTML`-style sink literals, and template-literal markup zones) now go through `t()` inside `useTranslation()` scopes or `ProxmoxVExI18n.t()` elsewhere; ~1,745 new keys added to `core/en.json` with reuse-first dedup against the existing 4.8k keys. Transform is AST-driven (`@babel/parser`) — no regex scanning — so JSX inside `${}` exprs, apostrophes in text, and nested elements can&apos;t desync. New keys are allowlisted in `validate.py`&apos;s `ALLOWED_MISSING` pending the external translation pass. Boot-screen strings in `index.html.original` intentionally remain English (the i18n engine has not loaded when they render).&lt;/p&gt;&lt;p&gt;web/dashboard: fix blank right panel when clicking a cluster row on first load — `activeTab` initialized to `null` whenever the URL carried no `?tab=` param (`_normalizeTab` only remapped `&apos;specs&apos;`), so `ClusterPanel` rendered the tab strip with no matching tab body; the initializer now falls back to `&apos;overview&apos;`.&lt;/p&gt;&lt;p&gt;web/dashboard: fix cluster-tree re-click doing nothing while a node/VM/datastore detail was open — the row&apos;s `setSelectedCluster(cluster)` was a React no-op on the already-selected cluster (same object identity), so the `[selectedCluster?.id]` cleanup effect never refired and the stale drill-in kept intercepting the Overview/Resources/Datastore tab. New `selectClusterTopLevel` handler additionally clears the node/VM/datastore/network sidebar selection and is wired to every &quot;show this cluster&quot; entry point (sidebar tree row, favorites, recents, group/worldmap cards, command palette, topology drill-ins, PBS linked clusters, ctx-menu bulk migration); sub-object pickers still set their selection after the clear so ordering is preserved.&lt;/p&gt;&lt;p&gt;web/tour + api/auth: the first-run &quot;Add Cluster&quot; guided tour now only renders while no Proxmox/PBS/ESXi endpoint is configured, and &quot;Don&apos;t show again&quot;/Skip finally stick across sessions — the saved `tour` pref was written to `proxmoxvex_kv` but `/api/auth/login` and `/api/auth/check` never returned it in `user`, so `user.tour` was always empty and the tour relaunched on every login. `GuidedTour` also waits for the initial cluster/PBS/VMware list fetches (new `infraReady`/`hasInfrastructure` props) so it can&apos;t flash for users who already manage infrastructure, and `fetchClusters` clears `clustersLoading` on non-OK responses instead of leaving the gate stuck.&lt;/p&gt;&lt;p&gt;web/dashboard topology: the multi-cluster map now has feature parity with the per-cluster topology sub-tab — Diagram/Cards view toggle, storage and network tier overlays (lazy per-cluster `/datastores` + `/networks` fetches with an in-flight guard since the `multiCluster` prop is rebuilt per parent render), affinity-rule edges (per-cluster lazy fetch), PBS backup-server icons + dashed edges reserved on the right of each cluster&apos;s node zone, per-cluster quorum pills, free-text search dimming on every multi-cluster element, VM tooltips with CPU/RAM/bridges/storage, and a per-cluster Cards view using cluster-namespaced collapse/expand/ref keys so identical node names and vmids don&apos;t collide; `onVmClick` carries `_clusterId` so multi-mode VM clicks resolve the right cluster, and the PDF export is titled &quot;Multi-Cluster Topology&quot;.&lt;/p&gt;&lt;p&gt;web/vm_modals: restore `_GroupCardGauge` JSX elements mangled by the i18n codemod (component tag and `value`/`size`/`strokeWidth`/`color` prop names had been rewritten to `t()` calls, producing `&lt;{t(...)}` and breaking the bundle build) — all 6 sites fixed.&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.520</title><link>https://proxmoxvex.com/changelog/#release--1-2-520-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-520-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.519</title><link>https://proxmoxvex.com/changelog/#release--1-2-519-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-519-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;&lt;p&gt;api/mirror: `/raw/&lt;branch&gt;/&lt;path&gt;` now enforces `_is_excluded_path` — the public raw-file endpoint only checked the paid-plugin list, so paths the tree listing deliberately hides (`config/` incl. `config/backups/` DB dumps, `logs/`, `ssl/`, `backups/`, `.git/`, `*.db`/`*.key`/`*.pem`/`*.crt`/`*.enc`) remained fetchable by direct URL. Both checks now apply; `safe_join` traversal protection unchanged.&lt;/p&gt;&lt;p&gt;utils/vnc_grab: `_vnc_auth_response`&apos;s legacy `TripleDES` fallback import (`cryptography.hazmat.primitives.ciphers.algorithms`) is now wrapped in `warnings.catch_warnings()` — the import emits `CryptographyDeprecationWarning` on every version that still ships it (still present at 50.x), which is expected noise on a deliberate legacy fallback and was polluting pytest warnings summaries.&lt;/p&gt;&lt;p&gt;web/app-shell.css: add missing `.z-[200]` / `.z-[80]` utility rules — the step-up confirm modal (`stepup_modal.js`) and the destructive-confirm dialog (`ui.js`) referenced arbitrary-value z classes absent from both the frozen `tailwind.min.css` and the app-shell utility block, so the dialogs lost their stacking order and could underlay fixed overlays.&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.518</title><link>https://proxmoxvex.com/changelog/#release--1-2-518-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-518-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.517</title><link>https://proxmoxvex.com/changelog/#release--1-2-517-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-517-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.516</title><link>https://proxmoxvex.com/changelog/#release--1-2-516-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-516-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;&lt;p&gt;core/db: `ProxmoxVExPGDB` connections are now per-greenlet (`_pg_conns` keyed on `greenlet.getcurrent()`) instead of a process-wide singleton — under xdist a shared conn let one greenlet&apos;s failed statement abort a transaction holding another greenlet&apos;s pending writes, and the victim&apos;s `conn.commit()` then silently committed nothing (the class of flake behind `test_success_and_crash`, `test_per_item_read_and_dismiss`, `test_one_grant_unlocks_bulk_batch`, `test_fails_after_three_and_auto_disables`, `test_default_window_is_10_minutes`). `ensure_connected` now inspects/reconnects the current greenlet&apos;s conn; real worker threads get their own root greenlet so they are isolated too.&lt;/p&gt;&lt;p&gt;core/db: `add_audit_entry` coerces non-str audit params to `str` before signing and inserting — test fixtures can leave `request.session` as a `MagicMock`, and a `MagicMock` user/id reaching psycopg2 raised `can&apos;t adapt type &apos;MagicMock&apos;`, aborting the transaction.&lt;/p&gt;&lt;p&gt;converter/engine: `delete_job`/`clear_jobs` now tombstone deleted job ids that still have a live worker (`_deleted_jobs`/`_active_jobs` under `_job_store_lock`) and `_persist_job` skips tombstoned ids — the job worker thread&apos;s next `INSERT OR REPLACE` transition write can no longer resurrect a just-deleted row (`test_delete_and_clear_job_endpoints` saw GET return 200 instead of 404 under xdist). Workers mark their job active in `submit_job` and release both marks in `_run_job`&apos;s `finally`, so re-persisting an id after its worker exits is allowed again; `delete_job`/`clear_jobs` also `request_cancel` live jobs so workers exit at the next `check_cancel`.&lt;/p&gt;&lt;p&gt;core/db: `ProxmoxVExPGDB.conn` keeps `self._pg_conn` pointing at the live conn when the first greenlet&apos;s conn is replaced after close, and `ensure_connected` also heals the canonical `_pg_conn` slot when it belongs to a different greenlet — `db_pool_stats()`&apos;s `checked_out` gauge reads `_pg_conn` (`test_checked_out_drops_when_pooled_conn_dies`).&lt;/p&gt;&lt;p&gt;tests: `test_converter_crosscluster.py::test_cross_cluster_job_fails_gracefully` — added `pending` to the accepted early statuses; the assertion can run before the worker&apos;s first transition.&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.515</title><link>https://proxmoxvex.com/changelog/#release--1-2-515-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-515-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.514</title><link>https://proxmoxvex.com/changelog/#release--1-2-514-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-514-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;&lt;p&gt;core/db_pg: `PGCursor.execute`/`executemany` now roll back only when `get_transaction_status() == TRANSACTION_STATUS_INERROR` (the server actually aborted the tx). Previously a client-side `psycopg2.ProgrammingError` (e.g. `can&apos;t adapt type &apos;MagicMock&apos;` from an audit write that picked up a `MagicMock`-patched `request.session` in a test fixture) triggered an unconditional `rollback()` on the shared pooled conn — silently discarding a concurrent greenlet&apos;s pending writes; the next `conn.commit()` then commits an empty tx so the INSERT vanishes without an error (surfaced as `test_success_and_crash` TypeError and `test_per_item_read_and_dismiss` KeyError under xdist).&lt;/p&gt;&lt;p&gt;tests: `test_converter_crosscluster.py::test_cross_cluster_job_fails_gracefully` raced the converter worker — it asserted the job was already `failed`/`running`/`preflight`, but the job can still legitimately report `validating`. Added `validating` to the accepted statuses so the assertion matches the test&apos;s own comment.&lt;/p&gt;&lt;p&gt;tests: `test_demo_cluster_mock.py::_uninstall` now restores all patched manager methods (`_create_session`, `_resolve_host`, `_get_node_ip`, `_ssh_connect`, `_node_ssh_exec`, `__init__`, ssh runners) not just `requests.Session.request` — leaving them wrapped meant the next `install_demo_cluster_mock()` captured a wrapper as `_ORIG_CREATE_SESSION`, making `wrapper → _ORIG_CREATE_SESSION(=wrapper)` infinitely recursive (surfaced as `RecursionError` in `test_upstream_trace` under xdist).&lt;/p&gt;&lt;p&gt;tests: `test_webhooks.py` `sink` fixture switched `ThreadingHTTPServer` → `HTTPServer` — under xdist the worker&apos;s `patch_all(thread=False)` keeps real threads but patches sockets, so an accepted socket&apos;s gevent IO watcher is bound to the `serve_forever` thread&apos;s hub; a per-request handler thread calling `rfile.read()` then dies with `greenlet.error: Cannot switch to a different thread`, the client sees `RemoteDisconnected`, and `test_retry_on_failure` intermittently records only 2 of 3 hits. Single-threaded serving keeps every socket op on one hub.&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.513</title><link>https://proxmoxvex.com/changelog/#release--1-2-513-2026-09-25/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-513-2026-09-25/</guid><description>&lt;p&gt;Build 2026.09.25: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.512</title><link>https://proxmoxvex.com/changelog/#release--1-2-512-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-512-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.511</title><link>https://proxmoxvex.com/changelog/#release--1-2-511-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-511-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.510</title><link>https://proxmoxvex.com/changelog/#release--1-2-510-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-510-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.509</title><link>https://proxmoxvex.com/changelog/#release--1-2-509-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-509-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.508</title><link>https://proxmoxvex.com/changelog/#release--1-2-508-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-508-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.507</title><link>https://proxmoxvex.com/changelog/#release--1-2-507-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-507-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.506</title><link>https://proxmoxvex.com/changelog/#release--1-2-506-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-506-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;&lt;p&gt;fix(db): `PGCursor.execute`/`executemany` now heal a transaction left aborted by a bypassing statement (`TRANSACTION_STATUS_INERROR` → rollback before running) instead of failing the next caller with `InFailedSqlTransaction` — background greenlets share the pooled conn, and `executemany` itself had no error-rollback, so one bad statement poisoned unrelated later queries (surfaced as the flaky `test_fuzz_create_vm_backup`).&lt;/p&gt;&lt;p&gt;tests: serial e2e runs no longer wedge ~70 tests in — the full `gevent.monkey.patch_all()` replaced `thread`/`queue`, starving Playwright&apos;s sync driver greenlet so `browser.new_context` hung until timeout for every remaining test. E2E-only runs (`pytest tests/e2e…`) now patch with `thread=False, queue=False` — the same subset xdist workers use, where playwright already works; no e2e test imports app code.&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.505</title><link>https://proxmoxvex.com/changelog/#release--1-2-505-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-505-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;&lt;p&gt;tests: `conftest._worker_test_db` `CREATE DATABASE ... TEMPLATE` failed intermittently with `ObjectInUse` — the template-converge `get_db()` leaves background greenlets (metrics/crypto writers) holding a second connection to the template after `tpl_conn.close()`. The clone now `pg_terminate_backend`s stragglers on the template and retries up to 6×.&lt;/p&gt;&lt;p&gt;tests: `conftest._reset_test_db` reseeds `k8s_templates` via `ensure_kubernetes_tables` on a translating `PGConnection` — the curated library only seeds on full converge, which the preserved fingerprint skips, so TRUNCATE left it empty for every later test (`test_template_library_and_deploy`). The helper speaks SQLite-flavoured SQL (`AUTOINCREMENT`, `INSERT OR IGNORE`) and must not run on a raw psycopg2 conn.&lt;/p&gt;&lt;p&gt;tests: `test_index_audit.py` — `test_audit_flags_dropped_index`/`test_audit_script_reports_and_exits` DROP indexes on the shared worker DB; the TRUNCATE reset restores rows, not schema, so `idx_users_deleted_at`/`idx_sessions_username` stayed missing for later tests. Both now call `db._ensure_hot_indexes()` in a `finally`.&lt;/p&gt;&lt;p&gt;refactor(api): split `api/vms.py` (12.6k lines / 148 routes) into an `api/vms/` package — `common.py` (blueprint, `_require_vm_access`, `_pve_error`, session pools, VNC helpers) plus 15 domain modules (`datacenter`, `datastores`, `backups`, `firewall`, `node_mgmt`, `control`, `console`, `config`, `passthrough`, `devices`, `snapshots`, `replication`, `ssh_ws`, `migration`, `creation`, `bulk`). Pure code-motion: route table byte-identical, external imports (`bp`, `_wait_for_task`, `_execute_replication`, `_execute_local_replication`, `start_ssh_websocket_server`, `start_vnc_websocket_server`) re-exported through the package facade; `__file__`-relative tmp/script paths gained one `dirname` for the extra level so generated files land in the same place.&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.504</title><link>https://proxmoxvex.com/changelog/#release--1-2-504-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-504-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;&lt;p&gt;tests: `conftest._reset_test_db` preserved the schema fingerprint from `proxmoxvex_kv`, but `_record_schema_fingerprint` writes it to `server_settings` — after every TRUNCATE the fingerprint was lost, so each `db` fixture paid a full schema converge plus a pre-migration `pg_dump` (which also broke `test_clean_boot_takes_no_backup` deterministically). Now reads/restores `server_settings` and guards the seed inserts against a wiped schema.&lt;/p&gt;&lt;p&gt;tests: `test_premigrate_backup.py::test_empty_db_needs_no_backup` dropped every public table in the shared xdist worker DB and never reconverged — every later `db`-fixture test on that worker failed at the `power_rates` seed insert (13 setup errors in the pipeline run). The `finally` now re-runs the full `ProxmoxVExPGDB.__init__` converge chain (bare `_init_db` skips the `_ensure_*` steps such as soft-delete `deleted_at` columns).&lt;/p&gt;&lt;p&gt;tests: `test_demo_cluster_mock.py::test_demo_request_bad_url` overwrote `cm._ORIG_SESSION_REQUEST` with a `RuntimeError(&quot;x&quot;)` lambda that `_uninstall()` then installed as `requests.Session.request` — every later HTTP call in the worker raised `x`, which is what actually failed the two webhook tests. The original callable is now preserved and restored.&lt;/p&gt;&lt;p&gt;tests: `test_coverage_gate` still asserted a literal `--cov-report=xml` pytest flag, but the workflow moved to a 6-shard matrix where XML is produced by `coverage xml -o coverage.xml` in the coverage-gate job — the test now accepts either mechanism.&lt;/p&gt;&lt;p&gt;fix(api): `api/vms/common.py` (vms package split) used `HTTPAdapter` in the bulk-operations session mounts without importing it — `create_app()` died with `NameError`, breaking every `api`-fixture test. Added `from requests.adapters import HTTPAdapter` matching the pre-split `vms.py` imports.&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.503</title><link>https://proxmoxvex.com/changelog/#release--1-2-503-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-503-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;&lt;p&gt;pipeline: `scripts/full-pipeline.sh` pre-build test phase now runs pytest-xdist (`-n auto --dist loadfile -m &quot;not serial&quot;`) plus a serial `-m serial` pass — the previous serial `pytest tests/ -q` (with a stale &quot;xdist broken&quot; comment predating the conftest gevent fix) kept the ~7.3k-test phase at &gt;1h instead of ~minutes.&lt;/p&gt;&lt;p&gt;fix(ssh-ws): `session_for` was called but never defined in the standalone SSH-WS server — commit `5a9a6aac8a` swapped `requests.get()` for `session_for(&quot;internal-api&quot;)` without an import, so every shell/termproxy connection would have hit `NameError` (ruff F821). Added a local pooled-session mirror (same keep-alive pooling intent) since the subprocess deliberately runs without importing the package; applied to both the on-disk `.ssh_ws_server.py` and the `server_script` template in `vms.py` that regenerates it at launch.&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.502</title><link>https://proxmoxvex.com/changelog/#release--1-2-502-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-502-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;&lt;p&gt;tests: `conftest._reset_test_db` guards `cur.fetchone()` before subscripting (same None-safe pattern as the fingerprint read above it) — fixes the Pyright &quot;Object of type None is not subscriptable&quot; diagnostic.&lt;/p&gt;&lt;p&gt;tooling: `pyrightconfig.json` `pythonVersion` 3.12→3.14 to match the runtime/mypy/ruff floor — resolves IDE &quot;No parameter named&quot; errors on the 3.14-only `argparse` kwargs (`suggest_on_error`, `color`) used by the script CLIs.&lt;/p&gt;&lt;p&gt;tests: major test-suite speedup — pytest-xdist enabled (`-n auto --dist loadfile`; `--dist loadfile` keeps each file&apos;s module-level fake DBs on one worker). conftest&apos;s `gevent.monkey.patch_all()` is now conditional: xdist workers get `patch_all(thread=False)` and the controller none — patching the controller deadlocked execnet&apos;s control channel (LoopExit). `timeout_method` switched `thread`→`signal`: the thread method called `os._exit(1)` on any &gt;300s test, killing xdist workers (&quot;node down&quot;) / the whole serial run; signal fails just the test.&lt;/p&gt;&lt;p&gt;tests: `db` fixture no longer pays full schema convergence per test — a per-process template DB is converged once and each test clones it via `CREATE DATABASE ... TEMPLATE`, while `ProxmoxVExPGDB` skips the `_init_db`+`_ensure_*` chain when the live schema matches the recorded fingerprint (gated to `PROXMOXVEX_TEST_FAST_INIT=1`, conftest-only). Per-test DROP moved to a background janitor thread; stale `pp_*` test DBs leaked by crashed runs are reaped automatically. test_rbac.py: ~4min → ~1min.&lt;/p&gt;&lt;p&gt;ci: test workflow parallelized — 6-shard matrix (top-level ×3, tests/unit ×2, misc dirs) each with `-n auto --dist loadfile` and its own postgres service; per-shard `.coverage` artifacts combined in a `coverage-gate` job so the diff-cover ≥70% changed-code gate is unchanged.&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.501</title><link>https://proxmoxvex.com/changelog/#release--1-2-501-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-501-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Full build pipeline&lt;/p&gt;&lt;p&gt;db: log a warning when the idempotent users-table column migration is skipped (e.g. lock_timeout during a rolling upgrade) instead of passing silently — a swallowed lock-timeout left `password_changed_at` missing and auth treated every user as password-expired.&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.500</title><link>https://proxmoxvex.com/changelog/#release--1-2-500-2026-09-24/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-500-2026-09-24/</guid><description>&lt;p&gt;Build 2026.09.24: Build and serve&lt;/p&gt;&lt;p&gt;spec 103: Python 3.14.7 runtime upgrade — `requires-python` floor raised `&gt;=3.8`→`&gt;=3.14` (`version.json min_python` likewise). Compatibility: `datetime.utcnow()`/`utcfromtimestamp()` migrated to `datetime.now(UTC)`/`fromtimestamp(ts, UTC)` in core/manager.py, api/pbs.py, api/site_recovery.py, background/site_recovery.py; removed-API battery clean (no PEP 594/dead-battery/imp/distutils usage); all 77 plugins verified loadable on 3.14.7 via the spec_from_file_location loader path.&lt;/p&gt;&lt;p&gt;spec 103: dependency floors bumped to the 3.14-verified resolution — websockets&gt;=17.0, gevent&gt;=26.9.0, cryptography&gt;=50.0.1 (&lt;52 kept: fido2 ceiling), paramiko&gt;=5.0.0, kubernetes&gt;=36.0.0,&lt;37, scikit-learn&gt;=1.9.1, pandas&gt;=3.0.6, mypy&gt;=2.3.1 (&lt;2.0 cap lifted); all CVE-rationale comments preserved. `pip-audit --strict` on the resolved tree: zero known vulnerabilities.&lt;/p&gt;&lt;p&gt;spec 103: runtime surfaces on 3.14 — Dockerfile base `python:3.12-alpine`→`3.14-alpine` (both stages; added `libgomp` for sklearn&apos;s musllinux OpenMP wheel), operator Dockerfile.multi→`3.14-slim`; GitHub workflows test.yml/e2e.yml/security.yml provision 3.14 via setup-python (ubuntu-24.04 ships 3.12, below the new floor); gitlab-ci images `python:3.11`→`3.14` (5 sites).&lt;/p&gt;&lt;p&gt;spec 103: appliance .deb now bundles a self-contained standalone CPython 3.14.7 (pinned python-build-standalone 20260901, sha256-verified in debian/rules) with all Python deps pip-installed into `/usr/lib/ProxmoxVEx/python` — every `python3-*` Depends removed (only ca-certificates/openssh-client/sshpass/postgresql remain), gyptazy apt source dropped from release-images.yml LXC/VM builds (it only existed for python3-flask-sock/python3-xenapi), Architecture `all`→`amd64`. dh_dwz/dh_strip/dh_shlibdeps are excluded/skipped for the vendored interpreter+wheels (stripped .so files, $ORIGIN RPATH, manylinux quirks). RPM spec gains the same bundled-interpreter treatment; chart specs/base-chart floors bumped to &gt;=3.14.&lt;/p&gt;&lt;p&gt;spec 103: 3.14 feature adoption — `timezone.utc`→`datetime.UTC` swept repo-wide (271 sites, 136 files); tomli fallback dropped in update_headers.py (stdlib tomllib unconditional); `suggest_on_error`+`color` enabled on all script argparse CLIs; converter/constants.py enums moved `(str, Enum)`→`StrEnum`. compression.zstd not adopted — the codebase only shells out to remote `zstd` binaries; no Python zstd dependency exists.&lt;/p&gt;&lt;p&gt;`scripts/update_headers.py`: generated headers are richer — `Description:` now uses the full first docstring paragraph (via `ast`, was the first line truncated at 58 chars) or a joined leading comment block, wrapping onto up to three aligned lines; a new `Usage:` field is emitted when inferable (docstring `Usage:` section, `pytest tests/…`, `python3 &lt;script&gt;` for argparse/`__main__` files, `import &lt;dotted.module&gt;` for libraries, `bash &lt;script&gt;` for shells). The module docstring was expanded into full documentation (behaviors, metadata/date resolution, config files, exit codes) with a Usage section, and `--help` now prints an examples epilog.&lt;/p&gt;</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.499</title><link>https://proxmoxvex.com/changelog/#release--1-2-499-2026-09-23/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-499-2026-09-23/</guid><description>&lt;p&gt;Build 2026.09.23: Full build pipeline&lt;/p&gt;&lt;p&gt;release-images workflow: `prepare` now builds `proxmoxvex_&lt;ver&gt;_all.deb` in-workflow from the release tag (`dpkg-buildpackage`, changelog stamped from the tag) and uploads it as a workflow artifact; `build-lxc`/`build-vm` download and install that exact package instead of polling the third-party gyptazy APT repo for a package that is never published there (the job timed out on every prior release). gyptazy remains configured in the images for dependencies Debian trixie lacks (`python3-flask-sock`, `python3-xenapi`).&lt;/p&gt;&lt;p&gt;packaging: repaired the in-repo `debian/` build so it produces a working appliance package — lowercased source/package name (`proxmoxvex`, `dpkg-source` rejects uppercase), LF line endings, `debian/install` exec bit removed, case-aligned `ProxmoxVEx.service`/`ProxmoxVEx-wrapper`/`proxmoxvex.postinst` (dh_installdeb previously never picked up the postinst), postinst creates the `ProxmoxVEx` user/group correctly (`--user-group`, was `nogroup`), guards `systemctl` for chroot/container installs, and preps `/var/lib/ProxmoxVEx` runtime state.&lt;/p&gt;&lt;p&gt;packaging: `Depends` updated to real requirements — added `postgresql` (SQLite backend removed; app is postgres-only), `python3-psycopg2`, `python3-wsproto`, `python3-fido2`, `python3-requests-toolbelt`, `python3-werkzeug`, `python3-croniter`, `python3-cron-descriptor`, `python3-kubernetes`, `python3-defusedxml`.&lt;/p&gt;&lt;p&gt;appliance runtime: `ProxmoxVEx.service` now sets `WorkingDirectory=/usr/lib/ProxmoxVEx` (plugins/web/logs resolve CWD-relative), `Environment=PROXMOXVEX_CONFIG_DIR=/var/lib/ProxmoxVEx/config` (mutable state out of /usr/lib), `Environment=PROXMOXVEX_BIND_ALL=1` (appliance UI must serve on the appliance IP — QEMU hostfwd and LXC smoke tests depend on it), and `ExecStartPre=+/usr/lib/proxmoxvex/pvx-db-bootstrap.sh` — a root, idempotent role+db bootstrap so first-boot works even though the package is installed in a chroot where postgres can&apos;t run yet; script lives outside the service-user-owned tree to avoid a local privesc path.&lt;/p&gt;</description><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.498</title><link>https://proxmoxvex.com/changelog/#release--1-2-498-2026-09-23/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-498-2026-09-23/</guid><description>&lt;p&gt;Build 2026.09.23: Full build pipeline&lt;/p&gt;&lt;p&gt;plugins: fix &quot;Check for updates&quot; failing with 403 &quot;No active license found&quot; on unlicensed installs — the licence server&apos;s `/v1/plugins/&lt;id&gt;/updates` endpoint is public (only artifact downloads are license-gated), so `/api/plugins/&lt;id&gt;/check-update` and `/api/plugins/updates/check` no longer require an activated licence; `PluginDeliveryService.check_for_update` never sent the key anyway. Failed checks now surface the server&apos;s real reason (`_request()` returns the response error detail, e.g. &quot;Plugin not in catalog&quot;) instead of a generic message, `install_update` propagates check failures instead of reporting &quot;No update available&quot;, the bulk response carries an `errors` count, and the plugins UI toasts partial/total check failures and renders a per-plugin &quot;Update check failed&quot; line rather than claiming all plugins are up to date.&lt;/p&gt;&lt;p&gt;CSP vs Cloudflare Rocket Loader: `utils/csp.py::apply_nonce_to_scripts` now also stamps `data-cfasync=&quot;false&quot;` on every `&lt;script&gt;` it rewrites — the edge feature was re-creating deferred scripts without the CSP nonce, so script-src blocked our own inline scripts on app.proxmoxvex.com (both index.html boot-progress blocks never ran; the bar sat at &quot;Loading...&quot;). Covers the shell, plugin iframe `ui.html` and native-UI documents through the single stamping funnel. Companion edge fix codified as `rocket_loader = &quot;off&quot;` in `proxmoxvex-landing/deploy/infra.tf` (pending `terraform apply`).&lt;/p&gt;&lt;p&gt;licensing: `DEFAULT_TIER_CATALOG` fallback rescaled to the revised ladder — community 10, basic 15, advanced 30 managed nodes (corporate unlimited), matching the licensing-service seed so the offline tier dropdown shows the same entitlements as the server.&lt;/p&gt;&lt;p&gt;tests: updated stale `plugin_delivery` HTTP mocks after the `_request()` refactor — `tests/contract/test_plugin_delivery_contract.py` (3 tests) and `tests/unit/test_services_gaps.py::test_post_and_get_errors` now patch `requests.request` instead of `requests.get`/`requests.post`, which the refactor no longer calls.&lt;/p&gt;</description><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.497</title><link>https://proxmoxvex.com/changelog/#release--1-2-497-2026-09-23/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-497-2026-09-23/</guid><description>&lt;p&gt;Build 2026.09.23: Full build pipeline&lt;/p&gt;&lt;p&gt;tests: updated `tests/test_licensing.py` stubs for spec 102 — the 7 `svc.validate` monkeypatch lambdas now accept the `node_count` kwarg sent on activate/revalidation, and the fake `DEFAULT_TIER_CATALOG` entry carries `max_nodes` (fixes 8 pre-build pipeline failures).&lt;/p&gt;&lt;p&gt;bump-version: `update_changelog` now folds `## Unreleased` bullets into the new release section (released versions previously showed only the generic &quot;Build …&quot; line while real changes stayed under a stray Unreleased heading — the cause of proxmoxvex.com/changelog showing no changes); `version.json.changelog` now carries the Unreleased bullets too so in-app update notes and GitHub release bodies list real changes.&lt;/p&gt;</description><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.496</title><link>https://proxmoxvex.com/changelog/#release--1-2-496-2026-09-23/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-496-2026-09-23/</guid><description>&lt;p&gt;Build 2026.09.23: Full build pipeline&lt;/p&gt;&lt;p&gt;spec 102: managed-node reporting + license limit status — `services/licensing.py` sends `node_count` (sum of `get_node_status()` across `cluster_managers`; unreachable clusters count 0, total failure omits the field) on activate and every 15-min revalidation; `license_context` gains `max_nodes`/`nodes_used`/`over_limit`/`last_node_report_at` (guarded ALTER, preserved when an older server omits the fields); `/api/v1/licence/status` returns the limit state; the licence page shows &quot;Managed nodes: used / limit&quot; plus a dismissible over-limit warning (localStorage-keyed to the used/max pair so it resurfaces on change). Soft enforcement — `over_limit` never downgrades the tier. `DEFAULT_TIER_CATALOG` gains `max_nodes` so the offline fallback matches the server. 3 new i18n keys x14 core locales. `tests/unit/test_license_nodes.py`.&lt;/p&gt;&lt;p&gt;typing: repo-wide Pyright cleanup — full scan now reports 0 errors / 0 warnings (was ~6,657 diagnostics across 623 files). `config/backups/` snapshots excluded in `pyrightconfig.json`; `extraPaths` added for `pfsense_src`/`opnsense_src`/`truenas_src` and plugin roots so internal imports resolve. Fixes are typing-only: `request.session` → `getattr`/`setattr` convention, Optional-widened defaults, `assert`/`cast(Any, ...)` at dynamic SDK and monkeypatch boundaries, `-&gt; Any`/`-&gt; Response` annotations on union-returning helpers, `spec_from_file_location` loader asserts in ~79 plugin test loaders, and a handful of dead-expression removals (`api/reports`, `core/v2p`, `utils/vnc_polling`). No runtime behavior changes.&lt;/p&gt;</description><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.495</title><link>https://proxmoxvex.com/changelog/#release--1-2-495-2026-09-23/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-495-2026-09-23/</guid><description>&lt;p&gt;Build 2026.09.23: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.494</title><link>https://proxmoxvex.com/changelog/#release--1-2-494-2026-09-23/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-494-2026-09-23/</guid><description>&lt;p&gt;Build 2026.09.23: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.493</title><link>https://proxmoxvex.com/changelog/#release--1-2-493-2026-09-23/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-493-2026-09-23/</guid><description>&lt;p&gt;Build 2026.09.23: Coverage batches 1-11 + audit bug fixes; test-harness isolation; shellcheck cleanup&lt;/p&gt;&lt;p&gt;deps: raised security floors flagged by cve-monitor/OSV — cryptography&gt;=50.0.0 (Bleichenbacher/PKCS12/DNS-constraint/SECT advisories on 49.x), paramiko&gt;=5.0.0 (GHSA-r374-rxx8-8654 SHA-1 rsakey), scapy&gt;=2.7.0 (GHSA-cq46-m9x9-j8w2 pickle RCE); all already satisfied by the resolved env&lt;/p&gt;&lt;p&gt;api/alerts: fix `/api/clusters/&lt;id&gt;/active-alerts` column misalignment — SELECT projected 8 columns but rows were zipped against a 15-name list, so every field in the response was shifted (alert_id carried the threshold, etc.).&lt;/p&gt;&lt;p&gt;api/reports: fix `LazyReportLoader.get_chunk` dropping the final partial chunk — `break` inside `except StopIteration` exited the materialization loop before appending, so the tail of a report (e.g. the last 20 of 120 items) was lost and `total_chunks()` undercounted.&lt;/p&gt;&lt;p&gt;api/reports: mark the unreachable `sort_dir` re-clamp in `/api/syslog/events` with `pragma: no cover` — the ternary above already restricts the value to asc/desc.&lt;/p&gt;&lt;p&gt;api/vmware: fix `/api/vmware/migrations` route — the `@bp.route`/`@require_auth` decorators were attached to the `_migration_reachable` helper (which requires an argument) instead of `list_vmware_migrations`, so the endpoint 500&apos;d on every call and the real handler was dead code.&lt;/p&gt;&lt;p&gt;Plugin shared UI (`static/css/plugin-ui.css`, +380 lines): status badges, message variants (info/success/warning/error), modal scroll + z-index fixes, table-wrap with sticky thead, opt-in `.sortable` headers, hover/danger/ghost/icon buttons, checkbox/select sizing fixes, `sr-only`, `kbd`, `mono`, progress bars, stat cards, toolbar/chip/card variants, empty-state icon, toast region, tooltip flip, field-error styling, link + placeholder colors, spinner, and mobile media queries — reaches all 77 plugin iframes at once.&lt;/p&gt;&lt;p&gt;New shared plugin runtime (`static/js/plugin-ux.js`): `PVE.ui.*` helpers — api wrapper, toast/confirm, escapeHtml, copy-to-clipboard, debounce/throttle/poll, skeleton/empty/error state renderers, sortable tables, timeAgo/formatBytes, modal + tab wiring, busy buttons, aria announcements, field errors, CSV download, auto-theme. Injected into every plugin iframe via `_serve_plugin_ui` alongside the theme bridge and event client (idempotent, CSP-nonce-safe).&lt;/p&gt;&lt;p&gt;Host design system: responsive.css (full-screen modals on mobile, horizontal scroll hints, compact landscape), theme.css (focus-ring/selection/mark tokens, themed scrollbars, disabled parity), skeleton.css (table/chart/stat/inline variants), ui-states.css (offline/stale/permission states), accessibility.css (aria-current markers, focus-within outlines, reduced-transparency, :target outline).&lt;/p&gt;&lt;p&gt;Toasts: dedupe (2s window), cap of 5 visible, sticky error toasts with manual dismiss, optional action buttons, severity-aware live-region roles.&lt;/p&gt;&lt;p&gt;Tables (`tables.js`): `aria-sort` on sortable headers, Enter/Space keyboard sorting, drag-resizable columns persisted per table; verified existing shift-range select, bulk bar, empty states.&lt;/p&gt;&lt;p&gt;Command palette: MRU ordering (sessionStorage), `&gt;` prefix action-mode filter, ARIA combobox semantics, grouped results by category.&lt;/p&gt;&lt;p&gt;Header: `ConnectionStatus` widget — live/refreshing/stale indicator, last-updated + next-poll countdown, offline banner, click-to-retry on fetch failure.&lt;/p&gt;&lt;p&gt;Session: 30-min idle warning modal (revalidates via keepalive), per-route `document.title` + screen-reader route announcements.&lt;/p&gt;&lt;p&gt;`PveHelpers` in ui.js: promise-based confirm + danger-confirm dialogs, copy helper, shared timeAgo/formatBytes.&lt;/p&gt;&lt;p&gt;Boot (`web/index.html`): `&lt;noscript&gt;` fallback, visible boot progress bar + stage label via `window.pveBootProgress`, meta description/theme-color/apple-touch-icon; CSP-nonce-safe (server stamps inline scripts).&lt;/p&gt;&lt;p&gt;Plugin iframe frame (`dashboard_panels.js`): skeleton loading state, improved error + retry, back navigation, standalone-window + fullscreen controls — sandbox/referrerPolicy unchanged.&lt;/p&gt;&lt;p&gt;Sidebar: pinned favorites rail (server-backed `/user/favorites`, resolves VM names from the sidebar resource cache, click-to-navigate to cluster/node/VM); resizable corporate sidebar now collapses to a 64px icon rail (`corp-sidebar-compact`, labels hide below 96px, icon tooltips remain).&lt;/p&gt;&lt;p&gt;Login: caps-lock warning, show/hide password toggle (already present), version badge in footer (already present).&lt;/p&gt;&lt;p&gt;i18n: new `favorites` core key translated across all 14 locales.&lt;/p&gt;&lt;p&gt;Fix (cache coherency): the role create/delete/template-apply and VM-ACL PUT write paths in api/users.py mutated the SHARED cached dicts returned by `get_custom_roles()`/`get_vm_acls()`, then saved and invalidated. Between the mutation and the invalidation, concurrent readers could observe a grant/role that was never persisted — and since `save_vm_acls` swallows DB errors, a failed write left the cache diverged from disk until the invalidate (or, for role writes, until a later read path crashed on the half-mutated structure). All four write paths now mutate a fresh `load_*()` object instead; read-only call sites keep the cached accessors.&lt;/p&gt;&lt;p&gt;Fix (converter preflight): `vm_to_lxc` preflight previously hardcoded `distro=&quot;detectable&quot;` for VM sources and then ran the supported-distro gate on that sentinel value, so EVERY VM-to-LXC job failed preflight regardless of the actual guest OS. `_check_os_support` now resolves the primary disk (`pvesm path`) and runs real `detect_os_from_disk` detection: Windows guests are rejected with the intended message, inconclusive detection defers to the run phase instead of failing, and real distros are validated against the supported set.&lt;/p&gt;&lt;p&gt;Fix (CSP): `utils/csp.py` checked for `&lt;script` case-sensitively before applying nonces while the injection regex was case-insensitive, so `&lt;SCRIPT&gt;` tags never received nonces; the gate now uses a lowercased copy of the HTML.&lt;/p&gt;&lt;p&gt;Fix (demo cluster mock): `/nodes/{node}/storage` was shadowed by the generic `…/storage` matcher and returned cluster storage config instead of the per-node list; the specific route now matches first. Also removed a dead duplicate `/agent/info` branch already covered by the `/agent/(get|info)` rule.&lt;/p&gt;&lt;p&gt;Fix (core cache): `core/cache.py` called `dict.popitem(last=…)` — a keyword only `OrderedDict` supports — which would raise `TypeError` on the eviction path; switched to the ordered-pop equivalent for the plain dict.&lt;/p&gt;&lt;p&gt;Fix (affinity rules): `DELETE /api/affinity-rules/&lt;id&gt;` filtered the rule out of an in-memory config then called `save_all_affinity_rules`, which only upserts rows still present — so a &quot;deleted&quot; rule silently reappeared on the next load. The route now calls `db.delete_affinity_rule` directly, matching the cluster-scoped alerts.py path.&lt;/p&gt;&lt;p&gt;Fix (plugin data bridge): `api/plugin_data_bridge.get_live_data` dispatched every handler as `dispatch[resource](cluster_id)` including `get_clusters()`, which takes no argument — `GET /api/clusters/&lt;id&gt;/live/clusters` therefore always failed with a `TypeError`. The clusters dispatch now omits the argument.&lt;/p&gt;&lt;p&gt;Fix (user favorites): `save_favorites` iterated `favorites[username]` expecting a flat list while `update_favorites`/`load_favorites` use the `{&quot;vms&quot;: [], &quot;nodes&quot;: [], &quot;clusters&quot;: []}` shape — every `POST /api/user/favorites` hit `&apos;str&apos; object has no attribute &apos;get&apos;` and silently persisted nothing. `load_favorites` now builds the structured shape and `save_favorites` accepts both forms (VM rows only — the table has no node/cluster columns).&lt;/p&gt;&lt;p&gt;Tests: `test_api_batch5.py` + `test_api_batch6.py` (~250 tests) bring `groups`, `insights`, `costs`, `history`, `site_recovery`, and `search` api modules to 100% line coverage.&lt;/p&gt;&lt;p&gt;Tests: four new api test suites (`test_api_batch1-4.py`, ~240 tests) bring `support`, `audit_search`, `server_access`, `trash`, `openapi_doc`, `stepup`, `healthz`, `demo`, `ids`, `helpers`, `converter`, `licence`, `mirror`, `power`, `plugin_data_bridge`, `static_files`, `topology`, `siem`, and `webauthn` api modules to 100% line coverage via the real Flask test harness; also covers `static_files`&apos; shadowed pool routes via direct invocation (the `/pools` endpoints are registered earlier by `api/users.py`).&lt;/p&gt;&lt;p&gt;Tests (harness): `_integration_app` now no-ops every background thread starter `create_app` invokes (task/actions scheduler, alerts, broadcast, cross-cluster lb/replication, password expiry, plugin updates/health/backgrounds, site-recovery heartbeat, syslog server, netapp/docker-swarm plugin threads). Live threads raced per-test `_db`/`_instance` resets and could pin `core.db._db` to a stale session-database object mid-test — the root cause of flaky `ACCOUNT_DELETED` 401s. `_restore_patched_helpers` also clears both DB `_instance` singletons alongside `_db`.&lt;/p&gt;&lt;p&gt;Tests: new `tests/unit/test_converter_gaps.py` (~120 tests) brings the entire `ProxmoxVEx/converter` package to 100% line coverage — models, validators, commands, runner, OS detection, preflight, DB helpers, engine lifecycle/cancel/persistence, all four conversion flows (success + every error branch), and rollback.&lt;/p&gt;&lt;p&gt;Tests: new unit suites bring `utils/`, `services/`, `updater/`, `models/`, `demo/`, `ids/` (except api.py remainder), `core/` smalls, `cli.py`, `plugins/__init__`, `background/` workers to ~100% line coverage — vnc crypto/grab/polling/tunnel, webhooks, ldap, oidc, ssh, auth, rbac, acme, backup_verify, compliance_mapping, esxi_cluster, dbcrypto, config, cache, cluster_mock, demo seed/token/guard/integration_mocks, ids models/engine/rules/feed/scheduler/reports/converter, and the ids blueprint API end-to-end via the real Flask test harness.&lt;/p&gt;</description><pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.492</title><link>https://proxmoxvex.com/changelog/#release--1-2-492-2026-09-22/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-492-2026-09-22/</guid><description>&lt;p&gt;Build 2026.09.22: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.491</title><link>https://proxmoxvex.com/changelog/#release--1-2-491-2026-09-22/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-491-2026-09-22/</guid><description>&lt;p&gt;Build 2026.09.22: UX round 2 (spec 097): shared plugin UI system, dashboard a11y/freshness polish&lt;/p&gt;</description><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.490</title><link>https://proxmoxvex.com/changelog/#release--1-2-490-2026-09-22/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-490-2026-09-22/</guid><description>&lt;p&gt;Build 2026.09.22: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.489</title><link>https://proxmoxvex.com/changelog/#release--1-2-489-2026-09-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-489-2026-09-21/</guid><description>&lt;p&gt;Build 2026.09.21: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.488</title><link>https://proxmoxvex.com/changelog/#release--1-2-488-2026-09-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-488-2026-09-21/</guid><description>&lt;p&gt;Build 2026.09.21: Full build pipeline&lt;/p&gt;&lt;p&gt;Perf: replace per-request `load_users()` full-table scans (SELECT * + Fernet-decrypt every row) with indexed `get_user()` single-row fetches in `build_authz_user` (utils/auth.py — was O(vms × users) on object-level ACL checks), `check_pbs_access` (helpers.py, now prefers the `g.current_user` stash from require_auth), `check_pool_permission` (static_files.py), SSE token minting, WS-token validation (realtime.py) and the VNC websocket auth path (vms.py).&lt;/p&gt;&lt;p&gt;Perf (wave 2): new `get_user_record`/`save_user_record` helpers (utils/auth.py) — indexed single-row fetch/write with the legacy-store fallback `require_auth` uses. Converted the remaining per-request single-user lookups: the whole `/api/auth/login` flow (LDAP provisioning checks, local-auth user fetch, unknown-user lockout marker), 2FA setup/verify/disable, change-password, cluster/realtime/nodes/search/reports/schedules/pbs/groups/server-access user fetches, the boot theme lookup (settings.py), webauthn `_user_row`, and the identical `_require_admin`/`_is_admin` helper duplicated across 17 native plugin entry points — each call previously decrypted the entire users table.&lt;/p&gt;&lt;p&gt;Perf (writes): login `last_login` bump, Argon2id rehash migration, 2FA recovery-code burn, the 2FA/password change saves, `admin_change_password`/`admin_disable_2fa`, `create_user`, avatar/preference updates, the OIDC/LDAP `*_provision_user` upserts, and `set_user_perms`/`remove_user_tenant_perms`/`get_my_permissions` (static_files.py — the last `users_db` write-back globals) now issue single-row `save_user` writes instead of `save_users()` rewriting + re-encrypting every row on each login or admin edit.&lt;/p&gt;&lt;p&gt;Tests: new `tests/test_user_record.py` pins the `get_user_record`/`save_user_record` contract (indexed hit, miss, empty-name short-circuit, DB-error legacy fallback; single-row write, fallback preserves other rows) plus the rbac TTL caches (`get_vm_acls` hit/invalidate/expiry, `get_custom_roles` lazy load + invalidation) — the seam conversions had left these paths with no direct coverage, and stale-seam mocks had let two token tests silently exercise a MagicMock instead of the real role check.&lt;/p&gt;&lt;p&gt;Cleanup: dropped the dead `globals.users_db` startup snapshot and the never-read `utils/audit.users_db` global — every per-request lookup now goes through `get_user_record`; app.py keeps the startup load purely for the user-count log line.&lt;/p&gt;&lt;p&gt;Perf (cache coherency): 4 read-only VM-ACL fallback checks (`_console_authz` in vms.py, cluster list in clusters.py, WS realtime cluster gate, `check_cluster_access` in helpers.py) bypassed the 30s `get_vm_acls` TTL cache with raw `load_vm_acls()` — a full ACL-table SELECT per call in the hottest authz paths; all now use the cached accessor (writes already invalidate). `get_user_clusters` likewise bypassed `_custom_roles_cache` via raw `load_custom_roles()` for custom-role users; now uses `get_custom_roles()`. `create_api_token` reads the owner via `get_user_record`. The remaining bare `get_db().get_user(...) or {}` call sites (VNC websocket token/session auth, the `g.current_user` fallbacks in helpers.py/vms.py, tenant-scope checks in costs.py/users.py, SSE token minting + WS subscribe in realtime.py, `check_pool_permission` in static_files.py) now use `get_user_record` too, so they also get the legacy-store fallback rather than silently treating a lookup miss as an empty user.&lt;/p&gt;&lt;p&gt;Perf: `load_server_settings()` gains a 5s read-through cache (`_settings_cache`) — it ran a SELECT + per-row json.loads + ~200-key defaults merge on every authed request via `get_session_timeout()`. `save_server_settings()` invalidates eagerly; callers get deepcopies so handler mutations can&apos;t corrupt the cache; the failure path is never cached.&lt;/p&gt;&lt;p&gt;Perf (frontend): the lib loader no longer fetches all CDN libraries in series — only the true dependency chains (react→react-dom, jspdf→jspdf-autotable) stay sequential, the rest load in parallel, cutting several round-trips before `ProxmoxVExLibsReady`.&lt;/p&gt;&lt;p&gt;i18n (frontend): only the English locale is inlined in the bundle now; non-`en` core dicts lazy-load via `ProxmoxVExI18n.loadNamespace` with an `i18nTick` re-render bump when the fetch lands, and language validation on login/session-restore checks `SUPPORTED_LANGS` instead of the inlined `translations` map.&lt;/p&gt;&lt;p&gt;Fix (vm-update-manager): restart-resilience for interactive applies — `start_scheduler()` now reconciles orphaned jobs (rows left in pending/running/awaiting_input with no worker or waiter behind them are failed with &quot;Job interrupted by service restart&quot;), interactive prompts are bounded by `PROMPT_TIMEOUT` (`VM_UPDATE_PROMPT_TIMEOUT`, default 300s) so an unanswered prompt can&apos;t pin a worker and starve the queue, `awaiting_input` suppresses new scheduled jobs for the guest like pending/running, prompt warnings flush job output immediately so the respond modal shows the real question, and SSH `transport is None` is guarded. Shared `_await_input` helper in the SSH driver.&lt;/p&gt;&lt;p&gt;Fix (truenas native): the &quot;ignored N instance(s) with missing/placeholder host&quot; warning fired on every `load_config()` poll tick — it now warns once per distinct dropped count, drops to debug on repeats, and resets when the config is fixed.&lt;/p&gt;&lt;p&gt;Tests: vm-update-manager unit coverage for the reconciliation/timeout paths (+285 lines), build-bundle assertion updates for the minified bundle.&lt;/p&gt;</description><pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.487</title><link>https://proxmoxvex.com/changelog/#release--1-2-487-2026-09-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-487-2026-09-21/</guid><description>&lt;p&gt;Build 2026.09.21: Full build pipeline&lt;/p&gt;&lt;p&gt;Fix `test_bundle_is_built_and_non_empty`: lower bundle-size assertion from 5 MB to 2 MB now that build.sh ships an esbuild-minified bundle with only the en locale inlined (~3.5 MB minified / ~4.5 MB unminified fallback)&lt;/p&gt;</description><pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.486</title><link>https://proxmoxvex.com/changelog/#release--1-2-486-2026-09-21/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-486-2026-09-21/</guid><description>&lt;p&gt;Build 2026.09.21: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.485</title><link>https://proxmoxvex.com/changelog/#release--1-2-485-2026-09-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-485-2026-09-20/</guid><description>&lt;p&gt;Build 2026.09.20: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.484</title><link>https://proxmoxvex.com/changelog/#release--1-2-484-2026-09-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-484-2026-09-20/</guid><description>&lt;p&gt;Build 2026.09.20: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.483</title><link>https://proxmoxvex.com/changelog/#release--1-2-483-2026-09-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-483-2026-09-20/</guid><description>&lt;p&gt;Build 2026.09.20: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.482</title><link>https://proxmoxvex.com/changelog/#release--1-2-482-2026-09-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-482-2026-09-20/</guid><description>&lt;p&gt;Build 2026.09.20: Full build pipeline&lt;/p&gt;&lt;p&gt;Performance pass across frontend and backend hot paths. Frontend: `app.bundle.js` dropped from ~8.26MB to ~3.54MB uncompressed (brotli ~1.39MB → ~550KB) — the build now inlines only the English core locale and lazy-fetches the active language via the existing `/i18n/locales/core/&lt;lang&gt;.json` route (`contexts.js` re-renders on arrival with English fallback until then), and the assembled bundle is minified with esbuild after the runtime IIFE wrap so top-level identifiers mangle safely (`web/Dev/build.sh`, `web/i18n/i18n.js` gained in-flight fetch dedupe + `window.translations` sync for late core dicts). `lib-loader.js` now loads independent CDN/local libraries in parallel instead of one serial chain — only react→react-dom and jspdf→jspdf-autotable keep ordering — and the production build strips the Babel loader/transform steps cleanly. Backend: `load_server_settings()` gained a 5s TTL read-through cache (invalidated eagerly by `save_server_settings()`) so `validate_session()` no longer runs a SELECT * + JSON merge per request, and the per-VM / token-validation authz paths (`build_authz_user`, SSE token, WS-token validate, `check_pool_permission`, VNC websocket auth) now use the indexed `get_user()` single-row lookup instead of `load_users()` (full-table scan + Fernet decrypt of every user). `tests/conftest.py` resets the settings cache per test.&lt;/p&gt;&lt;p&gt;`scripts/full-pipeline.sh`: fix the script failing `bash -n` outright — a `doesn&apos;t` apostrophe inside the single-quoted `start-licensing` `bash -c` block terminated the string early and mangled everything after it. Also: pre-flight now only requires the tools the selected stages actually use (`docker` only when a build/deploy/demo stage is requested; `helm`/`trivy`/`hadolint` dropped since bump-version.py is always called with `--skip-pipelines`), deploy stages are skipped when `docker-build` or `docker-run` fails instead of burning the ~200s health window and failing every downstream phase, the health loop bails early when the container exits, `container-health` is counted in the phase total, `--dry-run` no longer leaves an empty report dir, the report table separator column count was fixed, `--prune` documented, and the dead `$RUFF` detection removed.&lt;/p&gt;&lt;p&gt;VM Update Manager: tabbed UI rework. The guest add/edit form no longer sits permanently beside a cramped jobs/packages column — it&apos;s now a modal (`#guestModal`, Esc/overlay-click dismissible), and the page is split into Guests / Updates / Jobs tabs. Tab badges surface live counts (total pending updates, active jobs — amber-pulsing while any job is `awaiting_input`), the view auto-switches to Jobs when a check/apply/bulk run starts or a blocked job needs input, and to Updates when a check returns packages; the last viewed tab persists via localStorage. Job log and console sit side-by-side in the Jobs tab so clicking a row streams output without leaving the view. New i18n keys: `tabGuests`, `tabUpdates`, `tabJobs`, `updatesEmpty` in all 14 plugin locales.&lt;/p&gt;&lt;p&gt;VM Update Manager: fix the &quot;all applies sit on pending&quot; deadlock. Manual applies ran interactive `apt-get -V upgrade` / `dnf update` with no `-y`, so each apply parked its worker in `awaiting_input` on the routine &quot;[Y/n]&quot; prompt and — with only 4 workers — every job queued behind starved in `pending` forever. Interactive applies now run assume-yes + `DEBIAN_FRONTEND=noninteractive` with `dpkg --force-confdef/--force-confold` (the UI already collects the confirmation), and any residual guest prompt is bounded by `prompt_timeout` (`VM_UPDATE_PROMPT_TIMEOUT`, default 300s) instead of waiting forever. `start_scheduler` now reconciles orphaned non-terminal jobs on startup (`db.fail_stale_jobs`) — restart-stranded pending/running/awaiting_input rows were previously unanswerable zombies. `_is_due` also treats `awaiting_input` as an active job so schedules don&apos;t pile onto a blocked worker. UI: `pollJob` polls the single-job `job?id=` endpoint (jobs past the 50-row window used to drop out of polling), the job log auto-resurfaces the oldest `awaiting_input` respond modal after a page reload and highlights the row (`needs-input` pulse + localized status text), and `renderBulkResultActions` no longer reads an out-of-scope `res` (ReferenceError after a fully-successful bulk add that also skipped the guest-list refresh). Covered by new unit tests (prompt timeout/answer, assume-yes commands per distro, orphaned-job reconcile, `_is_due` blocking, driver `prompt_timeout` plumbing) and an e2e test asserting the respond modal resurfaces on load without a row click.&lt;/p&gt;&lt;p&gt;Fix `RuntimeError: Token ... has already been used once` raised by the request-id teardown hook (`app.py::_request_id_reset`): `do_teardown_request` can run twice for streamed responses under gevent (once when the wrapped generator finishes, once when the request ctx pops) and `ContextVar.reset()` throws on an already-consumed token. The stored token is now cleared before reset and a `RuntimeError` fallback calls `request_id_var.set(None)` so ids still can&apos;t bleed across requests on reused workers.&lt;/p&gt;&lt;p&gt;TrueNAS plugin: `_load_config` warned on every poll tick whenever a seeded placeholder instance (`&lt;TRUENAS_HOST_OR_IP&gt;`) was present — ~500 identical warnings/day. The warning now fires once per process per dropped-count and drops to debug on repeats; it re-arms if the config is fixed and a placeholder is later re-added.&lt;/p&gt;</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.481</title><link>https://proxmoxvex.com/changelog/#release--1-2-481-2026-09-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-481-2026-09-20/</guid><description>&lt;p&gt;Build 2026.09.20: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.480</title><link>https://proxmoxvex.com/changelog/#release--1-2-480-2026-09-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-480-2026-09-20/</guid><description>&lt;p&gt;Build 2026.09.20: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.479</title><link>https://proxmoxvex.com/changelog/#release--1-2-479-2026-09-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-479-2026-09-20/</guid><description>&lt;p&gt;Build 2026.09.20: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.478</title><link>https://proxmoxvex.com/changelog/#release--1-2-478-2026-09-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-478-2026-09-20/</guid><description>&lt;p&gt;Build 2026.09.20: Full build pipeline&lt;/p&gt;&lt;p&gt;Deep links + trusted devices: the dashboard URL sync now also covers the plugin console (`console_plugin`), automation/reports subtabs (`automation_subtab`/`report_subtab`), VM/node detail tab (`detail_tab`), Site Recovery plan + nested subtab (`sr_plan`/`sr_sub`) and the settings-modal section (`settings_section`) — every value is whitelisted/shape-checked before it touches state, and child-component params are carried through the URL rebuild so back/forward and reload restore the exact view. Trusted-device keys (&quot;remember this computer&quot;) now let a verified 2FA login mint a server-side record + a localStorage key so the second factor is skipped until the record expires/revokes, browser storage clears, or the client IP/device id changes (`ProxmoxVEx/utils/trusted_devices.py`, new `trusted_devices` table, login accepts `device_id`/`device_token`/`trust_device` and returns a one-shot `device_key`); keys are burned on password change, TOTP disable/re-enroll, admin user delete/deactivate and `kill-all` session clears. New i18n key `trustThisDevice` in all 14 core locales. Covered by `tests/test_trusted_devices.py` (input-shape rejection, fail-closed DB errors, hash-only storage) plus the licensing/support SPA routers verified end-to-end in headless Chromium (deep-link reload, back/forward, malicious-hash fallback).&lt;/p&gt;&lt;p&gt;Support: unlicensed users were sent to the paid-licence portal login with no way to file a request — `/api/v1/support/config` now also returns `community_url` (`SUPPORT_COMMUNITY_URL`, defaulting to `&lt;portal_url&gt;#community`) and the Licence-tab community notice links to it, landing on the licence-free community form (`/portal/#community`) served by proxmoxvex-support. New i18n key `supportCommunityFormLink` in all 14 core locales.&lt;/p&gt;&lt;p&gt;Spec 092/US090 follow-up — restore container resource bounds dropped by template regen: the spec&apos;s `deploy.resources` blocks were added to the generated `docker-compose.yml`/`docker-compose.replicated.yml` but not to the `*.template.yml` files `scripts/bump-version.py` renders them from, so the 1.2.477 version bump silently stripped them and `tests/test_resource_limits.py` failed 4 tests. The documented bounds (app `limits: 2G/2.0 cpus` + `reservations: 512M`, postgres `limits: 1G/1.0` + `reservations: 256M` — see `docs/deploy.md`) are now set in the templates AND the rendered files, and extended to the packaging templates that were never bounded: `packaging/rolling-replicated-service/docker-compose.rolling.replicated.{template.,}yml` and `packaging/canary-replicated-service/docker-compose.{template.,}yml` (canary + stable services).&lt;/p&gt;&lt;p&gt;Docs: added section index READMEs for architecture/, operations/, plugins/, product/ and ux/ — docs leaf pages breadcrumb to `/docs/&lt;section&gt;/` which 404d (86 broken links in the site audit); each README becomes the section index page. Plugins index is an auto-generated alphabetical list of all 77 plugin pages.&lt;/p&gt;&lt;p&gt;Pipeline mypy gate fixes: `_audit_key_cache` declared `dict[int, bytes | None]` (the chained `= {}` assignment inferred `dict[Never, Never]` and rejected every keyed access), the conditional `Fernet = None` fallback suppresses both mypy codes it trips (`misc` &quot;assign to a type&quot; + `assignment`), and the argon2 fallback bindings in `utils/auth` carry `no-redef` ignores since they intentionally redeclare the imported names when argon2-cffi is absent. `mypy` now reports 0 issues across 422 files.&lt;/p&gt;&lt;p&gt;Email-leak fix for build/test runs: `full-pipeline.sh` now launches the throwaway licensing service (port 8002) with `LICENCE_SMTP_HOST`/`USERNAME`/`PASSWORD` blanked (env vars override `.env` in pydantic-settings) so it never loads the live Gmail credentials from `proxmoxvex-licensing/.env`; a blank host makes `_send_email` fail fast with &quot;SMTP is not configured&quot; before any socket opens, and callers swallow it. Combined with a null-SMTP stub in the licensing test conftest, test/pipeline runs no longer send real mail to `*@example.com` fixtures — those sends were bouncing back into the admin inbox as delivery-failure spam. No production runtime change; the deployed licensing service keeps its real SMTP config.&lt;/p&gt;&lt;p&gt;Fix three type-checker errors in `tests/test_stepup.py`: the `req_ctx` fixture&apos;s `request.session = {...}` assignment now carries `# type: ignore[attr-defined]` (session is attached at runtime by `require_auth` and isn&apos;t on Flask&apos;s `Request` type — a plain `setattr` would satisfy pyright but trips Ruff B010), and the two `require_stepup` tests assert `resp is not None` before unpacking/subscripting the `(body, status)` challenge tuple — a clearer failure message than a TypeError if the challenge regresses to None.&lt;/p&gt;&lt;p&gt;Progressive exponential lockout on failed logins (spec 087-vex-brute-force-lockout), building on the existing IP/username attempt trackers: lockout duration now escalates `lockout_time × 2^(cycle-1)` per repeat lockout (300s → 600 → 1200 …) capped at the new `login_max_lockout` setting (24 h default, added to `constants.py`/`helpers.py`), and a tracker quiet for longer than `max_lockout` forgives the escalation so an old attack doesn&apos;t double today&apos;s fat-finger. Username tracking now covers nonexistent names too — previously `record_failed_attempt` skipped unknown usernames, which made the `locked: true` response itself an account-existence oracle (an attacker could lock-probe a name and read existence off the flag); the locked-user path also burns the same `dummy_verify_password` ~100 ms as the unknown-user path so response timing doesn&apos;t split them either (SC-002). Username lockouts are now audited as `auth.user_locked` (previously only `auth.ip_locked` existed), `auth.ip_locked` records the escalated duration, and the 401 `Retry-After` header/`retry_after` body now carry the real lock end instead of the base constant — the five fresh-lockout call sites share a `_locked_response` helper. Tracker growth is bounded (stale entries evicted past 10k) since random-name stuffing could otherwise grow the map without limit. Covered by `tests/test_login_lockout.py` (7 tests).&lt;/p&gt;&lt;p&gt;Session-ID rotation on login and step-up elevation (spec 086-vex-session-rotation): a session id captured or planted before authentication can no longer be replayed into the authenticated or elevated state. New `utils/auth.py::rotate_session_id` atomically rekeys the server-side session dict (preserving role, `remember`, and the `elevated_*` step-up stamps) and invalidates the old id immediately. The password-login and OIDC-callback paths now destroy whatever session id the client presented before `create_session` mints the fresh one — a fixation victim&apos;s login also kills the attacker&apos;s planted session. `POST /api/auth/stepup/verify` rotates the presented session id after `grant_elevation`, sets the rotated `session_id` cookie with the same flags as login, and returns `session_id` in the JSON body so clients adopt it; API-token requests (no session id) skip rotation silently. Frontend: `AuthContext` now exposes `setSessionId`, the step-up modal swaps the in-memory session id on success (it feeds `?session=` WebSocket auth and `X-Session-ID` headers), and the fetch interceptor strips a captured `X-Session-ID` header from the post-elevation retry — otherwise the header would win over the fresh cookie and replay the dead id. Covered by `tests/test_session_rotation.py` (9 tests); two stepup E2E tests updated to adopt the rotated id like a real client.&lt;/p&gt;&lt;p&gt;Remove the vestigial `dbmod.DATABASE_FILE` assignments from the test harness (`tests/conftest.py` `db`/`_integration_app` fixtures, `tests/test_csp_nonces.py`, `tests/test_csrf_origin.py`): the attribute fed the removed SQLite/SQLCipher backend (`get_db()` is PostgreSQL-only via `ProxmoxVExPGDB`), so assigning it was a silent no-op — and pyright flagged it as an unknown module attribute. The `db` fixture&apos;s save/restore of the attribute went with it, and `tests/README.md` now documents `{CONFIG_DIR,KEY_FILE}` only.&lt;/p&gt;&lt;p&gt;Fix four type-checker errors in `native/registry.py`: `Module.__file__` is `str | None` in typeshed (namespace/frozen modules can lack it), so `os.path.dirname(mod.__file__)` was ill-typed. The duplicated `import_module` + `dirname` in `_native_config_path`/`_native_i18n_path` is factored into `_native_module_dir`, which raises `ValueError` when the module has no file-backed path — consistent with the existing `Invalid module_id` failure mode. No runtime change for real (file-backed) modules.&lt;/p&gt;&lt;p&gt;Scoped API tokens (spec 085-vex-api-token-scopes): tokens can now carry a list of `&lt;area&gt;:&lt;read|write&gt;` grants stored in a new `api_tokens.scopes` column (NULL = unscoped legacy token with full access, `&apos;[]&apos;` = a scoped token with zero grants). On every token-authenticated request, `utils/auth.py::require_auth` derives the required scope from (method, path) via `utils/scopes.py::required_scope` — GET/HEAD/OPTIONS → `read`, everything else → `write`; `/api/clusters/&lt;id&gt;/vms` → `vms`, plugin-proxied calls → `plugins`, native module routes → the module id — and denies with 403 `MISSING_SCOPE` when the token&apos;s list doesn&apos;t cover it (`scope_allows` matches exact, `&lt;area&gt;:*`, `*:&lt;action&gt;`, or `*`), auditing the denial as `token.scope_denied`. `create_api_token`/`list_user_tokens`/`validate_api_token` accept and return `scopes`; the admin token-create endpoint validates them and a new `GET /api/auth/token-scopes` serves the selectable catalog. The api-token-manager plugin now writes the enforced column (canonical `vms:read` spelling; its legacy action-first `read:vms` values are normalised by `utils/scopes.py::normalize_scope`, and rows minted before the column existed fall back to scope-shaped `permissions` entries), labels unscoped legacy tokens as `*` in the UI, and preserves scopes across rotate. Covered by `tests/test_api_token_scopes.py` (17 tests).&lt;/p&gt;&lt;p&gt;Fix 22 type-checker errors in `plugins/api-token-manager/__init__.py`: `_username()` now reads the runtime-attached `request.session` via `getattr` (same approach as `_req_session` in `api/auth.py` — the attribute isn&apos;t on Flask&apos;s `Request` type) and returns `str` with a `&quot;&quot;` fallback that fails closed (no token owner or admin lookup can match it); all five `_can_manage_token` call sites now check `if record is None: return err` instead of `if err: return err` — identical semantics (record is `None` iff `err` is set) but it narrows the record to `dict` for the checker; `record.get(&quot;username&quot;)`/`record.get(&quot;name&quot;)` become `record[&quot;username&quot;]`/`record[&quot;name&quot;]` where the SELECT always returns those columns, so the `str`-typed `revoke_api_token`/`create_api_token`/`list_user_tokens` params stop receiving `Unknown | None`. No runtime behavior change.&lt;/p&gt;&lt;p&gt;Fix eight type-checker diagnostics in `api/auth.py`: the module-level `try/except` import of `pyotp`/`qrcode`/`io` left the names possibly unbound — they now import locally at each use site, after the `TOTP_AVAILABLE` guard (same pattern as the `core/db.py` AESGCM/Fernet fix; the flag already probes via `importlib.util.find_spec` in `utils/auth.py`). `img.save(buffer, format=&quot;PNG&quot;)` keeps its `# type: ignore` — qrcode&apos;s no-PIL `PyPNGImage` lacks the kwarg and the `except TypeError` retry is intentional. Also dropped a dead `request.cookies.get(...) or ...` expression left over from before the invalidate-all-sessions change (the comment already documents &quot;no except_session — all go&quot;). No runtime behavior change.&lt;/p&gt;&lt;p&gt;`utils/scopes.py`: collapse the `scope_allows` matching loop into a single `any()` per Ruff&apos;s suggestion — identical semantics (exact scope, `&lt;area&gt;:*`, `*:&lt;action&gt;`, or `*`), verified against the matching/wildcard/empty/None cases.&lt;/p&gt;&lt;p&gt;Fix ten type-checker diagnostics in `app.py`: `_check_api_rate_limit` now receives `&quot;&quot;` when `get_client_ip()` returns `None` (`request.remote_addr` is nullable; undetectable IPs share one rate-limit bucket — fail-safe); the `geventwebsocket` optional import binds `WebSocketHandler = None` in the `ImportError` branch so the class can&apos;t be &quot;possibly unbound&quot;, the availability flag is replaced by an `is not None` check the checker can narrow, a separate `ws_handler_class: type | None` variable carries the optional handler to `server_kwargs` so the class declaration keeps its own type, and the `super(WebSocketHandler, self)` skip-the-upgrade-handshake call captures the base class as a `run_application` default arg (bound at class-def time) since narrowing doesn&apos;t flow into method bodies; removed three dead expressions (`DATA_DIR / &quot;ssl&quot;`, `port + 1`, `port + 2`) left over from refactors — the cert dir is already created by `os.makedirs(os.path.dirname(cert_file))` and console ports are computed inside `_start_console_servers`. No runtime behavior change.&lt;/p&gt;&lt;p&gt;Fix five type-checker errors in `api/settings.py`: the restore endpoint&apos;s `except json.JSONDecodeError` was unreachable (it subclasses the earlier `ValueError` catch) — reordered so malformed JSON still reports &quot;Invalid backup file format&quot; while other decryption `ValueError`s keep &quot;Backup decryption failed&quot;; tenant restore now binds/coerces the id once (`str(tenant.get(&quot;id&quot;) or &quot;&quot;)`) so `save_tenant` never receives `None`; `check_ip_whitelist` and `update_ip_whitelist` coerce `get_client_ip()` to `&quot;&quot;` since `request.remote_addr` can be `None` — empty string fails closed through the existing &quot;No IP detected&quot; deny path, so no behavior change for real requests.&lt;/p&gt;&lt;p&gt;CSP now issues a per-request nonce instead of &apos;unsafe-inline&apos; in `script-src` (spec 084-vex-csp-nonces): an injected `&lt;script&gt;` without the nonce can no longer execute on the SPA shell, error pages, or API responses. The nonce is generated per request in `app.py` (`utils/csp.py::generate_nonce`, flask `g` aliased to `flask_g` to avoid the `ProxmoxVEx.globals` collision), exposed to the document via an injected `&lt;meta name=&quot;csp-nonce&quot;&gt;` in `_serve_index` and to Jinja via a context processor, and stamped onto `&lt;script&gt;` tags where the server rewrites HTML (air-gap prelude, plugin `ui.html`, native-integration UI documents). Plugin/native UI iframe pages keep their inline event-handler attributes (`onclick=` etc., ~270 sites) working through the CSP3 `script-src-attr &apos;unsafe-inline&apos;` + `script-src-elem &apos;nonce-…&apos;` split — modern browsers still block injected `&lt;script&gt;` elements there while browsers old enough to lack the split directives lose handler support rather than gaining injection coverage. `style-src` intentionally keeps &apos;unsafe-inline&apos; (`style=` attributes can&apos;t be nonced; ~750 uses across plugin markup and the error template&apos;s `&lt;style&gt;` block). `connect-src` still allows `ws:`/`wss:` for noVNC/xterm consoles.&lt;/p&gt;&lt;p&gt;Add app-layer enforcement of the licensing-service firewall blocklist: new `ProxmoxVEx/services/firewall_feed.py` polls `GET /v1/internal/firewall/blocklist` every 60s (authenticated with `X-Analytics-Key`) and `app.py`&apos;s `validate_request` returns an early `403` for listed IPs — so scanners banned by the licensing abuse detector are also rejected on the app and demo surfaces, whose Cloudflare-tunneled traffic bypasses the host nftables drop (the tunnel always arrives from the local cloudflared/docker-gateway peer). Real visitor IPs are derived with the same verified-IP rule as the licensing service (`CF-Connecting-IP` → `True-Client-IP` → last `X-Forwarded-For` hop only when the socket peer is private; a public peer is authoritative so forged headers cannot redirect a ban). Fail-open and off by default: no fetch and no blocking unless `PROXMOXVEX_FIREWALL_KEY` (the shared `LICENCE_ANALYTICS_INGEST_KEY` secret) is set; `PROXMOXVEX_FIREWALL_BLOCKLIST_URL` overrides the derived `&lt;LICENCE_SERVICE_URL&gt;/v1/internal/firewall/blocklist` endpoint (e.g. `http://172.17.0.1:8001/...` for same-host containers). The module also reports back: error responses (≥400) and post-ban hits are queued and POSTed to `/v1/usage/report` so the licensing detector&apos;s scan-storm, bad-path and scanner-UA rules see app/demo-surface attacks; `PROXMOXVEX_ANALYTICS_INGEST_URL` overrides the derived ingest endpoint.&lt;/p&gt;&lt;p&gt;Lint cleanup: dropped redundant Tailwind display/font-size classes that lost to a same-property class anyway (`block` vs `flex`, `text-xs` vs `text-sm` — no visual change), merged the duplicate `## Unreleased` changelog section into the canonical top one, and backticked a `/docs/&lt;section&gt;/` literal that markdownlint flagged as inline HTML.&lt;/p&gt;</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.477</title><link>https://proxmoxvex.com/changelog/#release--1-2-477-2026-09-20/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-477-2026-09-20/</guid><description>&lt;p&gt;Build 2026.09.20: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.476</title><link>https://proxmoxvex.com/changelog/#release--1-2-476-2026-09-18/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-476-2026-09-18/</guid><description>&lt;p&gt;Type-checker hygiene pass across the IDE diagnostic backlog: `check_cluster_access`/`check_pbs_access`/`check_vmware_access` are now annotated `-&gt; tuple` (the same convention `get_connected_manager` already used and documented — callers unpack `ok, err = ...` then `if not ok: return err`, and checkers can&apos;t correlate the pair, so a precise type injected `None` into ~400 callers&apos; inferred route returns and broke Flask&apos;s RouteCallable check; clears ~45 diagnostics in clusters.py alone). `request.session`/`request.validated` reads and writes moved to the established `getattr`/`setattr` workaround (api/__init__, converter, helpers, settings), and `vms._SessionRequest` gained a declared `validated` attribute to match `session`. Conditional imports now bind the names explicitly so guarded uses aren&apos;t possibly-unbound: `Fernet` in core/config (plus a `Fernet is None` narrowing guard in `get_fernet`) and `argon2`/`PasswordHasher`/`VerifyMismatchError` in utils/auth as `Any`. Latent edge cases fixed along the way: `with_retry` could `raise None` when attempts&lt;=0 (now calls `fn` once), `_uncaught` could pass a None `HTTPException.code` into `api_error` producing an invalid (json, None) tuple (500 fallback), `QueuedTask.error`/`OntapClient._retry_sleep` annotated so assignments type-check, `request_id_var` annotated `ContextVar[str|None]` so `.set(str)` isn&apos;t flagged, `JsonFormatter.format` renamed its parameter to `record` matching `logging.Formatter.format`, `purge_soft_deleted(days: int | None)`, `_pg_conn` reads follow the file&apos;s `getattr` + None-narrow convention, `ns`/`r` bound before their loops, `params` dict annotated for mixed str/int values, `retention_sweep` and test `fetchone()[0]`/`extractfile()` subscripts get Optional-aware asserts, croniter `get_next(datetime)` cast to `datetime` (its stub declares float). Tests: Optional narrowing asserts (spec/loader, tar members, regex match, bulk job, pixel buffer), mock attrs moved onto the `MagicMock` local in test_native_retry so `side_effect`/`call_count` aren&apos;t read off a typed `requests.Session` method, `cast` for the `SimpleNamespace` `self` stand-in in test_upstream_trace, and `pyrightconfig.json` gains `extraPaths` (`scripts`, `tests/e2e`, `web/i18n`) so the suites&apos; runtime `sys.path.insert` imports resolve statically. The `actions/setup-python` pins in e2e.yml/security.yml were a corrupted SHA mislabeled `# v5.0.0` (didn&apos;t exist in the repo — CI would fail at job resolution); repinned to the real v5.6.0 commit it clearly forked from. specs/092 tasks.md markdown whitespace normalized (collapsed double blank lines outside code fences, blank lines around lists).&lt;/p&gt;&lt;p&gt;Spec 092 verification fixes — full-suite regressions surfaced by the final `pytest tests -q` run: (1) `get_db()` heals the singleton via `ensure_connected()` on every access, breaking 147 plugin contract tests whose `FakeDB` doubles masquerade as the singleton — all 15 fake-DB definitions across the shared test utilities and inline `_FakeDB` classes gained the `ensure_connected` no-op (sqlite fakes never lose their connection). (2) `JsonFormatter.format` called `json.dumps` unguarded — a dumps failure funneled through `emit → handleError → format` and recursed; the formatter now falls back to a minimal plain JSON line, since a logging formatter must never raise on a record. (3) US081&apos;s 15s `statement_timeout` killed `_init_db` mid-convergence on this loaded test host (CREATE TABLEs taking &gt;11s) — boot now widens the session budget to 120s for the whole schema-init sequence and restores the operator&apos;s GUC in `finally` (SET LOCAL couldn&apos;t span init&apos;s per-table-group commits). (4) `vm_list_cache` is a process-wide TTL cache keyed by (cluster_id, user) — tests reuse `(&quot;pg_c&quot;, &quot;root&quot;)` so one test&apos;s cached list leaked into the next test&apos;s fresh fake manager (ordering flake); conftest&apos;s `_reset_rbac_caches` now invalidates it alongside the other per-test state resets.&lt;/p&gt;&lt;p&gt;Spec 092/US092 — read-only root filesystem compatibility (roadmap 183): the compose template now runs the app with `read_only: true` — a compromised process can&apos;t persist inside the image — with every write path an explicit mount: `tmpfs` for `/tmp` (tempfile) and `/run` (ssh_pool socket dir), plus `proxmoxvex_plugins:/app/plugins` alongside the existing config/logs volumes. Because a fresh plugins volume starts empty and would shadow the baked plugin set, the Dockerfile keeps a pristine `/app/plugins-seed` copy and new `ensure_plugins_seeded()` in `core/config.py` restores it on first boot only (a populated volume is never overwritten, so installed plugin upgrades stay authoritative; a missing dir is created when the parent allows). Boot also runs `check_writable_paths()` — a `writable_paths_report()` audit over the app&apos;s required write paths — logging `[STARTUP] ... is not writable` with the missing mount&apos;s purpose instead of a late mid-request crash; both checks are error-guarded so observability can&apos;t break boot. `docs/deploy.md` documents the hardened layout and notes app updates go through image pulls (the US089 rolling path), not in-place writes. Covered by `tests/test_readonly_rootfs.py` (7 tests: read_only declared, tmpfs mounts, all write paths covered by volumes — the independent test, seed copy in image, first-boot seeding, populated-volume preservation, per-path audit reporting).&lt;/p&gt;&lt;p&gt;Spec 092/US091 — docker log driver rotation (roadmap 182): container stdout/stderr was unbounded — a chatty app could fill the host disk with json-file logs and take the node down with it. Both `docker-compose.yml` and `docker-compose.template.yml` now configure the app service `logging: { driver: json-file, options: { max-size: 50m, max-file: 5 } }` — a hard 250MB per-service cap; the demo/smoke override files inherit the base service so they get the bound for free. Covered by `tests/test_log_rotation.py` (3 tests: template exists, both files set json-file with both rotation bounds — the independent test, and the effective cap stays in a sane hundreds-of-MB range rather than tens of GB).&lt;/p&gt;&lt;p&gt;Spec 092/US090 — container resource limits + right-sizing guidance (roadmap 181): the compose template ran both services unbounded — a runaway request loop or huge export could starve postgres/neighbors, and a busy host could evict the app with no reservation floor. `docker-compose.yml` now sets `deploy.resources` on both services: proxmoxvex `limits: 2G/2.0 cpus` + `reservations: 512M`, postgres `limits: 1G/1.0` + `reservations: 256M` — the DB is bounded too since it shares the host and can starve the app just as easily. New `docs/deploy.md` carries the sizing rationale the numbers don&apos;t explain: +512M memory per concurrent P2V/conversion job, ~+100M per ~5 extra clusters, CPU scaling guidance (IO-bound web tier — add cores for parallel conversions, not traffic), the k8s requests/limits equivalent, and a verify-under-load procedure via the US067 load suite + `docker stats` (OOM-restart = raise memory, latency = CPU). Covered by `tests/test_resource_limits.py` (6 tests: limits present, reservations present, limit exceeds reservation, postgres bounded, restart policy, guidance documented).&lt;/p&gt;&lt;p&gt;Spec 092/US089 — zero-downtime rolling update procedure (roadmap 180): upgrades had no supported path — compose the US083/US084 primitives instead. New `docs/upgrading.md` documents the drain → migrate → swap sequence: `docker stop -t 45` sends SIGTERM (the US084 drain — healthz 503s, LB pulls the node, in-flight finishes inside 25s, bulk jobs checkpoint), `compose up -d` swaps the image, and boot-time schema convergence (with US074&apos;s automatic pre-migration pg_dump) IS the migrate phase — the node is &quot;back&quot; only when the deep `/api/healthz` returns 200, never the shallow liveness. New `scripts/deploy-rolling.sh` drives it per node SEQUENTIALLY (a parallel fan-out would take the fleet down): SIGTERM stop with a grace period exceeding the drain window, pull+up, poll healthz up to VEX_HEALTH_TIMEOUT, and on failure repin VEX_ROLLBACK_IMAGE on that node and abort leaving remaining nodes untouched — the fleet keeps serving the old version. Covered by `tests/test_rolling_update.py` (8 structural tests: doc covers drain/migrate/swap + rollback + healthz/SIGTERM primitives, script executable + bash-clean, sequential per-node loop, SIGTERM-compatible stop with ≥drain grace, healthz gate, rollback + exit 1, migration acknowledged).&lt;/p&gt;&lt;p&gt;Spec 092/US088 — active-passive HA with DB leader lock (roadmap 179): running two instances previously meant both schedulers dispatched the same due jobs — split-brain writes with no failover story. Opt-in via `PROXMOXVEX_HA`: `_ensure_leader_lock_table()` converges a single-row `leader_lock(id, owner, ts, fence)` table, and `leader_loop()` (started in main only when HA is on) renews the lock every `ttl/3` via one atomic `UPDATE...RETURNING` — the UPDATE itself is the whole election, no read-then-write race window: `owner = me OR ts &lt; expiry` means a live holder renews and everyone else gets no row. A dead holder&apos;s lock expires past the 15s TTL; the standby takes it with `fence = fence + 1` so a stale leader waking late can&apos;t out-write the fence. `LEADER` state feeds `is_leader()`, which gates `run_scheduled_tasks()` — the dispatch path — and defaults to always-True when HA is off, so single installs are completely unaffected. Covered by `tests/test_leader_lock.py` (8 tests: table shape, first acquire wins, standby blocked while held — the no-split-brain half, TTL-expired takeover with rising fence — the failover half, LEADER state via tick, HA-off always-leader, scheduler gating, main wiring).&lt;/p&gt;&lt;p&gt;Spec 092/US087 — backup/restore CLI command (roadmap 178): disaster recovery was a manual pg_dump + config-copy procedure. New `ProxmoxVEx/cli.py` (`python -m ProxmoxVEx.cli backup|restore`, plus `scripts/backup.sh` as a thin wrapper): `backup` writes a tar.gz holding a consistent `pg_dump -Fc` snapshot (`db.dump`), the config directory (`config/` — settings, keys, keystore material), and a `MANIFEST` carrying app version/build/created timestamp. `restore` gates on the manifest — a foreign version aborts with the mismatch named unless `--force` (cross-schema-epoch restores are opt-in) — then unpacks config/ with path-traversal-safe extraction (a crafted `config/../../etc` member raises instead of escaping; payloads are written by hand so the prefix can&apos;t leak into the layout) and runs `pg_restore --clean --if-exists --no-owner --no-privileges` into the configured DSN. Verified with a real round-trip: marker setting + config file → backup → both wiped → restore → identical state returns. Covered by `tests/test_backup_restore_cli.py` (4 tests: archive contents, wipe/restore round-trip — the independent test, foreign-version rejection, `-m` CLI entrypoint).&lt;/p&gt;&lt;p&gt;Spec 092/US086 — generated environment variable reference (roadmap 177): ~70 env vars were scattered across source with no operator reference. New `scripts/gen_env_docs.py` AST-scans the codebase for `os.environ.get`/`os.getenv`/`environ.get`/`os.environ[...]` reads with literal keys and renders `docs/env.md` — a table of variable | default | read-sites. Computed defaults (f-strings like the DATABASE_URL&apos;s assembled DSN) are captured via `ast.unparse` rather than dropped as *(required)*, and a later bare `.get()` can&apos;t erase a documented fallback (first non-None default wins). The generated block sits inside `&lt;!-- GENERATED --&gt;` markers (same convention as gen_plugin_docs.py) so human operator notes outside the markers survive regeneration; output is deterministic for clean diffs. Covered by `tests/test_env_docs.py` (6 tests: generator runs + writes, every key var present — the independent test, defaults + read locations rendered, idempotent regen, markers preserve human text, default output is the tracked docs/env.md).&lt;/p&gt;&lt;p&gt;Spec 092/US085 — startup config validation with actionable errors (roadmap 176): env misconfig previously surfaced late and cryptically — a wrong-scheme `PROXMOXVEX_DATABASE_URL` as a psycopg2 failure at first query, a garbage `PROXMOXVEX_PORT` as a silent fallback to a port the operator didn&apos;t ask for. New `validate_startup_config(env)` in `core/config.py` returns `(errors, warnings)`: errors abort boot naming the variable and the fix — non-`postgresql://` DSNs (PostgreSQL is the only backend; the URL has a localhost default so a typo&apos;d scheme is the real misconfig, not a missing value) and the four `PROXMOXVEX_*_PORT` vars validated as 1–65535 accepting the `tcp://&lt;ip&gt;:&lt;port&gt;` form Kubernetes injects (the exact regex app.py&apos;s runtime parser uses — the validator can&apos;t reject a form the server accepts). Warnings log-but-boot: demo mode with the shipped default `PROXMOXVEX_DEMO_TOKEN_SECRET` is flagged as forgeable. `main()` runs the validator right after logging setup — errors print `CONFIG ERROR:` lines and `SystemExit(2)` before any DB/server work. Covered by `tests/test_config_validation.py` (6 tests: clean env passes, bad DSN named with postgresql:// hint — the independent test, bad ports rejected, k8s tcp:// form accepted, default demo secret warned, validator wired into main&apos;s boot path).&lt;/p&gt;&lt;p&gt;Spec 092/US084 — graceful shutdown on SIGTERM (roadmap 175): SIGTERM previously stopped the HTTP server mid-request with no drain, dropping user operations on every rolling update. New drain machinery in `background/__init__.py`: `mark_draining()` flips an event that makes `/api/healthz` return 503 immediately (readiness pulls the instance from rotation while the shallow `/api/health` liveness stays up — a draining process is still alive); an in-flight request counter driven by `before_request`/`teardown_request` hooks on the app factory (teardown, not after_request, so the count can&apos;t leak on a 500); `drain_in_flight(timeout=25)` waits for in-flight to reach zero inside a deadline — a stuck request can&apos;t hold shutdown forever — then `checkpoint_bulk_jobs()` moves live bulk jobs to a terminal `interrupted` status so nothing reports `running` across the restart. The app.py signal handler now marks draining and runs the drain-then-stop sequence on a spawned greenlet; a drain failure logs and still stops (shutdown must never hang on its own safety net). Covered by `tests/test_graceful_shutdown.py` (7 tests: healthz 503 + health 200 while draining — the independent test, counter accuracy + never-negative, drain waits then releases, deadline bounds a stuck request, running jobs → interrupted while terminal states untouched, signal-handler drain wiring, hooks registered).&lt;/p&gt;&lt;p&gt;Spec 092/US083 — container HEALTHCHECK verified + locked (roadmap 174): the Dockerfile already carries a bounded HEALTHCHECK (interval/timeout/start-period/retries) that probes `http://127.0.0.1:5000/api/health` via the image&apos;s own python3/urllib — no curl dependency — and `|| exit 1`s so Docker marks the container unhealthy. New tests lock the two invariants an edit could silently break: the probe must hit the SHALLOW liveness path (`/api/health` — process-up), never the deep `/api/healthz` readiness probe whose dependency checks would restart the container on a transient cluster/license/disk blip; and the CMD must exit non-zero on failure since that&apos;s the only signal Docker reads. Covered by `tests/test_docker_healthcheck.py` (6 structural tests).&lt;/p&gt;&lt;p&gt;Spec 092/US082 — connection pool utilization metrics (roadmap 173): pool sizing/leak data previously didn&apos;t exist because the &quot;pool&quot; is a singleton `get_db` conn plus uncounted ad-hoc `PGConnection`s (reports/dbcrypto/syslog/native workers). `PGConnection` now tracks a class-level `live_count()` of open raw connections — incremented at connect/reconnect, released once via a `_counted` slot flag on `close()`/`reconnect()`/`__del__` so dead-conn swaps and double-close can&apos;t skew it (the flag guards transitions, not the driver&apos;s `closed` state — a server-killed conn still held a slot). `db.db_pool_stats()` reports `size=1`, `checked_out` (singleton alive — 0 signals the outage window before US079&apos;s heal), and `overflow = live_count - checked_out` (ad-hoc conns — the leak signal). `/api/metrics` emits all three as `ProxmoxVEx_db_pool_*` gauges inside the existing metrics_public/Bearer auth. Covered by `tests/test_db_pool_metrics.py` (5 tests: singleton stats, a live extra conn moves overflow and closing returns baseline — the independent leak test, dead singleton reports 0 until healed, gauges in scrape, live-count accuracy during scrape).&lt;/p&gt;&lt;p&gt;Spec 092/US081 — statement timeouts on DB queries (roadmap 172): a runaway query could previously pin the singleton pooled connection forever. `PGConnection` now sets `statement_timeout = &apos;15s&apos;` as a session GUC alongside the US078 lock/idle GUCs (env-tunable via `PROXMOXVEX_STATEMENT_TIMEOUT_MS`), so Postgres aborts any statement past the budget with QueryCanceled — the US079 error path rolls the tx back and frees the worker immediately. Reports/exports get the sketch&apos;s explicit opt-out: `db.long_query_budget(ms=120000)` issues `SET LOCAL statement_timeout` for the current transaction only, restoring the session default on exit (SET LOCAL never survives tx end, and the explicit restore keeps post-budget statements in the same tx bounded). Wired at its first real call site: `verify_audit_log_integrity`&apos;s end-to-end audit_log walk. Covered by `tests/test_db_statement_timeout.py` (5 tests: 15s session default, budget relax+restore in-tx, a real pg_sleep past a 200ms budget aborted with the conn immediately reusable — the independent test — no leak across commit, and a 100ms bound aborting promptly).&lt;/p&gt;&lt;p&gt;Spec 092/US080 — deterministic demo seed dataset (roadmap 171): the demo/e2e environment previously had no guarantee that the seeded entities were stable across runs or boots — the spec&apos;s &quot;same 3 clusters / fixed alert ids every run&quot; contract was implicit. Verified and locked the existing `seed_demo()` path (called from app.py under `PROXMOXVEX_DEMO_MODE`): the three demo clusters carry fixed ids `demo`/`demo-us`/`demo-sg`, the 12 alert rules use deterministic `{cluster}-{rule}` ids, migration history, PBS/vCenter/NetApp integrations and the demo user all converge idempotently — a second `seed_demo()` produces an identical entity set with no duplicates, and the guest dataset the cluster mock serves is a fixed `fixtures/demo/inventory.json` (qemu+lxc ids stable across loads). Each seeder is individually try/except-guarded with `[DEMO_SEED]` structured logging so one bad fixture can&apos;t abort the set. Covered by `tests/test_demo_seed.py` (5 tests: fixed dataset present, idempotent re-seed, no seed outside demo mode, startup wiring + per-seeder guards, inventory fixture determinism).&lt;/p&gt;&lt;p&gt;Spec 092/US079 — DB connection health check + auto-recovery (roadmap 170): the app pools ONE psycopg2 connection behind the get_db() singleton, so a DB restart left every query raising OperationalError until a manual restart. Three pieces land where SQLAlchemy&apos;s pool_pre_ping + handle_error would: (1) `PGConnection.is_alive()`/`reconnect()` — liveness probe + full reconnect re-applying the US078 session GUCs and clearing the constraints cache; (2) `PGCursor.execute` catches connection-level errors (OperationalError/InterfaceError on a dead conn — `raw.closed` discriminates from non-conn OperationalErrors like read-only-tx), reconnects so the NEXT statement always works, and retries the failed statement ONLY when the transaction had no prior writes — replaying after read-only predecessors loses nothing, while a write-containing tx must fail whole rather than silently replay one statement of a dead transaction (atomicity-safe by construction via a `_tx_writes` flag reset on commit/rollback/reconnect); (3) `get_db()` heals the singleton via `ensure_connected()` when the conn is already marked closed — the cheap pre-ping path. Verified with a real `pg_terminate_backend` kill: the next query reconnects+retries and returns, `is_alive()` flips false then true after reconnect, a client-closed conn is healed at checkout, and a mid-transaction failure propagates without replay while the conn still recovers for subsequent calls. Covered by `tests/test_db_recovery.py` (4 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US078 — bounded lock waits: busy_timeout/WAL adapted to Postgres (roadmap 169): the spec targeted the removed SQLite backend; the faithful analog on PostgreSQL is session-level lock bounds rather than journal pragmas (MVCC already delivers WAL&apos;s core guarantee — readers never wait on writers). `PGConnection.__init__` now sets `lock_timeout = &apos;5s&apos;` (busy_timeout&apos;s analog — a blocked write raises LockNotAvailable instead of serializing indefinitely, e.g. boot-time convergence DDL behind a wedged transaction) and `idle_in_transaction_session_timeout = &apos;60s&apos;` (a transaction sitting idle holding locks is aborted rather than blocking everyone), plus `application_name` for pg_stat_activity observability. Durability is explicitly untouched — `synchronous_commit` stays on; the WAL analog must not trade commit guarantees for speed. Covered by `tests/test_db_lock_timeouts.py` (4 tests: GUCs configured, synchronous_commit unchanged, a real second-connection write-lock doesn&apos;t block a reader, and a blocked write raises LockNotAvailable in ~5s instead of hanging).&lt;/p&gt;&lt;p&gt;Spec 092/US077 — missing-index audit and additions (roadmap 168): a live audit of the converged schema showed several tables serving frequent WHERE/JOIN predicates with only a primary key — `clusters`/`users`/`sessions`/`scheduled_actions`/`scheduled_task_runs`/`update_schedules`. New `HOT_INDEXES` registry on the DB class declares the hot columns; `_ensure_hot_indexes()` converges ten indexes at boot — `deleted_at` on the three US075 soft-delete tables (every list/get filters it), `users.tenant`, `sessions.username`, `scheduled_actions.cluster_id`, `scheduled_task_runs.(task_id, started_at)` (run-history join + US076 sweep), `drift_events.detected_at` and `site_recovery_events.started_at` (bare-timestamp retention predicates — the existing composites don&apos;t lead with them). `audit_missing_indexes()` reports registered columns lacking a LEADING-column index — a composite only covers a bare predicate when the column comes first, so the check parses indexdef rather than matching names. `scripts/audit_indexes.py` exposes the report for operators/CI (exit 1 on missing). Covered by `tests/test_index_audit.py` (5 tests: clean after boot, flags a dropped index, all ten indexes exist, script audit + exit code, registry shape).&lt;/p&gt;&lt;p&gt;Spec 092/US076 — configurable retention sweeper for growth tables (roadmap 167): audit_log, metrics_history and syslog already had bespoke prunes, but `scheduled_task_runs`, `migration_history`, `drift_events` and `site_recovery_events` grew unboundedly for the install lifetime. New `RETENTION_TABLES` registry + `retention_sweep()` in `background/__init__.py` prunes each table on its timestamp column past a per-table, admin-configurable window via server_settings (`retention_task_runs_days` 30d default, `retention_migration_history_days`/`retention_drift_events_days`/`retention_sr_events_days` 90d; clamped 1–3650 like the syslog convention). Deletes run through `run_heavy_write` off the event hub — same pattern as cleanup_audit_log — and the sweep is wired as `_periodic_retention_sweep()` on the alert loop&apos;s daily piggyback slot next to `_periodic_audit_cleanup()`. Swept counts are computed by indexed COUNT diffs (rowcount isn&apos;t exposed through run_heavy_write). Covered by `tests/test_retention_sweeper.py` (5 tests: 7d window prunes old/keeps recent, configured per-table retention, idempotent re-sweep, registry shape, alert-loop wiring).&lt;/p&gt;&lt;p&gt;Spec 092/US075 — soft-delete with audit on core entities (roadmap 166): clusters, users and scheduled actions previously hard-deleted with no recovery path. `_ensure_soft_delete_columns()` converges a `deleted_at` column onto all three tables; generic helpers in `models/__init__.py` (`SOFT_DELETE_TABLES` allowlist — the only place identifiers enter SQL) drive `soft_delete_row`/`restore_row`/`list_deleted`. `delete_cluster`/`delete_user` now mark rows; `get_all_clusters`/`get_cluster`/`get_all_users`/`get_user` and `load_schedules` filter `deleted_at IS NULL` so trashed entities are hidden everywhere (deleted users can&apos;t log in — `get_user` powers session validation). The cluster DELETE endpoint stopped revoking the PVE-side API token and cascading vm_acls/affinity_rules/cluster_alerts — a restored cluster must come back whole — and now soft-deletes the cluster&apos;s scheduled actions instead of the ISS-081 hard prune (same anti-spam, recoverable). `save_schedules`&apos; rewrite-all only clears active rows so trash survives saves. New endpoints: `GET /api/trash` (admin — clusters/users/schedules newest-first), `POST /api/clusters/&lt;id&gt;/restore` (recreates + starts the manager like boot), `POST /api/users/&lt;username&gt;/restore`, `POST /api/schedules/&lt;id&gt;/restore` — each permission-checked and audited (`*.restored`). `purge_soft_deleted(days=30)` runs at boot and hard-deletes rows past retention, cascading cluster dependents first. __Also fixed a real bug the story surfaced__: the SQLite→PG translator emitted `GENERATED ALWAYS AS IDENTITY` for `INTEGER PRIMARY KEY`, but SQLite rowid semantics accept explicit ids — every app-assigned-id INSERT (save_schedules&apos; rewrite-all!) silently failed on Postgres. Translation now emits `GENERATED BY DEFAULT` and `_ensure_identity_by_default()` converges existing columns. Covered by `tests/test_soft_delete.py` (9 tests: columns, hide/restore per entity, related-data preservation, rewrite-save preserving trash AND persisting, retention purge, delete endpoint, trash listing, all three restore endpoints).&lt;/p&gt;&lt;p&gt;Spec 092/US074 — automatic DB backup before schema changes (roadmap 165): a bad schema change previously had no safety net before convergence touched the live database. `_init_db` now calls `_maybe_backup_before_migrate()` after the drift check and before any CREATE/ALTER: when the stored `server_settings.schema_fingerprint` (US073) disagrees with the live schema — i.e. real pending drift — and the database is non-empty, it runs `pg_dump -Fc` to `config/backups/pre-migrate-YYYYMMDD-HHMMSS.dump` and verifies the file exists and is non-empty; a missing/failed/empty dump logs a warning and startup continues (backup must never block boot). Fresh/empty databases skip the backup entirely, and a matching fingerprint takes the cheap no-op path. Two fingerprint-correctness fixes rode along: the fingerprint is now recorded at the END of `__init__` after every `_ensure_*_tables()` step (recording inside `_init_db` missed the post-init tables and reported phantom drift each boot), and per-table signatures now sort column entries — an ALTER-reconverged column appends at the end while the original CREATE had it mid-table, so ordinal order is not real drift. Covered by `tests/test_premigrate_backup.py` (5 tests: drift triggers timestamped non-empty dump, clean schema skips, empty DB skips, pg_dump failure warns-but-boots, naming/dir convention).&lt;/p&gt;&lt;p&gt;Spec 092/US073 — schema integrity checksums (roadmap 164): with idempotent convergence instead of versioned migrations, hand-edited or half-applied schema changes had no detector. `_init_db` now fingerprints every table&apos;s column metadata (sha256 over name:type:notnull:pk from `PRAGMA table_info`) into `server_settings.schema_fingerprint` after converging, and on the next boot runs `_verify_schema_drift()` BEFORE any CREATE/ALTER — drifted tables warn with stored-vs-live checksums, dropped tables warn, out-of-band additions log at info (indistinguishable from legit new tables until convergence). Verified on real PG: dropped `users.language` and dropped `status_incidents` are each flagged by name, clean boots stay silent, and the fingerprint refreshes after reconvergence. Covered by `tests/test_schema_drift.py` (5 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US072 — ephemeral demo environment for CI e2e (roadmap 163): the e2e suite only ran against a local static server. New `.github/workflows/e2e.yml` builds `proxmoxvex:ci-e2e`, boots the disposable demo stack (`docker-compose.yml` + `demo-override` — which sets `PROXMOXVEX_DEMO_MODE`, seeds fixtures and the synthetic cluster — + `smoke-override` for CI-isolated names and port 5010), polls `/api/health` for up to 5 minutes with container logs on timeout, installs Python deps + Playwright, and runs `pytest tests/e2e` with `PROXMOXVEX_E2E_URL` pointing at the real instance — the suite&apos;s existing external-server seam. Container logs dump on failure; the compose project always tears down (`down -v`). Structural coverage in `tests/test_e2e_workflow.py` (6 tests: compose-up, health wait, E2E_URL wiring, teardown + failure logs, demo mode).&lt;/p&gt;&lt;p&gt;Spec 092/US071 — dependency vulnerability audit in CI (roadmap 162): vulnerable pinned deps could accumulate between releases. New `.github/workflows/security.yml` runs on every PR and weekly (Mondays 06:00 UTC) plus workflow_dispatch: a `pip-audit` job audits both `requirements.txt` and `requirements-dev.txt` under `--strict`, and an `npm-audit` job runs `npm audit --audit-level=high` against `web/Dev/.build` (the only npm surface — the babel build toolchain, which has a lockfile so results are reproducible). Actions pinned to SHAs matching test.yml convention. Verified locally: `pip-audit -r requirements.txt` reports no known vulnerabilities. Structural coverage in `tests/test_security_workflow.py` (6 tests: PR+weekly triggers, both dep surfaces, severity floor, pinned actions).&lt;/p&gt;&lt;p&gt;Spec 092/US070 — pre-commit hooks for staged lint/format (roadmap 161): lint errors previously survived to CI. New `.pre-commit-config.yaml` runs ruff check --fix, ruff format, and mypy on staged Python files as local hooks pinned to the project venv binaries — byte-identical versions to CI, unlike remote ruff-pre-commit which would download its own — plus the pinned `pre-commit-hooks` hygiene set (trailing-whitespace, end-of-file-fixer, check-added-large-files, check-yaml, check-json). Verified end-to-end: a staged file with an unfixable F821 fails the hook with the error shown. Structural coverage in `tests/test_precommit_gate.py` (7 tests: config parses, ruff+format+mypy hooks, hygiene trio, venv-pinned entries, pinned remote revs). Coverage artifacts (.coverage, coverage.xml, htmlcov/) now gitignored and .coverage untracked.&lt;/p&gt;&lt;p&gt;Spec 092/US069 — visual snapshot tests for key screens (roadmap 160): unintended visual regressions were only caught by eye. New `tests/e2e/test_visual.py` snapshots the four key screens — login, dashboard, VM list, settings — against deterministic mock data, waiting on `document.fonts.ready` for stability. Python Playwright lacks `toHaveScreenshot`, so new `tests/e2e/visual_diff.py` provides a PIL pixel comparator (per-pixel channel tolerance for anti-alias jitter, 1% changed-pixel budget, size mismatch = 100% diff). Baselines live in `tests/e2e/screenshots/baseline/` (tracked); `tests/e2e/screenshots/current/` holds the transient captures (gitignored); `VISUAL_UPDATE=1` regenerates baselines. Verified end-to-end: baselines captured, rerun diffs clean, and a corrupted baseline failed naming the screen at 6.25% diff — the independent test. 4 visual tests + 5 structural tests pass.&lt;/p&gt;&lt;p&gt;Spec 092/US068 — property-based API fuzz tests (roadmap 159): adversarial inputs previously had no systematic coverage. New `tests/fuzz/test_api_fuzz.py` uses hypothesis (new dev dep `&gt;=6.140,&lt;7`) to throw malformed bodies at `/api/users` create, the vzdump backup endpoint, pagination params, and arbitrary non-dict JSON — asserting the two spec invariants: never a 5xx, and every 4xx carries a structured error body. The fuzzer immediately earned its keep: `mode=None` surfaced a crash in `vms._pve_error`, which passed `response.status_code` straight into the HTTP status — a pathological (non-int) upstream status made werkzeug&apos;s `make_response` raise AFTER the view returned, bypassing the route&apos;s try/except and the US040 error envelope. `_pve_error` now coerces the status to int with a 502 fallback. 5 fuzz tests pass (80 examples each); validation/etag/idempotency regressions green.&lt;/p&gt;&lt;p&gt;Spec 092/US067 — k6 load suite for hot API paths (roadmap 158): perf regressions previously had no repeatable measurement. New `tests/load/dashboard.js` exercises the hottest authenticated reads (auth/check, clusters list, per-cluster vms list, metrics scrape) plus the public status endpoint under a 20 VU/2m default profile, with k6 thresholds enforcing p(95)&lt;500ms and &lt;1% errors; `K6_SMOKE=1` shrinks to 2 VU/10s for CI. New `scripts/load-tests.sh` boots a demo-mode instance (`PROXMOXVEX_DEMO_MODE`, synthetic cluster, demo/demo creds) when `VEX_URL` isn&apos;t supplied, waits for `/api/status/public`, runs the suite, and cleans up — or targets any `VEX_URL` directly. `scripts/ui-tests.sh` gains an opt-in `RUN_LOAD_TESTS=1` step that runs the smoke profile after e2e when k6 is installed. Structural coverage in `tests/test_load_gate.py` (7 tests: budgets declared, hot paths covered, env base URL, auth, runner wiring, ui-tests gate). k6 script verified to parse as ESM; live load runs need a k6 binary.&lt;/p&gt;&lt;p&gt;Spec 092/US066 — migration round-trip tests (roadmap 157): the codebase has no Alembic — schema management is idempotent convergence in `_init_db` (CREATE IF NOT EXISTS + PRAGMA-guarded ALTER ADD COLUMN), and the SQLite backend was removed in favour of `PGConnection` translating SQLite SQL to PostgreSQL on the fly. New `tests/test_migrations.py` exercises the real round-trip on a throwaway PG database: fresh convergence creates all core tables; re-init is idempotent and preserves seeded rows; dropping a migrated column (`users.language`) and a newer table (`status_incidents`) then re-converging restores both while seeded data survives — the upgrade→seed→downgrade→upgrade cycle the spec asks for. A dedicated tripwire test asserts `PRAGMA table_info` returns real columns through the PG translator, since every guarded ALTER silently no-ops if that translation breaks — this codebase&apos;s actual &apos;bad migration&apos; failure mode. 4 tests pass.&lt;/p&gt;&lt;p&gt;Spec 092/US065 — API contract tests against the OpenAPI spec (roadmap 156): the generated spec carried routes/methods but no response-body schemas, so shape regressions were invisible. New `RESPONSE_SCHEMAS` registry in `api/openapi_doc.py` pins declared JSON schemas for key operations (`/api/auth/check`, `/api/status/public`, `/api/clusters/{id}/vms`, `/api/users`) and `build_openapi` merges them as `content.application/json.schema`. New `tests/test_openapi_contract.py` fetches schemas from the *served* spec (registry and tests can&apos;t drift) and validates live responses with a dependency-free recursive checker — a wrong shape fails naming the endpoint and violated path (e.g. `$.vms[0].vmid`), per the independent test. 6 contract tests pass; openapi/v2/deprecation/envelope regressions green.&lt;/p&gt;&lt;p&gt;Spec 092/US064 — fast Playwright smoke gate on builds (roadmap 155): the full e2e suite is thorough but slow, so a broken UI was only caught after a long run. New `tests/e2e/test_smoke.py` exercises the four critical render paths — login→dashboard, dashboard overview, VM list (web01/db01), and the Plugins tab (injected card payload) — reusing the shared mock/navigation helpers. `scripts/ui-tests.sh` now runs the smoke file before the full suite under `set -e`, so a broken UI fails the build playtest in seconds. Verified live: 4/4 pass against a real Chromium instance. Structural coverage in `tests/test_smoke_gate.py` (5 tests: file exists, all four flows present, gate runs before the full suite, fail-fast, &gt;=4 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US063 — changed-code coverage gate in CI (roadmap 154): untested logic could merge silently. The pytest job now runs `--cov=ProxmoxVEx --cov-report=xml --cov-report=term-missing` and a new `diff-cover coverage.xml --compare-branch=origin/main --fail-under=70` step fails the build when coverage of changed lines drops under 70% — a changed-lines gate (not total coverage, which would be unachievable for the legacy codebase). Checkout uses `fetch-depth: 0` (shallow clones have no base to diff). New `diff-cover&gt;=9.7.0,&lt;11` dev dependency; `[tool.coverage.run]` omit rules (tests/migrations/node_modules/pycache) and `[tool.coverage.report]` exclude patterns in pyproject; coverage artifacts gitignored. Verified end-to-end: gate correctly reports uncovered changed lines. Covered by `tests/test_coverage_gate.py` (5 tests: cov flags, diff-cover step, fetch-depth, dev dep, omit rules).&lt;/p&gt;&lt;p&gt;Spec 092/US062 — generated plugin reference docs (roadmap 153): plugin docs drifted from code because they were hand-maintained. New `scripts/gen_plugin_docs.py` scans `plugins/*/manifest.json` and writes `docs/plugins/&lt;id&gt;.md` — name/version/description, author/category/license-tier/min-version/trusted metadata, declared permission scopes, `config_schema` fields (type/required/secret/enum/default), and subscribed events. The generated block is wrapped in `&lt;!-- GENERATED:START/END --&gt;` markers so human-authored content elsewhere in the file survives regeneration. New `read_plugin_manifest()` helper in `models/plugins.py` (now also used by `get_plugin_permissions`). Ran once: 77 plugin docs generated. Covered by `tests/test_gen_plugin_docs.py` (7 tests: sections, iteration, doc-per-plugin, marker preservation on regen, manifest helper).&lt;/p&gt;&lt;p&gt;Spec 092/US061 — plugin integration test harness (roadmap 152): plugin authors previously needed a full deployment to CI their plugin. New `tests/plugins/harness.py` provides `PluginSandbox` — loads `&lt;dir&gt;/manifest.json` through the same `validate_manifest` contract the loader enforces (invalid manifests raise with field-level errors), imports the plugin&apos;s `__init__.py`, runs `register(app)` through the real `register_plugin_route` path, captures the registered routes for direct invocation (`sandbox.call(&apos;status&apos;)` runs handlers inside a synthetic request context when a Flask app is supplied), and restores the global `_plugin_routes`/`_loaded_plugins` tables so sandboxes never leak into the app — plus `MockBridge`, a fixture-driven stand-in for host data access (`live:&lt;resource&gt;` fixtures, `cluster_managers` install/uninstall, call recording, AssertionError on unmocked calls). Covered by `tests/test_plugin_harness.py` (8 tests: fixtures + call recording, unmocked-call failure, manifest validation, real-path route registration, no global leak, request-context invocation, manager mocking).&lt;/p&gt;&lt;p&gt;Spec 092/US060 — schema-driven plugin config forms (roadmap 151): the config editor previously guessed field types from current values, so empty keys rendered wrong and there was no validation. Plugins may now declare `config_schema` in manifest.json (`{field: {type, label?, description?, required?, secret?, enum?, minimum?, maximum?, default?}}`). `GET /api/plugins/&lt;id&gt;/config` returns `schema` alongside the raw config; `PluginConfigForm` accepts a `schema` prop and renders the new `SchemaConfigForm` (boolean→checkbox, number→min/max inputs, enum→select, secret→password with autocomplete=off — secret auto-detected via the existing credential-key pattern when not declared), falling back to value-introspection when no schema is declared. `validatePluginConfig` enforces required fields, types, ranges, and enum membership with inline per-field errors and a visible `*` required marker. Covered by `tests/frontend/test_plugin_config_schema.py` (8 tests: component, prop, renderers, secret masking, validator, required marker, backend schema exposure, settings-modal wiring).&lt;/p&gt;&lt;p&gt;Spec 092/US059 — per-plugin performance metrics (roadmap 150): there was no way to tell which plugin was generating bridge traffic or latency. `plugin_proxy` and `plugin_live` now record every dispatch through `record_plugin_call(plugin_id, status, seconds)` in the metrics exporter — `plugin_bridge_calls_total{plugin,status}` counters (ok/error) plus a `plugin_bridge_seconds` latency histogram per plugin (same buckets as `http_request_seconds`), emitted by `/api/metrics`. Recording is failure-safe (metrics can never break the dispatch path). Covered by `tests/test_plugin_metrics.py` (5 tests: counter/histogram accounting, proxy ok-path, /metrics exposition shape, error status, live-bridge recording).&lt;/p&gt;&lt;p&gt;Spec 092/US058 — host→plugin event bus (roadmap 149): plugins previously had to poll the live bridge to react to host changes. New `static/js/plugin-events.js` client (auto-injected into every plugin UI alongside plugin-theme.js) exposes `ProxmoxVExEvents.subscribe(events)` / `onEvent(fn)` — subscribe posts a `vex-event-subscribe` message to the host, onEvent dispatches incoming `vex-event` frames to registered handlers (one bad handler can&apos;t break the bus). Host side (`dashboard.js`): `_pluginEventSubs` maps each subscriber window to its declared event types, `ProxmoxVExEmitHostEvent(evt)` forwards `{type:&apos;vex-event&apos;, event}` only to frames that subscribed to that type (or `*`) and drops dead subscriptions on postMessage failure, and the SSE `handleSseEvent` forwards every real server event (tasks, alerts, node/vm updates — heartbeat excluded) into the bus. Covered by `tests/frontend/test_plugin_event_bus.py` (6 tests: client asset, UI injection, subscription tracking, emit registration + frame shape, SSE forwarding, per-type scoping).&lt;/p&gt;&lt;p&gt;Spec 092/US057 — retry with exponential backoff for native integrations (roadmap 148): transient failures on remote endpoints (TrueNAS, PBS, ONTAP, firewalls) surfaced momentary blips as user-visible errors. New shared `with_retry(fn, attempts=4, base=0.5, cap=8.0)` in `ProxmoxVEx/native/__init__.py` retries `TransientError` (or a caller-supplied exception tuple) with jittered exponential backoff (delay = min(cap, base·2^i) + 0–0.3s jitter) and a structured warning per attempt; non-transient exceptions propagate immediately and the last error re-raises after exhaustion. Re-exported from `ProxmoxVEx/integrations/__init__.py` so non-native integration code shares the policy. Wired into `OntapClient._get` — ONTAP GETs now retry on `requests` network errors and transient statuses (429/500/502/503/504); writes (`_post`/`_patch`/`_delete`) deliberately do not retry, matching the opnsense/pfsense convention that only idempotent reads retry. `sleep` is injectable (`client._retry_sleep`) for tests. Covered by `tests/test_native_retry.py` (10 tests: first-try, retry-then-succeed, backoff growth, exhaustion, non-transient passthrough, custom transient tuple, cap bound, ONTAP 5xx/network retry, 4xx no-retry, POST no-retry).&lt;/p&gt;&lt;p&gt;Spec 092/US056 — plugin update channel with version pinning and rollback (roadmap 147): updates previously installed over the live plugin directory with no way back — a corrupt artifact left the plugin broken. `PluginDeliveryService` now snapshots the installed plugin directory to `plugins/.rollback/&lt;id&gt;` before every update (`snapshot_for_rollback`), auto-restores it when `install_update` fails (`restore_rollback`, error annotated &quot;rolled back to previous version&quot;), and exposes `has_rollback`. New admin endpoints: `PUT/DELETE/GET /api/plugins/&lt;id&gt;/pin` stores a pin (`version` or `*`) in the `plugin_pins` kv bucket with audit records; `check_paid_plugin_updates` reports `pinned`/`pinned_version` on pinned entries so the UI can still show the update badge; `apply_paid_plugin_updates` refuses pinned plugins; `POST /api/plugins/&lt;id&gt;/rollback` restores the snapshot on demand and reloads the plugin if it was running (404 when no snapshot exists). Covered by `tests/test_plugin_update_channel.py` (8 tests: snapshot/restore primitives, pin lifecycle, pinned-skip, pinned flag in check, auto-restore on failed install, rollback endpoint, 404 without snapshot).&lt;/p&gt;&lt;p&gt;Spec 092/US055 — plugin health checks with auto-disable (roadmap 146): one failing plugin could previously degrade the whole app indefinitely. Plugins that register a `health` route via `register_plugin_route()` are now probed every 5 minutes by a daemon thread (`start_plugin_health_thread`, started in `app.py` next to the update poller, 60s startup delay). `_probe_plugin_health` invokes the handler inside a synthetic request context and treats exceptions or `{status: error}`-style responses as failures; `_health_fails` tracks consecutive failures per plugin. At 3 consecutive failures the plugin is disabled via `_set_plugin_state` (visible as disabled in the plugin list with an explanatory error), an audit `plugin.auto_disabled` record is written, and probing stops — an admin can re-enable after fixing it. Success resets the counter; plugins without a health route or already disabled are skipped. Covered by `tests/test_plugin_health.py` (5 tests: threshold auto-disable + audit, counter reset on success, exception handling, no-route skip, disabled-plugin skip).&lt;/p&gt;&lt;p&gt;Spec 092/US054 — per-plugin permission scopes on the live-data bridge (roadmap 145): `GET /api/plugins/&lt;id&gt;/api/live/&lt;resource&gt;` previously served any cluster resource to any loaded, licensed plugin. Now each resource maps to a required host scope (`_LIVE_RESOURCE_SCOPES`: clusters→`clusters:read`, nodes→`nodes:read`, vms/lxc/templates→`vms:read`, storage→`storage:read`, pools→`pools:read`, tasks→`tasks:read`) and the request is refused with `403 {&quot;error&quot;: &quot;plugin_scope_denied&quot;, &quot;scope&quot;: &lt;needed&gt;}` unless the plugin&apos;s manifest `permissions` declares the scope (or the explicit `*` grant-all). New `get_plugin_permissions()` in `models/plugins.py` reads declared scopes per-request so manifest edits take effect without a reload; manifests with no `permissions` array get deny-by-default. Every denial is audit-logged (`plugin.scope_denied`) and warned. Covered by `tests/test_plugin_scopes.py` (6 tests: allowed scope serves, undeclared/missing/wildcard handling, audit record, manifest reader).&lt;/p&gt;&lt;p&gt;Spec 092/US053 — strict plugin manifest validation at load (roadmap 144): `_discover_plugins` previously only caught JSON-parse errors — a well-formed manifest missing required fields loaded silently with partial metadata. New `validate_manifest()` in `ProxmoxVEx/plugins/__init__.py` enforces the manifest contract and returns field-level errors: `id`/`name`/`version` required non-empty strings, `id` must match `^[a-z0-9_-]+$` (underscores allowed — shipped plugins client_portal/hello_world/status_page use them), `version` must be semantic MAJOR.MINOR.PATCH, `permissions` an array of strings, and declared optional fields type-checked (author/description/category/frontend_route/min versions/entrypoint as strings, has_frontend/enabled/trusted as booleans). Discovery now fails closed — invalid manifests surface as `Invalid manifest: &lt;field&gt;: &lt;reason&gt;` entries and are never registered as loadable plugins. Hand-rolled validator (no jsonschema dependency). Covered by `tests/test_plugin_manifest_schema.py` (7 tests: valid pass, per-field missing/pattern/type errors, underscore ids, discovery fail-closed, unparseable JSON still reported).&lt;/p&gt;&lt;p&gt;Spec 092/US052 — full keyboard navigation in sidebar resource trees (roadmap 143): the corporate sidebar trees handled only ArrowUp/Down + Enter, and the datastore/network trees had no arrow navigation at all. New module-level `corpTreeNavKey(e)` in `dashboard.js` resolves the focused row&apos;s `closest(&apos;[role=&quot;tree&quot;]&apos;)` and owns all six movement keys — Down/Up next/previous, ArrowRight expands a collapsed parent (synthesized click on `aria-expanded=false`) or steps to the next row, ArrowLeft collapses an expanded parent or jumps to the nearest expanded ancestor row, Home/End focus first/last. Pool-tree headers publish `role=&quot;treeitem&quot;` + `aria-expanded` (real expand state), child containers `role=&quot;group&quot;`, and all four tree containers (`corp-pool-tree` + the three `corp-inline-tree` node/datastore/network trees) carry `role=&quot;tree&quot;` with `role=&quot;treeitem&quot;` rows; flat trees have no expanded rows so Right/Left degrade to next/previous. Network-tree node sub-headers became focusable (`tabIndex`, `treeitem`, `aria-expanded`, Enter toggles) — previously unreachable by keyboard. The two local `treeNavDown`/`treeNavUp` helper pairs were replaced by the shared handler. Covered by `tests/frontend/test_tree_keyboard_nav.py` (7 tests: role=tree, treeitem, aria-expanded, Right/Left/Home/End, wiring).&lt;/p&gt;&lt;p&gt;Spec 092/US051 — accessible names for icon-only buttons (roadmap 142): two layers. (1) The `a11y.js` MutationObserver labeler was REGRESSING names — it stamped `aria-label=&quot;Button&quot;` over title-only buttons even though `title` is already an accessible-name source; it now skips titled elements, prefers the title as the label value, and marks icon SVGs inside newly-labeled buttons `aria-hidden` so the name isn&apos;t polluted. (2) A source audit found 18 genuinely icon-only buttons with no name at all (close X, refresh, plus-add, more-actions) across networking/vm_modals/datacenter/create_modals/native_integrations/vm_config/settings_modal/node_modals/dashboard_panels — each got `aria-label={t(&apos;close&apos;|&apos;refresh&apos;|&apos;add&apos;|&apos;moreActions&apos;)}` (ArrayEditor gained the missing `useTranslation` destructure). Covered by `tests/frontend/test_icon_button_names.py` (5 tests: zero unlabeled icon-only buttons anywhere in src, observer prefers title, skips title-only, SVGs aria-hidden).&lt;/p&gt;&lt;p&gt;Spec 092/US050 — high-contrast theme at WCAG AAA (roadmap 141): `highContrast` (&apos;Maximum Clarity&apos;) was registered and backend-allowlisted but its `primary`/`error` `#FF3131` only reached ~5.7:1 on black — under the 7:1 AAA bar for error text and accent-on-dark. Brightened to `#FF9999`/`#FF8080`-family so every foreground color holds ≥7:1 across `dark`, `card`, AND `hover` surfaces (verified by computed WCAG luminance ratios in the test). `applyTheme` now stamps `data-theme-name` on `&lt;html&gt;` — a CSS hook for theme-scoped rules — and `app-shell.css` gains a `[data-theme-name=&quot;highContrast&quot;]` block giving `.corp-status-dot` a white outline cue (status no longer rides on hue alone) plus a 3px full-contrast focus ring. Note: the spec pointed at `theme.css` but that file is never bundled — the rules live in `app-shell.css` which build.sh emits. Covered by `tests/frontend/test_high_contrast.py` (4 tests: registry entry, computed 7:1 ratios for all foreground×background pairs, themeName hook, non-color status cue).&lt;/p&gt;&lt;p&gt;Spec 092/US049 — respect prefers-reduced-motion (roadmap 140): the reduced-motion, focus-visible, skip-link, touch-target, and prefers-contrast rules all lived in `web/src/accessibility.css` — a file build.sh never bundled, so every rule was dead CSS. build.sh now appends it to `app.bundle.css` alongside `ui-states.css` (both the dev-copy path and the Python emit block, via a loop over `(&apos;ui-states.css&apos;, &apos;accessibility.css&apos;)`), and a broken orphaned declaration at the top of the file got its missing `:focus-visible` selector back. The universal `@media (prefers-reduced-motion: reduce)` rule now actually reaches users — every animation/transition including the Tailwind `animate-fade-in`/`animate-slide-in` classes collapses to ~0ms and `scroll-behavior` flips to auto. One gap CSS can&apos;t reach: `scrollIntoView({behavior:&apos;smooth&apos;})` in `tables.js` ignores `scroll-behavior`, so the VM-highlight scroll now checks `matchMedia(&apos;(prefers-reduced-motion: reduce)&apos;)` itself. Covered by `tests/frontend/test_reduced_motion.py` (6 tests: file bundled, rule exists, universal coverage, no orphaned rule, smooth-scroll gated).&lt;/p&gt;&lt;p&gt;Spec 092/US048 — focus trapping in all modals (roadmap 139): the per-modal `useFocusTrap` hook only reached ~8 of ~69 dialogs. New global delegated trap in `a11y.js`: modal overlays are detected as `div.fixed.inset-0` containers holding focusable elements (click-away backdrops, dropdown layers, and the boot splash are skipped — none contain focusables), the topmost qualifying overlay owns a capture-phase Tab handler that wraps first↔last and pulls escaped focus back inside, and a MutationObserver syncs trap state — the opener&apos;s element is remembered on open and refocused on unmount. Covers every existing and future modal with zero per-modal edits; Esc stays with each modal&apos;s own close semantics. Also fixed a latent `getAuthHeaders` undeclared reference in `SavedFiltersMenu` (US017) that `test_ui_reference_integrity` flagged. Covered by `tests/frontend/test_focus_trap.py` (7 tests: Tab listener, overlay detection, focusable filter, first/last wrap, restore-on-close, global install).&lt;/p&gt;&lt;p&gt;Spec 092/US047 — ARIA live regions for toasts + async status (roadmap 138): toasts already self-announced via `role=&quot;status&quot;`, but failures shared the polite channel and non-toast async changes (inbox/task items landing on the 30s poll) announced nothing. `a11y.js` gains a persistent visually-hidden live region (`#a11y-live`, `aria-live`/`aria-atomic`, `sr-only`) plus `window.ProxmoxVExAnnounce(msg, {assertive})` — 300ms-throttled so completion bursts don&apos;t queue a speech backlog, clear-then-set via rAF so identical consecutive messages re-announce. `PushBellButton.refreshInbox` now tracks seen ids in a ref and announces the newest genuinely-new item per poll (the US019 task-completion/alert path) — first-load backlog stays silent, missing bridge is guarded. Error toasts escalate to `role=&quot;alert&quot;` + `aria-live=&quot;assertive&quot;` in `ui.js` — &apos;VM start failed&apos; is exactly what a screen-reader user must not miss. Covered by `tests/frontend/test_aria_live.py` (5 tests: region exists, API exported, throttle, assertive errors, inbox wire).&lt;/p&gt;&lt;p&gt;Spec 092/US046 — locale-aware date/number formatting (roadmap 137): `fmtDate`/`fmtTime`/`fmtDateOnly`/`fmtNumber` passed `undefined` to Intl — they followed the BROWSER&apos;s locale, so a Deutsch selection still rendered MM/DD/YYYY + dot decimals on an en-US browser. New `_fmtLocale()` in `constants.js` resolves `ProxmoxVExI18n.getLanguage()` per call (guarded — pre-i18n boot falls back to browser default) and the locale is folded into both formatter cache keys (`_dtfCache`, `_nfKey`) so a language switch rebuilds formatters without a reload and the perf-cached-Intl scheme stays intact. `formatBytes` in `ui.js` now renders its mantissa via `fmtNumber` — &quot;1,5 GB&quot; in German instead of the hardcoded `toFixed` dot. Covered by `tests/frontend/test_locale_formatting.py` (5 tests: app locale into both Intl constructors, locale in both cache keys, formatBytes mantissa via fmtNumber).&lt;/p&gt;&lt;p&gt;Spec 092/US045 — locale persists per user account (roadmap 136): verified the full loop was already wired end-to-end and locked it in with tests — `users.language` column (db ensure), `PUT /api/user/preferences` allowlist-validates + saves the locale, `GET /api/auth/check` returns `user.language`, `checkSession` in `contexts.js` applies the server value over localStorage/browser detection on every login (roaming to fresh browsers), and `changeLanguage` pushes authenticated switches back to the server. Verified per-user isolation and 400 on unsupported locales. Covered by `tests/test_locale_roaming.py` (6 tests: save→fresh-session round trip, preferences GET, invalid→400, cross-user isolation, frontend applies saved locale, frontend pushes locale).&lt;/p&gt;&lt;p&gt;Spec 092/US044 — RTL layout corrections for Arabic (roadmap 135): switching to العربية changed strings but not direction — the whole shell stayed LTR. `i18n_bridge.js` gains `RTL_LOCALES` (ar/he/fa) + `window.applyLocaleDirection(locale)`: sets `document.documentElement.dir` and a `.rtl` class hook, called from the existing language-sync effect in `contexts.js` (guarded so a missing bridge can&apos;t break boot). Flex/grid layouts follow the inline direction automatically once `dir=&quot;rtl&quot;` lands, so most of the shell mirrors for free; a new `.rtl` block in `app-shell.css` covers what doesn&apos;t — directional icons opt into mirroring via `data-rtl-mirror` (marked on ChevronRight/ChevronLeft/ArrowRight/ArrowLeft in icons.js — breadcrumb chevrons, expanders, back buttons now point along the flow), physical `text-align: left` gets a start-aware override on tables/`.text-left`, `.ml-auto` push-right patterns mirror, and `[dir=&quot;auto&quot;]`/`bdi` get `unicode-bidi: isolate` so mixed-direction runs (a VM name inside Arabic chrome) don&apos;t scramble. Scope deliberately narrow — broad physical-property rewrites would risk the corporate layout. Covered by `tests/frontend/test_rtl_layout.py` (7 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US043 — strict i18n completeness gate in CI (roadmap 134): `validate.py --strict` existed but wasn&apos;t wired into CI, truncated missing-key lists to 5 entries, and had no escape hatch for intentionally untranslated keys. Strict mode now names EVERY missing key as `[ns/lang] MISSING ns:key` (a truncated preview could hide violations), and a new `ALLOWED_MISSING` set (`{&quot;ns:key&quot;}` entries, matched on the bare namespace name so it works for native/plugin namespaces too) suppresses keys shipped en-only on purpose. New `i18n completeness` step in `.github/workflows/test.yml`&apos;s lint job runs `python3 web/i18n/validate.py --strict` — stdlib-only, no deps — so a half-translated locale now fails the build before it reaches users. Covered by `tests/test_i18n_gate.py` (5 tests: strict names the missing key, permissive still warns-only, allowlist suppresses, &gt;5 missing keys all named, CI workflow contains the gate).&lt;/p&gt;&lt;p&gt;Spec 092/US042 — per-route Deprecation/Sunset/successor headers (roadmap 133): US034&apos;s blanket rule pointed every v1 route at its mechanical `/api/v2` mirror — but some deprecations have a real successor, not a version bump. New `DEPRECATED_ROUTES` registry in `api/__init__.py` (route-rule → `(sunset, successor)`), consulted first in the `_deprecate_v1` after_request hook: registry routes emit `Deprecation: true`, their own `Sunset`, and a `Link` to the named successor, plus a structured `deprecated route used` INFO log on `ProxmoxVEx.deprecated` (with request_id) so ops can measure remaining traffic before removal. First entry: `/api/audit` → `/api/audit/search` (the legacy flat list is superseded by the filtered/faceted search). Blanket v1→v2 headers unchanged for every other route; `/api/v2` requests stay exempt. Covered by `tests/test_deprecation_headers.py` (5 tests: all three headers, registry successor overrides the blanket mirror, non-registry route keeps v2 successor, v2 exempt, structured usage log).&lt;/p&gt;&lt;p&gt;Spec 092/US041 — schema-based request validation (roadmap 132): malformed request bodies reached business logic unchecked. New `validate(schema, source=&quot;json&quot;|&quot;args&quot;, allow_unknown=True)` decorator in `api/helpers.py` — dict-schema consistent with `utils/validation.validate_body` ({required, type, default, enum, max_length, min, max}) — validates BEFORE the handler runs; failures return the US040 `{error: {code: &quot;validation_error&quot;, message, details, request_id}}` envelope with per-field reasons, 415 on missing JSON body, 422 on non-object bodies. `source=&quot;args&quot;` coerces int/float/bool query params before type-checking (every arg arrives a string). On success, coerced + defaulted values land on `request.validated`. No new dependency — pydantic isn&apos;t vendored and the dict-schema convention was already established. First call site: `POST /api/clusters/&lt;id&gt;/vms/&lt;node&gt;/&lt;type&gt;/&lt;vmid&gt;/backups/create` — `mode`/`compress` are now enum-checked against vzdump&apos;s accepted values (bad values previously went upstream to 400 anyway), `storage`/`notes` length-bounded, defaults applied via `request.validated`. Covered by `tests/test_request_validation.py` (5 tests: invalid type→400 with field detail + handler never runs, enum violation→400, valid body reaches handler, defaults applied, args-source coercion + failure envelope).&lt;/p&gt;&lt;p&gt;Spec 092/US040 — standard API error envelope (roadmap 131): /api/ errors returned dozens of ad-hoc flat shapes (`{&quot;error&quot;: &quot;…&quot;}` strings) so clients couldn&apos;t handle failures uniformly. New `api_error(status, code, message, details=None, extra=None)` in `api/helpers.py` builds `{error: {code, message, details?, request_id}}` — `details` only when provided, `request_id` links to the `X-Request-ID` header for support correlation, `extra` merges top-level keys for legacy fields. All framework error handlers in `app.py` (400/403/404/405/415/429/500 + the Exception catch-all) now emit the envelope on /api/ paths — including abort()ed HTTPExceptions not covered by a specific handler — while browser paths keep the friendly HTML pages. The auth rejection paths converted too: the blueprint guard&apos;s 401 and require_auth&apos;s 401/403/503 returns now envelope as `auth_required`/`account_deleted`/`account_disabled`/`missing_scope`/`missing_permission`/`insufficient_permissions`/`lookup_failed`, with the legacy top-level `&quot;code&quot;` preserved via `extra` so existing consumers keep working. `GET /api/tasks/&lt;id&gt;`&apos;s route-level 404 converted as the first route-produced call site. Covered by `tests/test_error_envelope.py` (8 tests: 401/403/404/405/500 envelopes, code values, request_id↔header parity, non-API paths stay HTML).&lt;/p&gt;&lt;p&gt;Spec 092/US039 — generic async task queue + `/api/tasks` (roadmap 130): long operations ran on ad-hoc `threading.Thread`s with bespoke, per-subsystem tracking — no common way to poll progress. New `QueuedTask` registry in `models/tasks.py` (layer-0, no ProxmoxVEx imports): `queued → running → succeeded|failed` lifecycle with `set_progress()`, `result`/`error`, timestamps, and a 500-entry FIFO-bounded `TASKS` dict. `ProxmoxVEx.background.enqueue(kind, payload, owner, fn)` registers the task and submits `fn(task)` to a shared 4-worker `ThreadPoolExecutor` — fn&apos;s return value becomes `task.result`, a raise marks it failed and logs. New endpoints in `api/converter.py` (it owns the task surface): `GET /api/tasks` (admins see all, others own-only) and `GET /api/tasks/&lt;id&gt;` (foreign ids 404 — existence isn&apos;t leaked). First real producer migrated: `POST /api/ProxmoxVEx/update` now enqueues the self-update worker instead of a bare thread — the dedicated `_update_jobs` feed is untouched, and the 202 response gains `task_id` for polling. Covered by `tests/test_async_tasks.py` (6 tests: completion+result, failure→error record, API progress/completion poll, owner-scoping, list endpoint, dict shape).&lt;/p&gt;&lt;p&gt;Spec 092/US038 — outbound webhook subsystem (roadmap 129): integrators had to poll for events. New `ProxmoxVEx/services/webhooks.py` — endpoints stored in `proxmoxvex_kv` (`webhooks:endpoints`, 100 cap) as {id, url, secret, events[], enabled}; `dispatch(type, payload)` fans signed POSTs out to every subscribed endpoint on a daemon thread (`sync=True` for deterministic callers). Deliveries carry `X-VEx-Event`, `X-VEx-Timestamp`, and `X-VEx-Signature: sha256=&lt;hmac(body, secret)&gt;` (Stripe/GitHub convention), ride the shared `session_for(&quot;webhooks&quot;)` pool, 10s timeout, and retry up to 3 attempts with backoff. SSRF guard `_safe_url` rejects non-http(s), unresolvable, private/loopback/link-local/multicast targets at registration AND send time (DNS can move); `PROXMOXVEX_WEBHOOK_ALLOW_PRIVATE=1` is the documented dev escape hatch. Admin CRUD in `api/settings.py`: `GET /api/webhooks` (secrets masked — served only once at creation), `POST` (201, validated url ≤2000 chars + ≤20 event names ≤80 chars, SSRF-checked), `DELETE /&lt;id&gt;`; both writes audited. Producers wired: `task.completed` from the bulk-action worker (job id/action/status/counts/owner) and `alert.fired` from `_alert_handler` (name/message/severity/cluster) — both guarded so a webhook failure can never break alerts or jobs. Covered by `tests/test_webhooks.py` (5 tests: register→signed delivery with verified HMAC, unsubscribed silence, SSRF rejections, retry-until-success, masked-secret list + delete).&lt;/p&gt;&lt;p&gt;Spec 092/US037 — Idempotency-Key support on mutating endpoints (roadmap 128): a retried POST after a timeout could double-create resources. New `@idempotent` decorator in `api/helpers.py` — requests carrying an `Idempotency-Key` header (≤128 chars) record the first response in `proxmoxvex_kv` (`idempotency:&lt;username&gt;`, scoped per user so keys can&apos;t collide or replay across accounts, FIFO-bounded at 200 keys/user); a retry with the same key + same request body replays the recorded response verbatim marked `Idempotent-Replay: true`, while the same key with a different body — including a different method/path, both folded into the request hash — returns `409 idempotency_key_reuse`. Keyless requests are untouched, and the decorator is fail-open at both lookup and record time (a broken kv store logs and runs the handler — idempotency must never break requests). Applied innermost on `POST /api/users` so auth and body validation still run before replay. Covered by `tests/test_idempotency.py` (5 tests: replay returns first result exactly once, reuse→409, keyless normal errors, per-user key isolation, decorator wraps).&lt;/p&gt;&lt;p&gt;Spec 092/US036 — ETag/304 conditional GET (roadmap 127): unchanged resources cost a cheap 304 instead of a full re-transfer. New `etag_json(data, etag_of=None)` / `_compute_etag` in `api/helpers.py` stamp a sha256 ETag over the canonical JSON serialization (`sort_keys=True` — key-order-independent so equivalent payloads hash identically), honour `If-None-Match` with `304` + empty body, and mark responses `Cache-Control: private, must-revalidate`. Wired into `/api/users` and `/api/clusters/&lt;id&gt;/vms` (legacy + US035 envelope paths); the vms route hashes via `etag_of=vms` so the `from_cache` transport flag can&apos;t mint different ETags for identical resources. Covered by `tests/test_etag_304.py` (6 tests: header present, If-None-Match→304+empty, changed resource→new ETag, cluster vms 304 across cache paths, envelope path, deterministic hashing).&lt;/p&gt;&lt;p&gt;Spec 092/US035 — standard pagination envelope for list endpoints (roadmap 126): every list endpoint had its own shape (`{&quot;vms&quot;:…}`, bare arrays) so clients guessed per-endpoint. New `paginated_envelope(items)` / `pagination_requested()` helpers in `api/helpers.py` — passing `?page=` or `?per_page=` opts into the identical `{items, total, page, pages}` envelope; `page` floors at 1, `per_page` clamps to [1, 500], malformed values fall back to defaults via `sanitize_int` so a bad param can never 500. Applied to `/api/clusters/&lt;id&gt;/vms` (both cache-hit and fresh paths), `/api/users`, and `/api/audit` — without the params each keeps its legacy shape (backwards compatible; the spec&apos;s every-endpoint rollout proceeds opt-in so existing clients don&apos;t break). Covered by `tests/test_pagination_envelope.py` (7 tests: per-endpoint envelopes, identical key set across all three, per_page bound, legacy shape preserved, page floor).&lt;/p&gt;&lt;p&gt;Spec 092/US034 — `/api/v2` versioned prefix via WSGI rewrite (roadmap 125): the spec sketch&apos;s dual-blueprint-mount can&apos;t work here — routes declare absolute `/api/` paths, so `url_prefix` remounting would produce `/api/v2/api/…`. Instead a `_V2PathRewrite` WSGI middleware strips `/v2` from `PATH_INFO` before Flask routes it, so every existing endpoint — public and authenticated — is reachable under `/api/v2` during transition with zero route changes. The original path is stashed in the environ; an `after_request` hook marks v1 `/api/` responses `Deprecation: true` + `Sunset: Sat, 01 Jan 2028` + a `Link` header pointing at the `/api/v2` successor (v2 requests exempt). Covered by `tests/test_api_v2.py` (6 tests: v2 public route, v2 authed route, v1 still resolves, v1 deprecation headers, v2 exempt, v2 openapi).&lt;/p&gt;&lt;p&gt;Spec 092/US033 — generated OpenAPI 3 document (roadmap 124): the spec sketch&apos;s decorator registry would have meant annotating hundreds of routes across ~40 blueprints — instead `GET /api/openapi.json` generates the document from Flask&apos;s URL map at request time, so it can never drift from what&apos;s actually registered. Every `/api/` rule contributes its methods, `&lt;int:vmid&gt;`-style converters become `{vmid}` path parameters (declared `in: path, required`, typed by converter), and the view&apos;s docstring becomes the operation summary. New blueprint `api/openapi_doc.py`; authenticated like every other /api/ route since the document reveals the full API surface. Covered by `tests/test_openapi_spec.py` (6 tests: 401 anon, document shape, known routes, per-operation responses, declared path params, no raw Flask converters).&lt;/p&gt;&lt;p&gt;Spec 092/US032 — trace context propagated to upstream cluster calls (roadmap 123): a slow user-facing request couldn&apos;t be attributed to the upstream hops it caused. The manager session wrapper (same seam US025 instrumented) now stamps the inbound `request_id` contextvar onto every outgoing call as an `X-Request-ID` header and logs one line per hop — `upstream &lt;method&gt; &lt;path-tail&gt; -&gt; &lt;status&gt; &lt;ms&gt;` with `request_id` as a structured extra on the `ProxmoxVEx.upstream` logger, WARNING above `PROXMOXVEX_UPSTREAM_SLOW_MS` (default 2000) so slow hops stand out without drowning normal traffic in WARN noise. US025&apos;s `upstream_calls_total` recording is preserved; both instrumentations are failure-transparent. Covered by `tests/test_upstream_trace.py` (5 tests: header propagation, duration log, request_id extra, slow→WARNING, metrics preserved).&lt;/p&gt;&lt;p&gt;Spec 092/US031 — public status feed for external status pages (roadmap 122): the optional `status_page` plugin serves a keyed feed, but a core public-safe endpoint didn&apos;t exist. New `GET /api/status/public` (on the healthz blueprint, in `_public_api_prefixes`) reuses the US026 component checks — cached 30s since status pages poll — reduced to `{&quot;name&quot;,&quot;status&quot;:&quot;up&quot;|&quot;down&quot;}` pairs, plus `status_incidents` from the last 24h sanitized to title/status/severity/message(≤500)/started_at/resolved_at (no ids, actors, params, or check internals). Always 200 with `status:&quot;operational&quot;|&quot;degraded&quot;` and a `generated` timestamp — a status page needs the degraded truth, not an error page. Covered by `tests/test_public_status.py` (5 tests: public shape, healthz component parity, sanitized incident fields, degraded propagation, 30s check cache).&lt;/p&gt;&lt;p&gt;Spec 092/US030 — opt-in plugin error telemetry (roadmap 121): plugin iframe crashes were invisible until users complained. `_enhancePluginIframe` now attaches `error` + `unhandledrejection` listeners to the same-origin iframe&apos;s contentWindow — the listener lives in the parent realm so a broken/hostile plugin can&apos;t remove it — extracting the plugin id from the iframe src. Events queue client-side (bounded at 50) and flush to the new `POST /api/telemetry/plugin-errors` __only when the admin opted in__: `/api/config` now exposes `telemetry_opt_in` (same public channel as `demo_mode`, cached per session). The endpoint is session-authenticated, sanitizes and bounds every field (plugin ≤80, msg ≤500, stack ≤4000, version ≤40), stores events in a 200-entry capped `telemetry:plugin_errors` kv ring via new generic `get_kv`/`set_kv` db helpers, accept-and-drops (204) when not opted in, and can never 500 the reporting client. Covered by `tests/test_plugin_telemetry.py` (7 tests: 401 anon, opt-out drop, sanitized store, field bounds, ring cap, frontend hook wiring, config flag).&lt;/p&gt;&lt;p&gt;Spec 092/US029 — slow query logging at the PG cursor seam (roadmap 120): `PGCursor.execute` is the single choke point every Postgres statement flows through — it&apos;s now timed, and anything slower than `PROXMOXVEX_SLOW_QUERY_MS` (default 200) logs `slow query &lt;ms&gt;: &lt;statement&gt;` with the statement truncated to 400 chars so a giant query can&apos;t flood the log. N+1s and missing indexes surface without a profiler. The instrumentation is fully guarded — a broken clock or threshold parse error can never fail a query. Covered by `tests/test_slow_query_log.py` (4 tests: threshold-0 logs everything, silent above threshold, statement truncation, instrumentation failure transparency).&lt;/p&gt;&lt;p&gt;Spec 092/US028 — configurable log rotation bounds (roadmap 119): `main()`&apos;s dev-server RotatingFileHandler hardcoded 10MB/5 backups — operators couldn&apos;t tighten retention on small installs or expand it on busy ones. New module-level `build_log_file_handler(path)` reads `PROXMOXVEX_LOG_MAX_BYTES` / `PROXMOXVEX_LOG_BACKUPS` env vars (falling back to 10MB/5 on unset/invalid values) so disks don&apos;t fill silently. Covered by `tests/test_log_rotation.py` (5 tests: factory exists, defaults, env overrides, real rollover prunes to backupCount, main() wired to the factory).&lt;/p&gt;&lt;p&gt;Spec 092/US027 — audit trail search honours the delegated auditor permission (roadmap 118): `/api/audit/search` and `/api/audit/facets` were gated on `roles=[ROLE_ADMIN]` while the legacy `/api/audit` and `/api/audit/integrity` endpoints already used `perms=[&quot;admin.audit&quot;]` — a user granted the audit permission could page the old endpoint but got 403 on the rich search the settings-modal viewer actually calls. Both now use `perms=[&quot;admin.audit&quot;]` (the spec&apos;s `audit:read`), so delegated auditors investigate without the full admin role. Search also audits-the-audit: every query logs an `audit.read` entry (requesting user + active filters), matching the `audit.integrity_check` pattern. The searchable viewer itself (settings-modal audit tab: q/user/action/cluster/severity/ip/date filters + pagination) was already wired — no frontend delta needed. Covered by `tests/test_audit_search.py` (5 tests: no-perm 403, delegated-perm 200, self-logged read, user/action filters, limit bound).&lt;/p&gt;&lt;p&gt;Spec 092/US026 — deep `/api/healthz` readiness probe (roadmap 117): `/api/health` only proved the process was alive — a wedged DB or dead cluster still reported ok to load balancers. New `api/healthz.py` blueprint reports per-dependency booleans: __db__ (`SELECT 1`), __clusters__ (every configured manager&apos;s `connected` flag — zero configured is a valid fresh-install state, not a failure), __license__ (free tier ok; an installed-but-lapsed license context fails), __disk__ (config volume keeps ≥128MB free). Every check runs in a bounded worker thread (2s) so a hung dependency marks itself failed instead of hanging the probe; failures are logged with the degraded component set. Unauthenticated under the existing `/api/health` public prefix (probes can&apos;t carry sessions; the response is booleans only). 200 `{&quot;status&quot;:&quot;ok&quot;}` / 503 `{&quot;status&quot;:&quot;degraded&quot;, components:{…}}`. Covered by `tests/test_healthz.py` (5 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US025 — Prometheus app-level metric families on the existing exporter (roadmap 116): `/api/metrics` already shipped curated cluster/node/guest gauges via a deliberately dependency-free, hand-rolled exposition — the spec&apos;s request-rate/latency/upstream/queue families now ride the same path (no `prometheus_client` dependency). `install_metrics(app)` adds before/after hooks in `create_app` that count every request into `http_requests_total{method,route,status}` (route = URL rule so `/api/vms/&lt;vmid&gt;/...` collapses to the rule — ids can&apos;t explode the series) and observe `http_request_seconds` per route with a fixed bucket set emitted in proper histogram form (`_bucket{le=…}` cumulative + `+Inf`, `_sum`, `_count`). The manager session wrapper — the single seam every hypervisor API call flows through — records `upstream_calls_total{result=&quot;ok&quot;|&quot;error&quot;}` on success, 5xx, and exception (lazy import: the api package imports manager, so a top-level import would be circular; a metrics failure can never break a request). `ProxmoxVEx_task_queue_depth` gauges live bulk jobs in `starting`/`running` at scrape time. Auth is unchanged: Bearer API token or `metrics_public: true` (the endpoint sits in `_public_api_prefixes` and self-authenticates — scrape clients don&apos;t carry web sessions). Covered by `tests/test_metrics_exporter.py` (6 tests: unauthenticated 401, family presence, counter labels + histogram shape, upstream record, live queue-depth gauge, manager wiring).&lt;/p&gt;&lt;p&gt;Spec 092/US024 — request-ID middleware end-to-end (roadmap 115): a user-reported failure couldn&apos;t be mapped to log lines. `create_app` gains a `before_request` hook that honours a caller-supplied `X-Request-ID` (bounded at 128 chars, else a fresh `uuid4().hex`), stashes it on `g.request_id` __and__ the US023 `request_id_var` contextvar — `RequestContextFilter` stamps it on every record emitted during the request, so `grep &lt;rid&gt;` finds exactly the request&apos;s lines. `after_request` echoes the id on the response; `teardown_request` resets the contextvar token so ids never bleed across requests on reused worker threads. Covered by `tests/test_request_id.py` (5 tests: generated id, caller echo, oversized regeneration, real-middleware contextvar set via `preprocess_request`, teardown reset).&lt;/p&gt;&lt;p&gt;Spec 092/US023 — structured JSON logging across the backend (roadmap 114): every handler emitted free-text lines a SIEM can&apos;t query. New `ProxmoxVEx/utils/json_logging.py`: `JsonFormatter` emits one JSON object per line — `ts`, `level`, `logger`, `msg`, `request_id`, `exc` — and redacts secret-ish `extra` keys (password/token/secret/ticket) so a stray extra can&apos;t leak creds. `RequestContextFilter` + the `request_id_var` contextvar inject request_id into plain `logging.*` calls (populated by US024&apos;s middleware). `make_formatter()` defaults to JSON with a `PROXMOXVEX_LOG_FORMAT=text` escape hatch; `install_json_logging()` swaps formatters on existing root handlers + attaches the filter, called in `create_app()` (covers WSGI workers that never run main) and re-applied after `main()`&apos;s basicConfig so the rotating file handler goes JSON too; per-cluster loggers in manager.py share `make_formatter()` so one env var switches the whole backend. Covered by `tests/test_json_logging.py` (7 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US022 — copy-to-clipboard affordances on identifiers (roadmap 113): `CopyButton` existed in ui.js but had __zero call sites__ (dead code) and gave no toast feedback. It now accepts an optional `addToast` prop that fires the spec&apos;d &apos;copied&apos; toast (label via the global `ProxmoxVExI18n.t` bridge so it localizes without threading `t`) alongside the existing check-icon flash; the secure-context clipboard API + hidden-textarea `execCommand` fallback is unchanged. Wired onto the resource table&apos;s identifiers: VMID in the table cell, VMID in the cards view, and the resolved guest IP (rendered only when an address exists — never on the `-` placeholder), all fed by ResourceTable&apos;s existing `addToast` prop. Covered by `tests/test_copy_button.py` (3 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US021 — consistent empty states with next-action CTAs (roadmap 112): blank lists dead-ended on bare &quot;No results&quot;/italic text. The shared `EmptyState` (icon + title + hint + optional CTA) now renders in the spec&apos;s three call sites plus tasks: ResourceTable&apos;s guest list (both cards + table views share one `emptyStateEl` — filtered-empty offers a __Clear filters__ CTA that resets filter/nodeFilter/tagFilter/search; a truly empty cluster offers __Create VM__ via a new `onCreateVm` prop wired to `setShowCreateVm(&apos;qemu&apos;)` at the ClusterPanel call site), the datastore tree (`Icons.Database` + `noDatastoresHint`), the tasks table (`Icons.Clock` + `noTasksHint`), and cluster alerts (`Icons.Bell` + `noAlertsHint` + a __New Alert__ CTA that runs a new shared `openNewAlertModal` — the header button&apos;s inline handler was extracted so both entry points do identical setup: default email channel, clean escalation steps, fresh channel list). 6 new i18n keys across all 14 locales. Covered by `tests/test_empty_states.py` (4 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US020 — first-run guided tour with server-side progress (roadmap 111): new installs had only a localStorage welcome toast — no walkthrough, and dismissal never roamed. New `components/tour.js` (`GuidedTour` + `TOUR_STEPS`) renders a floating, non-modal step card anchored to real controls via new `data-tour` attributes (`add-cluster`, `search`, `help` on the support button); steps whose anchor isn&apos;t rendered (non-admin add-cluster, corporate layout) fall back to a centered card rather than hiding — and no fullscreen backdrop, so nothing critical is blocked (spec edge case). FR-056 launches when `user.tour.done !== true`; FR-057 × pauses mid-tour ({done:false, step} resumes on next login) while Skip/&apos;Don&apos;t show again&apos; completes it; FR-058 persists via `PUT /api/user/preferences` — the preferences whitelist now accepts a strictly validated `tour` object ({done: bool, step: int 0..20}, 400 on malformed) stored as `tour:&lt;username&gt;` in proxmoxvex_kv (new `get_user_tour`/`save_user_tour` — users table is columnar). 7 new i18n keys across all 14 locales. Covered by `tests/test_guided_tour.py` (5 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US019 — notification center: durable per-user feed, per-item read/dismiss, all-user alert fan-out (roadmap 110): `push_inbox` already doubled as a feed, but alerts only reached *push subscribers* — a user who never enabled browser push had an empty bell. `_alert_handler` now fans out to every user (same fail-closed tenant/cluster access check); `_wake_user` still gates browser pushes to subscribers. Feed bounded at 200/user on insert (`INBOX_PER_USER_CAP`) rather than the old 24h global trim; `GET /api/push/inbox` serves the full 200 (was LIMIT 50). New per-item endpoints `POST /api/push/inbox/&lt;id&gt;/read` and `DELETE /api/push/inbox/&lt;id&gt;` — both username-scoped, 404 on foreign/missing ids (FR-054). `push_inbox` moved into the main schema-init block in db.py — it was created lazily at push.py import, so a DB provisioned later had no table and the feed 500&apos;d. Bulk-job completion now pushes an owner entry via `_push_to_inbox` (lazy import — api.push can&apos;t be imported at worker start) with severity by outcome and a `/#resources` deep-link (FR-055). Frontend bell feed shows the whole list (was 8-item slice), adds per-item ×-dismiss and read-on-open. Also fixes a US008 regression: `pollingInterval`/`clearPollingInterval` called themselves recursively instead of `setInterval`/`clearInterval` after the global sed — would have stack-overflowed the first poller. Covered by `tests/test_notifications.py` (8 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US018 — per-user per-table column layouts with picker (roadmap 109): the resource table&apos;s columns were a fixed literal — ops couldn&apos;t hide or reorder them. New `TABLE_COLUMNS` registry marks `name`/`status`/`actions` required (FR-052 — can&apos;t be hidden; the checkbox column is scaffolding). `ColumnPicker` popover in both toolbar variants toggles visibility with one click inside the picker (SC-036) and reorders via ↑/↓; the thead and the row now render from one `tableColumns` list — row `&lt;td&gt;`s were re-keyed into a `resourceCells` map (`{(() =&gt; { const resourceCells = {...}; return tableColumns.map(...) })()}`) so order and visibility apply to headers and cells together, and the empty/spacer `colSpan`s follow the visible count. Layout persists per user per table (FR-051): localStorage for instant paint + new `GET/PUT /api/user/table-prefs/&lt;table_id&gt;` endpoints backed by `table_prefs:&lt;username&gt;` kv blobs (`get_user_table_prefs`/`save_user_table_prefs`) — keyed per table so layouts never bleed across tables. Shape-validated (hidden/order lists of ≤100 keys ≤80 chars), per-user isolated, 401 for anon. Stale layouts degrade: unknown order keys drop, new columns append. Covered by `tests/test_table_columns.py` (8 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US017 — named filter presets saved server-side per user (roadmap 108): the VM table&apos;s filter set already persisted per-user in localStorage (#T018) but couldn&apos;t roam or be named. New `/api/user/filters` endpoints on the users blueprint — `GET` lists presets, `PUT /&lt;name&gt;` saves criteria, `DELETE /&lt;name&gt;` removes — stored as a JSON blob in `proxmoxvex_kv` (`saved_filters:&lt;username&gt;`) via new `get_user_filters`/`save_user_filters` db helpers, keeping the change off the columnar users schema (FR-048). Criteria are whitelisted to the keys `ResourceTable` owns (`filter`, `nodeFilter`, `tagFilter`, `itemsPerPage`, `search`, `sortBy`, `sortDir`, `viewMode`); unknown keys are dropped and reported in `skipped` — a preset from a newer/older UI degrades with a notice, never errors (FR-049). Name ≤80 chars, 50 presets/user, save/delete audited. Frontend `SavedFiltersMenu` (tables.js) sits in both toolbar variants — one click applies a preset exactly (SC-033), unknown keys toast a warning; Enter saves the current set under a name; × deletes. Covered by `tests/test_saved_filters.py` (8 tests: roundtrip, skipped-with-notice, all-invalid 400, 404, per-user isolation, name/count bounds, frontend wiring).&lt;/p&gt;&lt;p&gt;Spec 092/US016 — bulk ops: per-item denial, queued-item cancel, bound batches, shift-range select (roadmap 107): `POST /vms/bulk` used to 403 the whole batch on the first permission-denied guest — SC-032 requires it be a failed *item*. The route now splits guests into allowed/denied, passes `denied_guests` to `start_bulk_job` (recorded as `{&quot;success&quot;: false, &quot;error&quot;: &quot;permission denied&quot;}` before the worker starts, `task.total` covers both lists), and returns `{&quot;denied&quot;: [vmids]}` in the 202 — 403 only when *every* guest is denied. New `POST /vms/bulk/&lt;job_id&gt;/cancel` flags `task.cancel_requested`; the worker checks between batches (the in-flight batch finishes) and records the remainder via `add_cancelled` (`success: null, cancelled: true`, never dispatched — verified disjoint from `vm_action` calls). Guest list bounded at 500. Final status gains `cancelled` for fully-cancelled jobs; `to_dict` exposes the `cancelled` count and progress accounts for it. Frontend `ResourceTable` gains shift-click range selection via `lastSelectedIndexRef` — the contiguous span between the anchor and clicked row in the filtered list is added to the selection; plain/ctrl clicks keep single-toggle (FR-044). Covered by `tests/test_bulk_vm_ops.py` (5 tests); step-up bulk regression suite still green.&lt;/p&gt;&lt;p&gt;Spec 092/US015 — server theme applied before first paint (roadmap 106): theme already roamed via `PUT /api/user/preferences` (FR-041), but `checkSession` applied it only after an async fetch — a fresh browser painted the localStorage/default palette first, then flashed to the user&apos;s real theme. `_serve_index` now validates the session cookie/header (the `/` route is public so `request.session` is unset) and injects `window.__PROXMOXVEX_THEME__` into `&lt;head&gt;` — json.dumps-encoded, nonce-stamped by the existing CSP pass — using the same `user.theme || default_theme || proxmoxDark` chain as the preferences GET. The boot IIFE in `tailwind-config.js` prefers the injected name over localStorage (FR-042); anonymous shells get no injection and keep the localStorage/system fallback (FR-043). Covered by `tests/test_theme_roaming.py` (5 tests: saved theme injected, anon uninjected, default fallback, injection precedes bundle, boot prefers server value).&lt;/p&gt;&lt;p&gt;Spec 092/US014 — &apos;?&apos; shortcuts modal: context grouping, focus restore, dead listings wired (roadmap 105): the `KeyboardShortcutsModal` existed but listed shortcuts flat and silently dropped focus on close. `KEYBOARD_SHORTCUTS` entries now carry a context group rendered under Global/Navigation/Actions headings (FR-039), and the modal uses the shared `useFocusTrap` hook — focus is captured on open, Tab-cycled inside the dialog, Escape closes, and focus returns to the previously focused element (FR-040; replaces the modal&apos;s bespoke window-Escape listener). SC-027 caught a real bug: `n`/`Shift+N` were listed but never wired — the main keydown handler now opens the create-VM / create-container dialogs for the selected cluster. &apos;?&apos; in text inputs remains ignored (SC-028). Covered by `tests/frontend/test_shortcuts_modal.py` (6 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US013 — command palette lands on the picked entity (roadmap 104): the Ctrl/Cmd+K palette already existed (cluster/VM/node/action catalog, exact→prefix→substring scoring, arrow/Enter/Escape nav) — the spec gap was that picking a VM merely opened the Resources tab, and picking a node just re-selected the cluster. `onPickVm` now mirrors global-search `navigateToResult`: it selects the guest in the sidebar and scroll/highlights its row via `setHighlightedVm` after resources load. Node entries route through a new `onPickNode` prop that selects the node in the sidebar on the overview tab. Covered by `tests/frontend/test_command_palette.py` (6 tests: component/hotkey, catalog kinds, VM-lands-on-VM contract, node selection, keyboard nav).&lt;/p&gt;&lt;p&gt;Spec 092/US012 — content-hashed asset URLs (roadmap 103): `?v=` cache-bust params were stamped with a build *timestamp*, so every rebuild — even with zero source changes — minted a new URL and wasted the year-long immutable cache. `build.sh` now stamps `?v=&lt;sha256[:12]&gt;` of the emitted file content: identical builds produce identical URLs (verified: two consecutive builds hash to `489552e6090d`), any byte change mints a new URL so upgrades never serve stale JS/CSS. `web/manifest.json` maps logical names → current hash for future consumers. Found + fixed a determinism bug this exposed: `generated_at` in the inlined plugin-directory preamble made every bundle byte-different — it&apos;s now stripped from the inline (kept in `directory.json` for ops) at both dev and prod inline sites. HTML write moved after CSS finalize so the css hash covers the final bytes. Covered by `tests/frontend/test_asset_hashing.py` (5 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US011 — shared pooled HTTP sessions for ad-hoc callers (roadmap 102): cluster API traffic already rode per-manager cached keep-alive sessions (`_create_session`, auth-key invalidated) and bulk ops had `BULK_OPERATIONS_SESSION` — but module-level `requests.get()` callers built a throwaway session and a fresh TCP+TLS handshake per call. New `session_for(key)` in `ProxmoxVEx/services/__init__.py` returns one pooled `Session` per origin label (HTTPAdapter pool_connections=4/pool_maxsize=8/keep-alive, `pool_block=False` so a full pool opens a transient extra connection rather than stalling a request — safer than the sketch&apos;s blocking pool under concurrency). The four per-console-open internal calls in `api/vms.py` (`/api/ws/token/validate` + `/api/internal/cluster-creds/` on `ProxmoxVEx_URL`) now ride `session_for(&quot;internal-api&quot;)`. Covered by `tests/test_session_pool.py` (6 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US010 — global search stale-response discard (roadmap 101): the 300ms debounce existed, but two overlapping searches could still resolve out of order — a slow earlier request overwriting fresher results is the flicker this kills. `globalSearchSeqRef` gives every `performGlobalSearch` a monotonic sequence; results and the loading flag only apply while `my === seq.current`. `globalSearchAbortRef` aborts the superseded fetch (`authFetch` forwards `rest.signal`, AbortError swallowed silently), and clearing the box below the 2-char floor bumps the sequence + aborts immediately so stale results can&apos;t land after the input emptied. Covered by `tests/frontend/test_search_debounce.py` (5 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US009 — virtualized resource-table rows (roadmap 100): pagination bounded the DOM to the page size, but a 500-per-page view still mounts hundreds of heavy `&lt;tr&gt;` nodes (sparklines, action menus). New `useVirtualRows` hook (`web/src/components/virtual_list.js`, added to CORE_FILES) windows the row slice inside a scroll container — visible rows + 8-row overscan each side — with spacer `&lt;tr&gt;`s preserving total scroll height, so the DOM stays under ~100 row nodes at any scale (SC-013 independent test). ResourceTable engages it past `VIRTUALIZE_THRESHOLD = 100`: the table gets its own bounded scroll container (`maxHeight: calc(100vh - 260px)`, matching the detail-view pattern), a sticky thead, and a measured row height (`tr[data-vrow]` — corporate rows are denser, tag wrapping shifts the estimate). The mount-stagger `animationDelay` is disabled when virtualized (idx reaches hundreds) and the shared `renderResources` path keeps small pages un-windowed. Cards/detail views keep pagination (already bounded). Covered by `tests/frontend/test_virtual_rows.py` (9 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US008 — dashboard polling pauses in hidden tabs (roadmap 099): ~44 `setInterval` pollers across `web/src` used to fire at full cadence in background tabs, burning cluster API quota. New `pollingInterval`/`clearPollingInterval` helpers in `dashboard.js` guard each tick with `document.hidden` and register the refresh fn in `_pollers`; a `visibilitychange` listener runs every registered fn once on refocus for immediate catch-up (errors logged, not fatal). `clearPollingInterval` deletes the registry entry so dead pollers never refire — and it no-ops cleanly on ids it didn&apos;t create, so every `setInterval`/`clearInterval` site in `web/src` could convert mechanically (auth cross-tab sync, SSE token refresh, and update-watchers included: all either pause harmlessly or catch up on refocus). Covered by `tests/frontend/test_polling_visibility.py` (7 tests).&lt;/p&gt;&lt;p&gt;Spec 092/US007 — JSON response compression verified end-to-end (roadmap 098): `Compress(app)` in `app.py` was already wired (COMPRESS_MIMETYPES includes application/json, level 6, min 500B) — `tests/test_response_compression.py` (5 tests) now locks the contract through the real app: `Accept-Encoding: gzip` yields `Content-Encoding: gzip` + `Vary: Accept-Encoding` on a 40-guest status payload, `identity` stays plain, the compressed body is smaller and gunzips to valid JSON, and brotli is negotiated when advertised (brotli installed; zstd backend absent so br/gzip are the enabled algorithms). No code change needed — the US003/US005 cached endpoints compress per-request since flask_compress runs `after_request` on the response object, not the cached dict.&lt;/p&gt;&lt;p&gt;Spec 092/US006 — SSE event coalescing (roadmap 097): the `/api/sse/updates` generator previously emitted one `data:` frame per queued event — a 200/s storm meant 200 React render-loop hits. New `_collect_sse_batch` keeps draining the client queue for up to `SSE_BATCH_WINDOW` (100ms) after the first message, capped at `SSE_BATCH_MAX` (500); `_sse_payload` emits the raw message for a single event or a joined JSON array for a batch (queued messages are already JSON strings — joined, not re-encoded). Frontend `eventSource.onmessage` now normalizes `Array.isArray(parsed)` frames back to per-event handling via a `handleSseEvent` closure — no dispatch changes needed for batched payloads. Covered by `tests/test_sse_batch.py` (8 tests: single/burst/max-cap/window-bounded/late-arrival batching, payload shape, frontend array parsing).&lt;/p&gt;&lt;p&gt;Spec 092/US005 — batched guest-status endpoint (roadmap 096): new `GET /api/clusters/&lt;id&gt;/vms/status` returns `{nodes: {node: {vmid: {status, type}}}, count}` built from a single `manager.get_vm_resources()` walk — one upstream `/cluster/resources` call serves every guest&apos;s status instead of N per-VM `/status/current` calls. `?max_age=&lt;s&gt;` (clamped 0–300) reuses the broadcast loop&apos;s fresh snapshot inside the same poll window. Rides the US003 `cached_get` (5s TTL, per-user/cluster/query key) so repeat polls skip even the snapshot lookup; US003&apos;s global mutation invalidation drops it on any cluster write. Non-guest rows in the resources walk are filtered; disconnected clusters return the standard 503. Covered by `tests/test_vms_status_batch.py` (6 tests: 401, node grouping, single-walk contract via `assert_called_once`, max_age forwarding, 503, X-Cache hit on second GET). The `api` test fixture now resets `_response_cache` per test — a cached 200 otherwise leaks across tests and masks the stubbed-manager assertions.&lt;/p&gt;&lt;p&gt;Spec 092/US004 — lazy plugin JS loading + retryable frame states (roadmap 095): plugin JS was already chunked correctly — each plugin&apos;s `ui.js` loads inside its iframe only when the user opens `plugin:&lt;id&gt;`, keeping it out of `app.bundle.js` (FR-009/FR-010/SC-007). The gap was FR-011: a hung plugin backend sat on a blank frame forever because iframe `onError` doesn&apos;t fire cross-document. New `PluginFrontendFrame` component adds a loading spinner, a 20s bounded timeout that fails over to a retryable error state (`pluginLoadFailed`/`pluginLoadFailedDesc` keys in all 14 core locales), and a Retry button that remounts the iframe via `reloadKey`. A `test_plugin_js_stays_out_of_bundle` test guards CORE_FILES/LAZY_FILES against future plugin bundling. Covered by `tests/frontend/test_plugin_lazy_frame.py` (7 tests); the file&apos;s previously corrupted duplicate iframe block was removed in the swap.&lt;/p&gt;&lt;p&gt;Spec 092/US003 — GET response TTL cache + mutation invalidation (roadmap 094): new `GETResponseCache`/`cached_get(ttl)`/`invalidate_response_cache` in `core/cache.py` cache 200 JSON responses keyed by sha256(cluster|user|path|sorted-args) — permission-filtered data can&apos;t cross users (FR-007), only complete JSON 200s are stored (errors/tuples/non-JSON pass through), hits return `X-Cache: hit`, TTL is per-endpoint (edge case). A global `after_request` in `api/__init__.py` drops the cluster&apos;s cached GETs __and__ its bespoke `vm_list_cache` on any successful mutating `/api/clusters/&lt;id&gt;/…` call (FR-008 — `vm_list_cache.invalidate` previously had zero callers, so VM lists went stale up to 30s after mutations). Applied to four previously uncached read-heavy routes: `datacenter/status` (10s), `datacenter/cluster-info` (30s), `datacenter/storage` + `datastores` (15s). Covered by `tests/test_response_cache.py` (7 tests: hit, TTL expiry, per-user/per-cluster/args keying, mutation invalidation incl. cross-cluster isolation, error non-caching).&lt;/p&gt;&lt;p&gt;Spec 092/US002 — security response-header suite verified and locked in by tests: `add_security_headers` already emits the full recommended set (X-Content-Type-Options nosniff, X-Frame-Options, Referrer-Policy strict-origin-when-cross-origin, Permissions-Policy camera/microphone/geolocation, COOP/CORP, nonce-CSP with `frame-ancestors &apos;self&apos;`, and HSTS when the request is genuinely https — including `X-Forwarded-Proto` from a trusted proxy only). X-Frame-Options is deliberately SAMEORIGIN rather than DENY (the #381 plugin-iframe contract; CSP frame-ancestors covers modern browsers). `tests/test_security_headers.py` (4 tests) now guards the suite plus the HSTS trusted/untrusted-proxy split.&lt;/p&gt;&lt;p&gt;Spec 092/US001 — breach-corpus rejection added to the configurable password policy: `validate_password_policy` (the single gate every password-set path already calls) now optionally checks new passwords against the Have-I-Been-Pwned corpus via k-anonymity (`_hibp_range` sends only the first 5 SHA-1 chars; `Add-Padding` stops response size leaking the queried prefix). Two new admin-adjustable server settings — `password_breach_check` (default off: needs egress) and `password_breach_strict` (default off: lookup failures fail open, matching the spec edge case — strict fails closed) — persisted by the settings POST endpoint and reported by both policy GET endpoints so change forms can surface the check. Min-length/character-class policy, per-rule error detail, and no-restart adjustment were already implemented (`password_min_length`/`password_require_*`); covered by `tests/test_password_policy.py` (6 tests: breach reject, clean accept, fail-open, strict fail-closed, disabled-skips-lookup, class enforcement).&lt;/p&gt;&lt;p&gt;Spec 091 — tamper-evident audit-log chain: each `audit_log` row now stores `prev_hmac` (the previous row&apos;s signature, or a `chain-genesis` sentinel at an epoch anchor) and `key_version`, and its HMAC signs `canonical|prev_hmac` — so deleting or reordering a row breaks the link, which a standalone per-row signature could never detect (FR-001). `verify_audit_log_integrity` walks the log in chronological (id) order and now reports `chained_entries`, `chain_breaks`, and `first_broken_id` — the first row failing either its own signature or its predecessor link (bounded spot-checks skip the link check at the scan boundary, but a full scan flags a non-genesis link on the true first row, catching deleted-prefix attacks). Chain-epoch keys live encrypted in `proxmoxvex_kv` and are retained across rotation, so `rotate_audit_key()` (FR-003, plus a new `POST /api/audit/key-rotate` admin endpoint with `{&quot;confirm&quot;: true}`) starts a new epoch without invalidating old chains; epoch keys are preloaded on the hub thread before the heavy verify runs off-hub so worker-thread verification never touches the psycopg2 connection. Rows written when no chain key is available fall back to the pre-091 standalone signature, and legacy rows keep their existing verified/unsigned counting. Covered by `tests/test_audit_chain.py` (7 tests: linkage, intact chain, tamper, deletion, rotation anchoring, cross-epoch verification, legacy coexistence).&lt;/p&gt;&lt;p&gt;Spec 090 — Origin/Referer CSRF gate hardening in `app.py`: `X-Forwarded-Host` now only extends the allowed-origin targets when the direct peer is a trusted proxy (`_is_trusted_proxy(request.remote_addr)` — previously any client could send `X-Forwarded-Host: x` + `Origin: https://x` and pass the gate from a foreign site), and mutating `/api/*` requests carrying a *validated* `Bearer pgx_` API token are exempt from the origin check (FR-003 — the token authenticates the request itself, so there is no ambient cookie for CSRF to ride, and curl/automation legitimately sends no Origin; a garbage Bearer header still falls through to the normal gate, so it can&apos;t be used to bypass). Foreign Origin/Referer rejections are now logged as warnings for the security audit trail (the no-header rejection stays quiet — bare scripts without Origin are common enough to be noise). `PROXMOXVEX_ALLOWED_ORIGINS` continues to accept a comma-separated allowlist of external hostnames (FR-002). Covered by `tests/test_csrf_origin.py` (21 tests incl. spoofed-forwarded-host, trusted-proxy, Bearer-exemption, bypass, and multi-origin cases).&lt;/p&gt;&lt;p&gt;Spec 089 — step-up coverage extended to all destroy-class routes: a new `STEPUP_ROUTES` registry in `utils/stepup.py` maps (method, URL rule) → destructive category and is enforced in the app-level `before_request` guard before the view runs — covering ~30 previously ungated routes (datastore volume deletes, disk wipe, Ceph OSD/pool/fs removal, VM restore-overwrites, disk/NIC/passthrough detaches, replication job deletes, bulk snapshot delete, tenant/role/user-2FA deletes, security-lockout clears, cluster membership + HA topology deletes, and all nine SDN deletes under a new `network.delete` category). Inline `require_stepup` calls stay as defense-in-depth for parameter-dependent routes (power `&lt;action&gt;`, node `&lt;action&gt;`), and bulk `POST /vms/bulk` now challenges stop-class actions — one grant covers the whole batch (SC-002). API tokens are exempt from registry gating (automation can&apos;t answer a factor prompt; scoped per #085); a broken registry check fails closed with 503. Step-up window default raised 5 → 10 min per spec.&lt;/p&gt;&lt;p&gt;Spec 088 — role-scoped TOTP enforcement + one-time recovery codes: new `force_2fa_roles` server setting confines sessions of listed roles (e.g. `[&quot;admin&quot;]`) to the MFA-enrollment allowlist even when global `force_2fa` is off — `_mfa_enrollment_block` and the `requires_2fa_setup` flag now share one enforcement check (explicit role listing beats `force_2fa_exclude_admins`; OIDC/Entra and WebAuthn-enrolled users still exempt; API tokens exempt from role scoping per the spec&apos;s automation edge case). TOTP enrollment now issues 8 one-time recovery codes (`totp_recovery_codes` column, SHA-256 hashes — plaintext returned exactly once); a recovery code satisfies the login 2FA challenge and is burned on use (audited `2fa.recovery_used`), keeping a broken authenticator from locking out the last admin. Security settings UI gains a role multi-select; the confinement gate and 2FA settings panel display the issued codes; the login 2FA field accepts the 10-char code format. 7 new i18n keys across all 14 locales.&lt;/p&gt;&lt;p&gt;Build 2026.09.18: Firewall feed: enforce licensing blocklist + report detections&lt;/p&gt;&lt;p&gt;Fix `AESGCM`/`Fernet` &quot;possibly unbound&quot; type-checker diagnostics in `core/db.py`: the module now only probes that the optional `cryptography` symbols are importable to set `ENCRYPTION_AVAILABLE`, and the classes are imported locally inside `_init_encryption`/`rotate_encryption_key` (after the availability guard), so the names are unconditionally bound where used. No runtime change.&lt;/p&gt;&lt;p&gt;Firewall feed reporting now also covers SPA-shell hits: `spa_catch_all` serves `index.html` with a 200 for every unmatched path, so exploit probes (`/wp-admin/*`, `.env`, …) produced no error and escaped detection. Catch-all responses are now reported as `server_hit` — the licensing `evaluate_event` checks bad-path/scanner-UA signatures regardless of status, so probes on the app/demo surfaces instant-block just like on licensing, and the events double as page-load visit analytics. A `g._fw_blocked` flag (flask `g` aliased to avoid the `ProxmoxVEx.globals` name collision) stops blocked hits being counted twice.&lt;/p&gt;</description><pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.475</title><link>https://proxmoxvex.com/changelog/#release--1-2-475-2026-09-18/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-475-2026-09-18/</guid><description>&lt;p&gt;Build 2026.09.18: Full build pipeline&lt;/p&gt;&lt;p&gt;Fix boot-time port race between `ProxmoxVEx.service` and the production compose stack: the enabled, `Restart=always` unit crash-looped on &quot;address already in use&quot; whenever the `unless-stopped` container won ports 5000-5002 (5,424 journal failures). `deploy-production.sh` now disables the unit while docker owns the ports and `stop-production.sh` re-enables it when handing them back, so the boot-time winner is deterministic.&lt;/p&gt;</description><pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.474</title><link>https://proxmoxvex.com/changelog/#release--1-2-474-2026-09-18/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-474-2026-09-18/</guid><description>&lt;p&gt;Build 2026.09.18: Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.473</title><link>https://proxmoxvex.com/changelog/#release--1-2-473-2026-09-17/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-473-2026-09-17/</guid><description>&lt;p&gt;Build 2026.09.17: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.472</title><link>https://proxmoxvex.com/changelog/#release--1-2-472-2026-09-17/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-472-2026-09-17/</guid><description>&lt;p&gt;Build 2026.09.17: Fix API token persistence and idempotent custom_scripts migration&lt;/p&gt;</description><pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.471</title><link>https://proxmoxvex.com/changelog/#release--1-2-471-2026-09-17/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-471-2026-09-17/</guid><description>&lt;p&gt;Build 2026.09.17: Plugin integrity: ignore bytecode in published manifests&lt;/p&gt;</description><pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.470</title><link>https://proxmoxvex.com/changelog/#release--1-2-470-2026-09-17/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-470-2026-09-17/</guid><description>&lt;p&gt;Build 2026.09.17: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.469</title><link>https://proxmoxvex.com/changelog/#release--1-2-469-2026-09-17/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-469-2026-09-17/</guid><description>&lt;p&gt;Build 2026.09.17: Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.468</title><link>https://proxmoxvex.com/changelog/#release--1-2-468-2026-09-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-468-2026-09-16/</guid><description>&lt;p&gt;Build 2026.09.16: Full build and deploy pipeline&lt;/p&gt;</description><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.467</title><link>https://proxmoxvex.com/changelog/#release--1-2-467-2026-09-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-467-2026-09-16/</guid><description>&lt;p&gt;Build 2026.09.16: Spec 081 support ticketing + Helm deploy fixes (in-cluster postgres, k8s env-injection port crash, writable plugins volume)&lt;/p&gt;</description><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.466</title><link>https://proxmoxvex.com/changelog/#release--1-2-466-2026-09-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-466-2026-09-16/</guid><description>&lt;p&gt;Build 2026.09.16: Spec 081: in-app support ticket submission — /api/v1/support/* proxy to proxmoxvex-support + Licence settings Support section&lt;/p&gt;</description><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.465</title><link>https://proxmoxvex.com/changelog/#release--1-2-465-2026-09-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-465-2026-09-16/</guid><description>&lt;p&gt;Build 2026.09.16: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.464</title><link>https://proxmoxvex.com/changelog/#release--1-2-464-2026-09-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-464-2026-09-16/</guid><description>&lt;p&gt;Build 2026.09.16: Fix UI reference checker false positive for DOM Option constructor&lt;/p&gt;</description><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.463</title><link>https://proxmoxvex.com/changelog/#release--1-2-463-2026-09-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-463-2026-09-16/</guid><description>&lt;p&gt;Build 2026.09.16: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.462</title><link>https://proxmoxvex.com/changelog/#release--1-2-462-2026-09-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-462-2026-09-16/</guid><description>&lt;p&gt;Build 2026.09.16: Multi-region demo clusters&lt;/p&gt;</description><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.461</title><link>https://proxmoxvex.com/changelog/#release--1-2-461-2026-09-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-461-2026-09-16/</guid><description>&lt;p&gt;Build 2026.09.16: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.460</title><link>https://proxmoxvex.com/changelog/#release--1-2-460-2026-09-16/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-460-2026-09-16/</guid><description>&lt;p&gt;Build 2026.09.16: Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.459</title><link>https://proxmoxvex.com/changelog/#release--1-2-459-2026-09-15/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-459-2026-09-15/</guid><description>&lt;p&gt;Build 2026.09.15: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.458</title><link>https://proxmoxvex.com/changelog/#release--1-2-458-2026-09-15/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-458-2026-09-15/</guid><description>&lt;p&gt;Build 2026.09.15: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.457</title><link>https://proxmoxvex.com/changelog/#release--1-2-457-2026-09-15/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-457-2026-09-15/</guid><description>&lt;p&gt;Build 2026.09.15: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.456</title><link>https://proxmoxvex.com/changelog/#release--1-2-456-2026-09-15/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-456-2026-09-15/</guid><description>&lt;p&gt;Build 2026.09.15: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.455</title><link>https://proxmoxvex.com/changelog/#release--1-2-455-2026-09-15/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-455-2026-09-15/</guid><description>&lt;p&gt;Build 2026.09.15: Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.454</title><link>https://proxmoxvex.com/changelog/#release--1-2-454-2026-09-15/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-454-2026-09-15/</guid><description>&lt;p&gt;Build 2026.09.15: Full build pipeline&lt;/p&gt;&lt;p&gt;`web/i18n/ollama_sync.py`: add `--locale-root DIR` (repeatable) so the sync can reach satellite projects&apos; locale trees — the workspace &quot;language system renew (AI)&quot; task now also scans `proxmoxvex-landing/src/i18n/locales` (`landing` ns) and `proxmoxvex-licensing/app/admin/static/i18n/locales` (`licensing` ns); `--namespace` resolves inside extra roots too. Also add `--retranslate-all` (regenerate every key from `en`, not just missing) and `--dry-run` (report missing/extra per namespace+language without calling Ollama or writing files).&lt;/p&gt;</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.453</title><link>https://proxmoxvex.com/changelog/#release--1-2-453-2026-09-14/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-453-2026-09-14/</guid><description>&lt;p&gt;Build 2026.09.14: Full build pipeline&lt;/p&gt;&lt;p&gt;`ids/api.py`, `ids/models.py`, `ids/actions.py`, `ids/rules.py`, `ids/engine.py`: clear IDE diagnostics — `_json_body()` now returns `{}` instead of `None` on its error paths (callers always return early on the error response, so the data value is never consumed there) which keeps the parsed body non-Optional for static checkers; `update_segment`/`update_rule`/`update_policy`/`tune_rule` now guard the post-write `get_*()` re-fetch returning `None` (was an unguarded subscript/dict arg); `_version_is_newer` and the `list_rules`/`count_rules`/`list_alerts`/`block_ip`/`apply_action`/`re_evaluate`/`analyze_traffic` filter params are declared `Optional` to match their `= None` defaults and the `None`-permitted call sites; and the `from gevent import get_hub` import carries `# type: ignore` (gevent ships no stubs — same suppression as `app.py`).&lt;/p&gt;&lt;p&gt;`app.py`: clear IDE diagnostics — guard `self.environ` (typed Optional without gevent stubs), annotate `server_kwargs` as bare `dict` so adding `handler_class` type-checks, drop redundant `str()` around `type(e).__name__`, and migrate self-signed certificate generation from the deprecated pyOpenSSL `crypto.*` API to `cryptography`&apos;s X.509 builder (already a pinned dependency).&lt;/p&gt;&lt;p&gt;`api/plugins.py`, `background/plugin_updates.py`, `app.py`, `web/src/settings_modal.js`: plugin update management — `POST /api/plugins/updates/check` checks every enabled/installed paid plugin against the licence server in one call (enabled-but-missing plugins count as pending so the apply pass performs their initial license-verified download), `POST /api/plugins/updates/apply` downloads+installs pending updates (optionally scoped to `plugin_ids`) and reloads each plugin in place, and `GET/POST /api/plugins/auto-update` persist a `plugin_auto_update` toggle + `plugin_auto_update_interval_hours` (1–168, default 24) that a new `plugin-update-check` daemon thread polls, recording `plugin_updates_last_check_at`/`plugin_updates_last_result` in server_settings for the UI. The Plugins settings tab gains &quot;Check for updates&quot; / &quot;Update all&quot; buttons, per-plugin update badges with a one-click download button, and the auto-update toggle + interval with last-run status.&lt;/p&gt;&lt;p&gt;`api/plugins.py`: fix enabled-but-never-loaded paid plugins — `load_enabled_plugins()` only iterated on-disk manifests, but `plugin_state` lives in Postgres (persistent) while downloaded plugin files land in the app-local `plugins/` dir, so a container rebuild/recreate left rows saying &quot;enabled&quot; with no code on disk; the UI showed them enabled with &quot;Not installed&quot; and they never loaded or displayed. Startup now reconciles: enabled plugins missing from disk are re-downloaded via the license server and loaded, with failures recorded into `plugin_state.error` so the UI shows the reason.&lt;/p&gt;&lt;p&gt;`api/plugins.py`: `POST /api/plugins/&lt;id&gt;/update` now unloads the plugin before reloading — `load_plugin()` is idempotent for already-loaded modules, so an applied update previously kept running the old code until restart.&lt;/p&gt;&lt;p&gt;`services/plugin_delivery.py`: `is_paid_plugin()` now resolves `directory.json` via `self.plugins_dir` (PLUGINS_DIR) instead of a fixed `parents[2]` path that diverged when the process CWD wasn&apos;t the project root; and two call sites passed the DB wrapper where the raw connection was expected (`_log`/`set_plugin_integrity` call `conn.commit()`, which the wrapper doesn&apos;t expose — the integrity-mismatch and tamper branches would have raised AttributeError).&lt;/p&gt;&lt;p&gt;`tests/test_plugin_delivery.py`: regression tests for startup restore of enabled-but-missing paid plugins (success + failure paths) and for the bulk update check covering not-installed plugins. Multi-context `with` blocks use backslash continuation — the parenthesized form needs Python 3.9 but the project floor is 3.8.&lt;/p&gt;&lt;p&gt;`api/licence.py`, `ids/api.py`: apply the same `current_app` unwrap fix as plugins.py — licence activation now loads plugins via the shared `_current_flask_app()` helper, and the IDS feed-update path resolves the real app with `app_ctx.app` before handing it to the gevent threadpool (where an unbound `LocalProxy` would raise anyway).&lt;/p&gt;&lt;p&gt;`api/plugins.py`: fix IDE type-checker errors — Flask 3 stubs annotate `current_app` as `Flask` (it&apos;s a `LocalProxy` at runtime), so `current_app._get_current_object()` failed checks; all four call sites now go through a `_current_flask_app()` helper returning `flask.globals.app_ctx.app` (same proxy unwrap, typed). Also guard `importlib.util.spec_from_file_location()` returning `None`/no loader before `module_from_spec`/`exec_module` — it now raises `ImportError`, which the existing `except` turns into a clean load failure.&lt;/p&gt;&lt;p&gt;`tests/e2e/test_vm_update_manager.py`: annotate the respond-flow `state` dict as `dict[str, Any]` — `respond_body` starts `None` and is later assigned/subscripted with the parsed JSON body, which the checker rejected under the inferred `None` type.&lt;/p&gt;&lt;p&gt;`scripts/full-pipeline.sh`: write `phases.tsv` rows with `printf` instead of `echo` so field separators are real tabs — plain `echo` emitted literal `\t` characters, which broke the `IFS=$&apos;\t&apos;` parse in `generate_report` and rendered the BUILD_REPORT.md phase table as raw `name\tPASS\t…` text&lt;/p&gt;&lt;p&gt;`web/i18n/ollama_sync.py`: replace the `locals()` probe in the `KeyboardInterrupt` handler with explicit partial-progress state (`partial` tuple armed only while a language file is mid-translation, disarmed after save) — the old check tripped &quot;possibly uninitialized&quot; diagnostics on `current`/`translated`/`lang_path` and could merge a previously finished file&apos;s translations into the next file&apos;s path if interrupted between iterations.&lt;/p&gt;&lt;p&gt;`api/pbs.py`, `core/db.py`: fix 500 on `GET/PUT /api/pbs/verify-schedule` — the route created `ProxmoxVEx_kv` per-request with a mixed-case name; the PG translator emits `INSERT INTO &quot;ProxmoxVEx_kv&quot;` (quoted ⇒ case-sensitive) while the unquoted CREATE made lowercase `proxmoxvex_kv`, so every save died on `UndefinedTable`, and the per-request CREATE TABLE could also race concurrent GETs. The store is now `proxmoxvex_kv` (lowercase, matching table-naming convention), created once in `_init_db`.&lt;/p&gt;&lt;p&gt;Sweep for other SQLite-isms the Postgres translator mishandles (same class as verify-schedule):&lt;/p&gt;&lt;p&gt;`core/db.py` (audit_facets, search_audit_log, get_audit_log), `core/manager.py` (task→actor attribution), `api/settings.py` (support-bundle audit/ssh exports), `background/broadcast.py` (portal task feed), `native/netapp_storage/api/setup.py` (PVE cluster creds): `user` is a PG reserved word — unquoted it resolves to `current_user`, so every audit `SELECT user` returned the DB role (`proxmoxvex`) instead of the actor, user filters matched nothing, and netapp built PVE logins with the wrong username. All refs now `&quot;user&quot;`.&lt;/p&gt;&lt;p&gt;`core/db.py`: `!= &quot;&quot;` is a zero-length delimited identifier (SyntaxError) on PG — fixed to `!= &apos;&apos;` in audit_facets and all four `rotate_master_key` SELECTs (key rotation previously errored out without rotating any secrets).&lt;/p&gt;&lt;p&gt;`api/groups.py`, `background/broadcast.py`, `api/settings.py`, `native/netapp_storage/api/{restore,dr}.py`: literal `%` LIKE patterns moved into params — psycopg2 %-escapes the whole statement when params are present, so `LIKE &apos;xclb.%&apos;` + `?` died with `IndexError: tuple index out of range` (500).&lt;/p&gt;&lt;p&gt;`core/db.py`: `_migrate_custom_roles` now supplies `tenant_id` — the composite PK `(name, tenant_id)` made every JSON-migrated role hit a NOT NULL violation that was silently suppressed.&lt;/p&gt;&lt;p&gt;`core/db_pg.py`: `datetime(&apos;now&apos;,&apos;-24 hours&apos;)` translated to `NOW() - INTERVAL &apos;-24 hours&apos;` (sign flip → +24h) — the `task_users` cleanup deleted *all* rows on each restart. Now emits `NOW() + INTERVAL &apos;&lt;modifier&gt;&apos;`.&lt;/p&gt;&lt;p&gt;`native/netapp_storage/__init__.py`: `_migrate_dr_v3` could never detect the `netapp_dr_plans → netapp_dr_sites` FK on PG (the emulated `sqlite_master.sql` yields the table name, not DDL) — on PG it now checks `pg_constraint` and drops the FK via `ALTER TABLE … DROP CONSTRAINT`.&lt;/p&gt;&lt;p&gt;`tests/unit/test_db_pg_translate.py`: regression tests for the case-sensitivity contract and the pbs route&apos;s table naming.&lt;/p&gt;&lt;p&gt;`services/licensing.py`, `api/licence.py`, `web/src/pages/Settings/Licence.jsx`: add a calculated machine ID (`pxvex-&lt;sha256&gt;` over /etc/machine-id, DMI serials, MAC and hostname — `PROXMOXVEX_MACHINE_ID` env override, persisted to `config/.proxmoxvex_machine_id`) sent on `/v1/licenses/validate` so the licence server can lock a key to one server; `/v1/licence/status` now returns `machine_id`/`node_fingerprint`, the licence settings page displays the machine ID with an unlock hint, and activation surfaces a &quot;locked to another server&quot; message on `MACHINE_LOCKED`.&lt;/p&gt;&lt;p&gt;`tests/e2e/ids/run_e2e.py`: serve the test app with `make_server(..., threaded=True)` — the UI opens a long-lived `/api/sse/updates` EventSource stream on load, and the default single-threaded `BaseWSGIServer` blocks inside that infinite response generator, so every request after the SSE connect queued in the TCP backlog and `page.goto` timed out (production uses gevent `WSGIServer` with per-connection greenlets, so threading matches real behaviour).&lt;/p&gt;&lt;p&gt;`core/db.py`: restore the connection-pool helpers (`_DB_CONNECTION_POOL`, `_get_db_connection`) in PostgreSQL form — keyed by DSN and returning `PGConnection` instances instead of the removed `dbcrypto.connect`, LRU-evicting and closing the oldest entry past `_DB_POOL_MAX_SIZE` under a lock (the original called `dict.popitem(last=False)` on a plain dict, which would have raised TypeError if ever reached). Restores the surface asserted by `tests/test_connection_pooling_for_database_queries.py`.&lt;/p&gt;&lt;p&gt;`api/search.py`: `search_index: dict = {}` → `search_index = {}` — the annotation broke the `tests/test_indexing_search_index.py` source-parity check.&lt;/p&gt;&lt;p&gt;`pyproject.toml`: exclude `config/backups/` from Pyrefly (`project-excludes`) and Ruff (`extend-exclude`) — the per-release package snapshots intentionally lag the live tree, so the IDE type checker was reporting already-fixed diagnostics on files like `config/backups/*/proxmoxVEx/api/.ssh_ws_server.py`.&lt;/p&gt;&lt;p&gt;`models/server_access.py`: `has_server_access_assignments` now re-raises DB errors instead of returning `False` — a DB failure previously masqueraded as &quot;feature inactive&quot;, so callers granted non-admins legacy open access to all clusters/servers (fail-open); rbac wrappers already fail closed, so propagating keeps the error path restrictive.&lt;/p&gt;&lt;p&gt;`api/vms.py`, `core/v2p.py`: repair shell flags mangled to `-_l` by an earlier bulk edit — `wc -_l` (node-count check in `test_node_connection`), `pmxcfs -_l` ×3 (cluster join/remove local-mode fallback), `nc -_l` ×2 (V2P nc transfer listeners), `umount -_l` (lazy unmount); all were invalid options silently breaking those paths.&lt;/p&gt;&lt;p&gt;`core/db.py`: `get_session` now hashes the lookup token with SHA-256 — `save_session`/`delete_session` store `sha256(token)`, so a raw-token `WHERE token = ?` could never match a row.&lt;/p&gt;&lt;p&gt;`api/auth.py`: fall back to `img.save(buffer)` without a `format=` kwarg when qrcode&apos;s pure-python `PyPNGImage` (no PIL installed) is in use — `PyPNGImage.save(stream)` rejects the `format` kwarg, so QR generation raised TypeError on PIL-less installs.&lt;/p&gt;&lt;p&gt;`ids/capture.py`: `process_packets` now heartbeats `ids_capture_jobs` only when a job already exists with status `running` — the unconditional `upsert(..., &quot;running&quot;)` meant any on-demand `/analyze` call resurrected a stopped job (or created a phantom running row) and cleared its error message.&lt;/p&gt;&lt;p&gt;`api/.ssh_ws_server.py`: encode the ws→ssh pump message to bytes before `channel.send` — paramiko documents `send(s: bytes)`; it happened to coerce str via `util.asbytes`, but sending bytes explicitly removes the reliance on that implicit conversion.&lt;/p&gt;&lt;p&gt;`ids/api.py`: replace 14× `request.get_json(silent=True) or {}` with a `_json_body()` helper — malformed JSON now returns 400 &apos;Invalid JSON body&apos; instead of being silently treated as `{}` (which produced misleading &apos;Missing fields&apos; errors or no-op updates), and non-object JSON (array/string) is rejected with 400 instead of crashing on `data.keys()`. (`_actor` session-spoofing finding was inapplicable — session is server-side behind `require_auth`.)&lt;/p&gt;&lt;p&gt;`core/manager.py`: shell-quote remote-command interpolations that were bare in SSH strings — `ha-manager node-maintenance` node_name, `qm/pct stop` vmid/ctid, `sg_persist` disk path, HA config `mv`/`cp` source/target paths, and `pvesm path` content_type (API-whitelisted, quoted anyway for defense in depth). `suppress(BaseException)` → `Exception` on the SFTP-relay close sites so greenlet kills aren&apos;t eaten during cleanup. (All ssh/sshpass subprocess calls already use argv lists with shell=False and SSHPASS env — the &apos;unquoted user/host&apos; and &apos;SSHPASS env insecure&apos; findings were inapplicable; `_wrap_with_sudo`&apos;s base64|sudo-bash is the safe elevation primitive, not an injection vector.)&lt;/p&gt;&lt;p&gt;`native/docker_swarm/__init__.py`: fix `log_audit(&quot;policy_apply&quot;, audit_data, user)` arg-order bug (a dict landed in the `action` column and the username in `details` — now `log_audit(user, action, json.dumps(audit_data))`); `_compute_balance_insights`/`_api_balance_insights` called `.get` on the `(dict, status)` tuple `_api_load_balance` returns (AttributeError → insights always failed) — now unpacked; `total_freed += freed_bytes` coerced to int (str from parsed output would TypeError); `_metrics_init_db` guards `PGConnection(_pg_dsn())` connect failures (metrics stay best-effort, retried next sample); `_load_config` logs malformed config.json instead of silently defaulting; `_save_config` chmod 600s config.json (contains SSH credentials); policy applier `apply` verified callable before invocation; `suppress((A, B))` tuple arg → `suppress(A, B)`; 12 silent `suppress(Exception)` sites now debug-log; `wrapped.__swarm_only__` via `Any`-typed alias; `request.session` via `getattr`; JSON-helper return annotations added. (`?`→`%s`/`INSERT OR REPLACE` findings were false positives — `PGConnection._translate` handles both.)&lt;/p&gt;&lt;p&gt;`core/v2p.py`: fix `shlex.quote()` called on ints (num_blocks/DELTA_BS/BLOCK_SIZE/rdi/size_gb → runtime TypeError crashing the delta-sync, checksum, NBD-retry and conf-write paths); guard unbound `pve_mgr` in the migration-failure cleanup (early failures hit NameError); guard `vmware_mgr is None` in `_clone_snap_present` and `vol_path`/`cflat` None in checksum/RDM paths; `_qemu_device_spec` now emits `virtio-blk-pci` for virtio disks (was silently falling back to ide-hd); `_pvesm_alloc_disk` tracks which filename the winning attempt used so a qcow2 success no longer guesses `fn_raw`; remove the dead `or True` BIOS condition and `if False` dd branch + unused randint/quote statements; nc-transfer dd writers now emit `status=progress` to the `-dl-` logs `_monitor_disk_write` reads (block-device targets previously got heartbeats only); `suppress(BaseException)` → `Exception` at 14 cleanup sites (was swallowing GreenletExit/KeyboardInterrupt); debug-log the silent SSE broadcast and monitor-tick failures.&lt;/p&gt;&lt;p&gt;`core/pbs.py`: `_ensure_ticket` now returns the connect result and all five api_* callers bail early with the real auth error (a failed ticket refresh previously fired the request with dead credentials → confusing 401); `api_get`/`api_delete` parse the PBS error body on retry failure like post/put already did; `api_get_raw` logs a skipped request on failed refresh; the silent gevent-spawn fallback in `start_update` is logged; `contextlib.suppress(BaseException)` → `Exception` on the three ssh.close() sites (BaseException also swallowed `GreenletExit`/`KeyboardInterrupt`, so a greenlet kill during close was silently eaten); widened 67 implicit-Optional parameter defaults to `| None`.&lt;/p&gt;&lt;p&gt;`api/users.py`: fix `unlock_all_ips`/`unlock_all_users` rebinding the module&apos;s imported `login_attempts_by_*` names — `globals.py`&apos;s dicts (which `api/auth.py` actually checks) kept their entries, so &quot;unlock all&quot; silently unlocked nothing; now `.clear()` in place. Drop the redundant `global users_db`/`tenants_db`/`login_attempts_*` declarations (reassignments only clobbered an unused module attribute); hoist `uuid` and `check_tenant_quota` to top-level imports and remove duplicate in-function `re`/`invalidate_all_user_sessions` imports; `_sl()` the tenant-quota error log; `request.session` reads via `getattr`; annotate heterogeneous `result`/`template` dicts and widen `_parse_avatar_data_url` to accept non-str input.&lt;/p&gt;&lt;p&gt;`api/storage.py`: fix `sc_config = None` glued onto a comment — the name was unbound when a cluster had no storage-cluster config (NameError → 500 in `get_storage_cluster_status`); guard `_g._ssh_semaphore` acquire/release (None until `init_ssh_semaphore()` runs); make the `vmid`/`disk`/`target` None-check explicit; annotate heterogeneous `node_result`; `_sl()`-sanitize exception, PVE `error_msg`/`raw_body`, `vmid`, `sc[&apos;name&apos;]`, `storage_id` interpolations in ~30 log calls (CWE-117); `request.session` reads via `getattr`.&lt;/p&gt;&lt;p&gt;`api/static_files.py`: fix the pool-delete permission cleanup that could never run — it called `conn.cursor()`/`conn.commit()`/`conn.close()` on the `get_db()` singleton (which has none of those methods; `close()` would also have killed the shared PG connection), now `db.execute()`; `_sl()`-sanitize the add-pool-member exception; correct the misleading &quot;File operation failed&quot; label in `get_vms_without_pool`; `request.session` reads via `getattr` with `.get` defaults. `utils/rbac.py`: widen implicit-Optional signatures (`tenant_id`/`cluster_id`/`vm_type`/`permission`).&lt;/p&gt;&lt;p&gt;`api/snapshots.py`: schedule dedup guards now compare calendar units instead of elapsed seconds — cron fires once per minute (a &lt;55s-early tick could previously skip a run), daily once per date, weekly once per ISO week, monthly once per calendar month (a mid-period `schedule_at`/`schedule_day` edit could previously swallow a run). `request.session` via `getattr`, `params: list`, `cluster_id`/`cluster` narrowed to `str`.&lt;/p&gt;&lt;p&gt;`api/site_recovery.py`: fix `db` used uninitialized in `check_readiness` (assigned only inside `if job_ids:` → `NameError` + 500 when no plan VM had a replication job); log the previously-silent failure-status write in the background-crash handler; `_sl()`-sanitize exception text in crash log; `request.session` reads via `getattr`.&lt;/p&gt;&lt;p&gt;`api/siem.py`: log the silent `except` in `_record_result` (failed status bookkeeping was invisible); annotate `_mask`&apos;s heterogeneous `out`, `params`, `_queue`, `_TLS_DOWNGRADE_WARNED`.&lt;/p&gt;&lt;p&gt;`api/settings.py`: fix the update-rollback restoring to the wrong paths (the first `.py` in a backup was copied over `settings.py` itself and `index.html` into `api/`; now restores `ProxmoxVEx/`+`web/`+`static/`+root files to `install_dir` matching the backup layout); remove the legacy `update_server_settings` else-branch that reset domain/ports/SSL/ACME to form defaults on a bodiless POST, hoisting the cert/key/login-background uploads so they are actually reachable; realpath the `commonpath` guard; drop a duplicated `resp` check; tighten CORS origin validation to a parsed scheme+hostname; fully redact IPs in support bundles; log silent `except`s (`_ip_matches`, rolling-update `completed_at`, `get_node_vms` fallback, restore id-only records, backup `fetchall`s). `core/db.py`: fix `save_affinity_rule` call signature in restore (missing `data` arg meant affinity rules never restored — TypeError per rule), skip tenants missing `id`, guard `len(None)` on unreadable AES key files, remove the dead `_get_db_connection` pool (`dbcrypto.connect` no longer exists), declare `db_path`/`_initialized`/`aes_key`, widen implicit-Optional signatures and `execute`/`query` params, `cast` the `verify_audit_log_integrity` transform result. `core/db_pg.py`: `row_factory: Any` for the Row compat shim.&lt;/p&gt;&lt;p&gt;`api/search.py`: log the previously-silent `except Exception` swallows (legacy JSON fallbacks in `load_favorites`/`load_vm_tags`, per-VM detail fetch in `search_vms_by_tag`); `load_favorites`/`load_vm_tags` return `-&gt; dict` so favorite/tag mutation sites type correctly; annotate module dicts and read `request.session` via `getattr`.&lt;/p&gt;&lt;p&gt;`api/schedules.py`: log the previously-silent `except Exception` swallows (legacy JSON fallback in `load_schedules`, rolling-update node-status polls, maintenance-lock sweep, malformed update-schedule `time`/`last_run` values); `load_schedules() -&gt; dict` so `.get(&quot;actions&quot;)` call sites type correctly; `update_schedule_last_run`&apos;s `next_run` is `str | None` (`once` schedules pass `None`); annotate the update-schedule literal as `dict` and read `request.session` via `getattr`.&lt;/p&gt;&lt;p&gt;`api/reports.py`: fix `log_audit` argument-order bug in hardening apply/rollback (action string was passed as `user`, details dict as `action` — audit rows recorded garbage); log the `get_node_status` 500 paths in CVE scan/debsecan install and the silent per-cluster VM-fetch in `get_top_vms`; guard `fetchone()[&quot;count&quot;]` against None and `snapshots` against explicit JSON null; annotate heterogeneous dict/list accumulators.&lt;/p&gt;&lt;p&gt;`api/realtime.py` + `utils/realtime.py`: log the silent exception swallows (WS-token pool-permission check, console-ticket mint, SSE ip-cache spawn, SMTP password decrypt fallback); `create_sse_token`&apos;s `allowed_clusters` is `list | None` (`None` = all clusters); widen `broadcast_update`/`broadcast_sse`/audit `details`/`cluster_id`/`user` params to `| None`.&lt;/p&gt;&lt;p&gt;`api/push.py`: log the previously-silent `except Exception` swallows (VAPID key parse/migrate, inbox trim, subscription failure bookkeeping, subscriber lookup — which silently dropped alert fan-out, endpoint URL parse); fix the CGN check comparing `IPv4Address &lt;= IPv6Address`, which raised `TypeError` on IPv6 endpoint hosts and silently skipped the check.&lt;/p&gt;&lt;p&gt;`api/datacenter.py`: whitelist `vendor`/`policy` inputs in `generate_multipath_conf` (they were interpolated verbatim into the generated `/etc/multipath.conf` — a `&quot;` or newline could inject config directives onto every node; now `ValueError` → 400); validate multipath `dev_name` against the dm-name charset before it reaches remote-shell `grep`/`lsblk` strings; annotate heterogeneous `result` dicts.&lt;/p&gt;&lt;p&gt;`api/converter.py`: `shlex.quote` each package name in the `fix_missing_tools` apt-get command (list is hardcoded today — hardening against future edits), and narrow `target_id` through `str()` before `int()`.&lt;/p&gt;&lt;p&gt;`api/clusters.py`: fix orphaned-schedule pruning on cluster delete (iterated `load_schedules()` as a list, but it returns `{&quot;actions&quot;, &quot;last_id&quot;}` — the prune silently never ran); guard `api_token_user` splits against malformed values missing `!`; normalize HA-group `nodes` to a comma-separated string for PVE&apos;s form-encoded API; tighten `_SAFE_NODE` to require an alphanumeric last char; drop redundant `data[&quot;pass&quot;]` fill-ins and a dead `getattr`; coerce numeric sort/arithmetic fields via `_num()` so string config values can&apos;t 500 the endpoints. `utils/audit.py`: `log_audit` signature widened to `| None` for `details`/`ip_address`/`cluster`.&lt;/p&gt;&lt;p&gt;`api/ceph.py`: harden `rbd` command construction against shell/CLI injection — `_rbd_cmd` now accepts argv lists joined via `shlex.join` (all call sites converted), and peer `client`/`site`/`mon_host` validation requires an alphanumeric/`[` first character so values can&apos;t be parsed as rbd flags. Also annotate `get_connected_manager`/`_get_any_online_node` as bare `tuple` (element types are correlated), widen `register_task_user`&apos;s `cluster_id` to `str | None`, drop a redundant function-scope `import json`, and read `request.session` via `getattr`.&lt;/p&gt;&lt;p&gt;`api/auth.py`: sanitize the client-supplied OIDC `state` before logging mismatches (log-injection hardening), use the same username normalization for OIDC existing-user checks as provisioning, and validate the auto-detected redirect-URI host from `X-Forwarded-Host`/`Host` instead of trusting it blindly.&lt;/p&gt;&lt;p&gt;Typing fixes surfaced by Pyrefly in `api/auth.py`, `utils/auth.py`, `utils/oidc.py`: read runtime-attached `request.session` via a `getattr` helper (and `setattr` where the attribute is installed), widen `validate_session`/`invalidate_session`/OIDC `expected_nonce`/`code_verifier`/`id_token_claims` signatures to accept `None`, narrow dict-key lookups past `| None`, import `quote` from `urllib.parse` instead of `requests.utils`, and annotate the health-check payload dict.&lt;/p&gt;&lt;p&gt;`api/audit_search.py`: distinguish database failures (503 &quot;database unavailable&quot;) from unexpected handler bugs (500) in the search and facets endpoints.&lt;/p&gt;&lt;p&gt;Typing fixes surfaced by Pyrefly: annotate `PGCursor.fetchall()`/`_get_table_info()` as `-&gt; list` (rows are name/position-indexable `DictRow`s), `load_alerts_config() -&gt; dict[str, Any]`, `send_email` `html_body`/`smtp_settings` as `| None`, and read the runtime-attached `request.session` via `getattr` in `api/alerts.py`. Add `types-psycopg2` to dev requirements.&lt;/p&gt;&lt;p&gt;`api/alerts.py`: narrow the alert-config JSON parse catch to `JSONDecodeError`/`TypeError` and log the JSON-file fallbacks (`cluster_alerts.json`, `cluster_affinity_rules.json`) instead of silently returning `{}`, so fallback failures are diagnosable.&lt;/p&gt;&lt;p&gt;Harden `api/.ssh_ws_server.py` exception handling: narrowed the SSH key-class probing catch to `SSHException`/`ValueError`, replaced `contextlib.suppress(Exception)` in the WS↔SSH pump and cleanup paths with logged `try/except` so real errors stay visible, scoped `urllib3.disable_warnings` to `InsecureRequestWarning`, and removed redundant `str()` calls.&lt;/p&gt;&lt;p&gt;Add `[tool.pyrefly]` config (`python-interpreter = &quot;.venv/bin/python&quot;`) so the IDE type checker resolves third-party imports.&lt;/p&gt;&lt;p&gt;`plugins/disk-usage-explorer/fs_scan.py` + `priv_scan.py`: harden the privileged-scan protocol — the helper now emits a `{&quot;ready&quot;: true}` sentinel at startup and echoes the requested path in every response, and `_PrivilegedScanner` waits for the sentinel before sending requests and reads until the matching-path response arrives. Previously, any line sudo/PAM wrote to stdout before the helper started (e.g. `sudo: unable to resolve host ...`, common in containers) was consumed as the first request&apos;s response and shifted every subsequent request one response back — each directory silently showed another directory&apos;s children. A dead helper (failed sudo auth, missing NOPASSWD) now raises a clear startup error instead of a silent empty listing.&lt;/p&gt;&lt;p&gt;`plugins/disk-usage-explorer/ui.js`: fix `ReferenceError: downloadBtn is not defined` in `openScanLog` (use `$(&apos;fileDownload&apos;)`, and un-hide it in `openFile`); surface `accessible:false` list responses as a real &quot;cannot read directory&quot; error pointing at the scan-user settings instead of rendering &quot;Empty directory.&quot;; replace `window.open` download with an anchor download (the plugin iframe sandbox grants no `allow-popups`); fix the &quot;entire host filesystem&quot; button to set `allowed_roots = [&apos;/&apos;]` — `/host` is not a mount point in any shipped deployment, so the button configured a path that can never resolve.&lt;/p&gt;&lt;p&gt;`plugins/disk-usage-explorer/ui.html`: clarify that the scan password is the service account&apos;s sudo password (not the scan user&apos;s), and that the container image ships passwordless sudo.&lt;/p&gt;&lt;p&gt;`web/src/dashboard_panels.js`, `web/src/native_integrations.js`, `web/src/settings_modal.js`: restore `allow-modals allow-downloads` on the plugin/integration iframe sandboxes — the ISS-074 hardening removed them, which silently broke `confirm()`/`alert()` destructive-action checks and export downloads across dozens of plugin UIs; popups and top-navigation remain blocked.&lt;/p&gt;&lt;p&gt;`api/plugins.py`: fix runtime enable/reload for blueprint-based plugins (apc-ups, dell-idrac, hpe-ilo, eaton-ipm, redfish-bmc, …) — `register()` calling `app.register_blueprint()` after the app had served its first request died on Flask&apos;s setup-finished guard (&quot;The setup method &apos;register_blueprint&apos; can no longer be called…&quot;), so enabling via `/api/plugins/&lt;id&gt;/enable`, `/reload`, licence activation, or post-download install always 500&apos;d. `load_plugin` now temporarily clears `app._got_first_request` for the duration of `register()` (Werkzeug&apos;s URL map supports late rule addition) and tracks attached blueprint names in `_plugin_blueprints` so `unload_plugin` can detach them — stale URL rules are rebound to a &quot;Plugin not loaded&quot; 404 stub (a disabled plugin&apos;s API was previously still reachable) and a later reload clears the stubs so re-registering the same blueprint name no longer hits the duplicate-name/endpoint guards. Regression tests in `tests/test_plugin_runtime_blueprint.py`.&lt;/p&gt;&lt;p&gt;`plugins/vm-update-manager/ui.js`: fix `awaiting_input` jobs being unanswerable — the respond flow (`jobs/respond` → `engine.respond_to_job` → SSH stdin) only ran inside `pollJob`, which was only started for jobs launched by the current page, so bulk/scheduled applies that asked a y/n question deadlocked all four workers with no input path. Clicking an in-flight job row now attaches the live poll loop (console keeps updating and the input modal is offered for `awaiting_input`), the Apply-all/Check-all queue prompts for each waiting job and posts to `jobs/respond`, `pollJob` awaits each poll tick so its promise resolves at a terminal state (the new `pollingJobs` guard keys off that to prevent duplicate pollers/modals, and `performApply`/`bulkApplySelected` now genuinely wait for completion instead of firing detached), and `awaiting_input` renders with the warning status colour. The respond modal also prefixes the guest name so bulk runs show which VM is asking.&lt;/p&gt;</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.452</title><link>https://proxmoxvex.com/changelog/#release--1-2-452-2026-09-14/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-452-2026-09-14/</guid><description>&lt;p&gt;Build 2026.09.14: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.451</title><link>https://proxmoxvex.com/changelog/#release--1-2-451-2026-09-14/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-451-2026-09-14/</guid><description>&lt;p&gt;Build 2026.09.14: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.450</title><link>https://proxmoxvex.com/changelog/#release--1-2-450-2026-09-14/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-450-2026-09-14/</guid><description>&lt;p&gt;Build 2026.09.14: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.449</title><link>https://proxmoxvex.com/changelog/#release--1-2-449-2026-09-14/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-449-2026-09-14/</guid><description>&lt;p&gt;Build 2026.09.14: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.448</title><link>https://proxmoxvex.com/changelog/#release--1-2-448-2026-09-14/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-448-2026-09-14/</guid><description>&lt;p&gt;Build 2026.09.14: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.447</title><link>https://proxmoxvex.com/changelog/#release--1-2-447-2026-09-14/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-447-2026-09-14/</guid><description>&lt;p&gt;Build 2026.09.14: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.446</title><link>https://proxmoxvex.com/changelog/#release--1-2-446-2026-09-14/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-446-2026-09-14/</guid><description>&lt;p&gt;Build 2026.09.14: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.445</title><link>https://proxmoxvex.com/changelog/#release--1-2-445-2026-09-14/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-445-2026-09-14/</guid><description>&lt;p&gt;Build 2026.09.14: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.444</title><link>https://proxmoxvex.com/changelog/#release--1-2-444-2026-09-14/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-444-2026-09-14/</guid><description>&lt;p&gt;Build 2026.09.14: Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.443</title><link>https://proxmoxvex.com/changelog/#release--1-2-443-2026-09-13/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-443-2026-09-13/</guid><description>&lt;p&gt;Build 2026.09.13: API-token guard, theme tokens, release manifest sync&lt;/p&gt;</description><pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.442</title><link>https://proxmoxvex.com/changelog/#release--1-2-442-2026-09-13/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-442-2026-09-13/</guid><description>&lt;p&gt;Build 2026.09.13: Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.441</title><link>https://proxmoxvex.com/changelog/#release--1-2-441-2026-09-13/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-441-2026-09-13/</guid><description>&lt;p&gt;Fix Server Access groups tab and per-server RBAC UI&lt;/p&gt;</description><pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.440</title><link>https://proxmoxvex.com/changelog/#release--1-2-440-2026-09-13/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-440-2026-09-13/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.439</title><link>https://proxmoxvex.com/changelog/#release--1-2-439-2026-09-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-439-2026-09-12/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 12 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.438</title><link>https://proxmoxvex.com/changelog/#release--1-2-438-2026-09-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-438-2026-09-12/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 12 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.437</title><link>https://proxmoxvex.com/changelog/#release--1-2-437-2026-09-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-437-2026-09-12/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 12 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.436</title><link>https://proxmoxvex.com/changelog/#release--1-2-436-2026-09-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-436-2026-09-12/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 12 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.435</title><link>https://proxmoxvex.com/changelog/#release--1-2-435-2026-09-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-435-2026-09-12/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 12 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.434</title><link>https://proxmoxvex.com/changelog/#release--1-2-434-2026-09-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-434-2026-09-12/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 12 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.433</title><link>https://proxmoxvex.com/changelog/#release--1-2-433-2026-09-12/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-433-2026-09-12/</guid><description>&lt;p&gt;Fix: Server Access settings tab — the &quot;Create&quot; group form and the per-server &quot;Assign&quot; action sent JSON bodies without `Content-Type: application/json`, so Flask 3.x rejected them with 415 and the UI failed silently. `server_access.js` now sends the correct headers, includes session credentials on every request, and shows an error toast on failure instead of doing nothing.&lt;/p&gt;&lt;p&gt;Server Access groups are now manageable end-to-end: group rows in the settings tab expand to add/remove members via `/api/server-access-groups/&lt;id&gt;/members` (user picker fed by `/api/users`), and `ServerAccessPanel` is now mounted in the cluster reconfigure modal (`cluster`), PBS edit modal (`pbs`), and ESXi edit modal (`vmware`) so admins can assign groups and permission levels per server. Non-admin viewers see a permission-denied note instead of a blank tab.&lt;/p&gt;&lt;p&gt;Pipeline fix: cleared the 5 mypy errors failing the `mypy-typecheck` phase — `pfsense routes/logs.py` now parses `limit` via `int(str(...))` like the OPNsense twin (the `type: ignore[arg-type]` didn&apos;t cover `call-overload`), `opnsense routes/_domain.py` excludes dataclass *classes* before `asdict`, `opnsense writers/ops.py::list_backups` type-guards the response to a list, and `fwkit/authz.py` coerces session/user role to `str` before the hierarchy lookup. Also silenced two warnings: `plugin_delivery._unpack_artifact` passes `filter=&quot;fully_trusted&quot;` to `tar.extractall` on Python 3.12+ (manual member validation stays the control; removes the 3.14 DeprecationWarning in tests) and the Dockerfile pip install uses `--root-user-action=ignore`.&lt;/p&gt;&lt;p&gt;Firewall parity (spec 003): pfSense and OPNsense integrations now share a common management surface — shared `fwkit` kernel (audit, HA sync/verify, divergence, payloads, normalize, authz), full pfSense collectors/writers (rules, aliases, NAT port-forward/outbound/1:1, DHCP, DNS, WireGuard/IPsec/OpenVPN, services, ops, audit, metrics) plus new OPNsense rules/aliases/NAT/services/ops/audit/vpn routes&lt;/p&gt;&lt;p&gt;Granular RBAC via `pfsense.*`/`opnsense.*` permission families with API-token role capping and denied-attempt auditing&lt;/p&gt;&lt;p&gt;Both plugin pages now boot the shared `static/js/firewall-manager.js` UI (legacy opnsense ui.js/ui.css removed)&lt;/p&gt;&lt;p&gt;Confirm-token + step-up on destructive ops&lt;/p&gt;&lt;p&gt;I18n for both namespaces.&lt;/p&gt;&lt;p&gt;Docs: removed the &quot;Auto-generated page&quot; banner from `docs/wiki.md`; the `Publish Wiki` workflow copies the file verbatim to the GitHub wiki `Home.md`, so dropping it from the source keeps it off both the local `/docs` page and the published wiki after the next sync.&lt;/p&gt;&lt;p&gt;Docs: dropped the intro blockquote from `docs/SECURITY.md` (the root-`SECURITY.md` vulnerability-reporting pointer and the v1.2.x PostgreSQL persistence note) so the operator guide opens directly on the architecture content.&lt;/p&gt;&lt;p&gt;API auth guard registration fix: the default-deny `before_request` guard is now attached once at the Flask app level, scoped to the registered API blueprint names, instead of being deferred onto each shared blueprint object. Previously `bp.before_request` recorded a setup hook on module-level blueprint singletons, so registering the same blueprint into a second app (as tests do) raised &quot;The setup method &apos;before_request&apos; can no longer be called on the blueprint&quot; and leaked the guard into test apps that register a blueprint directly — surfacing as order-dependent 401s (e.g. `test_stepup` after `test_settings_theme`).&lt;/p&gt;&lt;p&gt;NetBox DCIM spec surfaces (spec 016): `integrations/netbox.py` gains the spec RBAC model — connection records with token masking + TLS-trust opt-in (`netbox.connection.read`/`.write`), devices/elevations/cables (`netbox.device.read`), IP addresses/prefixes (`netbox.ip.read`), device + IP write-back with per-object-type opt-in and conflict-resolution policy (`netbox.device.write`, `netbox.ip.write`), drift report, sync history, secret-validated webhooks, and ipam-manager prefix links. All spec permissions now declared in PERMISSIONS.&lt;/p&gt;&lt;p&gt;phpIPAM spec surfaces (spec 017): `integrations/phpipam.py` gains connection records (`phpipam.connection.read`/`.write`), subnet read/write-back (`phpipam.subnet.read`/`.write`), IP address read/write-back (`phpipam.ip.read`/`.write`), sync history and drift report.&lt;/p&gt;&lt;p&gt;MinIO/S3 spec surfaces (spec 019): `integrations/s3.py` gains registered endpoints with provider type (minio/s3), TLS-verify default + self-signed opt-in, per-endpoint capability matrix, paginated bucket inventory with estimated-metrics flag, capability-gated MinIO admin view (pools/drives/erasure/healing), Prometheus metrics surface, bucket/site replication status, per-bucket quota thresholds, PBS/backup-job correlation links, and configurable cost rates for chargeback. Secrets masked on read; every mutation audited (FR-016).&lt;/p&gt;&lt;p&gt;Prometheus source spec surfaces (spec 027): `integrations/prometheus.py` gains `metrics.prometheus.*` endpoints — connection records (auth secrets masked, TLS-verify default), bounded PromQL query, metric-to-inventory mappings, server health and widget data.&lt;/p&gt;&lt;p&gt;Grafana bidirectional spec surfaces (spec 030): `integrations/grafana.py` gains `grafana.*` endpoints — connections (tokens masked), dashboard embed config, inventory links, annotation push, and panel render.&lt;/p&gt;&lt;p&gt;OpenLDAP integration (spec 032): new `integrations/openldap.py` — connection profiles with `posixAccount`/`inetOrgPerson` schema presets and ldaps/starttls/sasl_external modes (`openldap.configure`), user/group views, read-only ppolicy state, syncprov replication health, directory browser with base-DN/attribute filtering, and widget.&lt;/p&gt;&lt;p&gt;HashiCorp Vault spec surfaces (spec 035): `integrations/vault.py` gains `vault.*` endpoints — connections with auth-method validation and token masking (`vault.configure`), secrets engines (`vault.engines`), PKI certificate expiry (`vault.certificates`), leases (`vault.leases`) and audit devices (`vault.audit`).&lt;/p&gt;&lt;p&gt;Integration permission declaration: all `netbox.*`, `phpipam.*`, `s3.*`, `prometheus.*`, `grafana.*`, `vault.*` (incl. spec-named surfaces), `openldap.*` and spec-016/017/027/030/035 fine-grained permissions are now declared in `PERMISSIONS` so non-admin roles can be granted them; previously only admin&apos;s all-perms bypass reached them.&lt;/p&gt;&lt;p&gt;NetBox blueprint registration fix (spec 016): `integrations/netbox.py` existed with full export/connector/sync/import/templates/widget/alerts/policy/roles/automation endpoints but was never registered in `api/__init__.py`, so every `/api/clusters/&lt;id&gt;/netbox/*` route 404&apos;d. The blueprint is now imported and registered.&lt;/p&gt;&lt;p&gt;External Ceph cluster monitoring (spec 018): new `integrations/ceph_external.py` — connector (ceph-mgr endpoint registration), cluster list, health/monitor-quorum summary, OSD/pool views, capacity + RBD inventory, confirm-gated OSD out/reweight ops (`ceph_external.osd.manage`), alerts and dashboard widget. New permissions `ceph_external.connector`, `ceph_external.view`, `ceph_external.osd.manage`.&lt;/p&gt;&lt;p&gt;Synology DSM monitoring (spec 020): new `integrations/synology.py` — DSM connector, device inventory, volumes/pools/disk-health, shared folders + iSCSI LUNs, service states, Hyper Backup jobs, system metrics, datastore correlation links, alerts and widget. New permissions `synology.connector`, `synology.view`.&lt;/p&gt;&lt;p&gt;MikroTik RouterOS integration (spec 021): new `integrations/routeros.py` — device enrollment, interfaces/routes, tunnels/queues/device health, neighbor discovery for the topology map, config backups (`routeros.backup`), alerts and widget. New permissions `routeros.connector`, `routeros.view`, `routeros.backup`.&lt;/p&gt;&lt;p&gt;Cisco fabric visibility (spec 022): new `integrations/cisco.py` — NX-API/RESTCONF/SNMPv3 enrollment, interfaces/optics/port-channels, VLAN/trunk/STP/MAC fabric view, chassis health, config backup+diff (`cisco.backup`), confirm-gated interface admin control (`cisco.interface.admin`), alerts and widget.&lt;/p&gt;&lt;p&gt;Arista EOS integration (spec 023): new `integrations/eos.py` — eAPI/SNMP enrollment, interfaces/optics/port-channels, MLAG/BGP-EVPN/VXLAN fabric state, environment/SysDB/neighbors, config backup+sessions+diff (`eos.backup`), alerts and widget.&lt;/p&gt;&lt;p&gt;Juniper Junos integration (spec 024): new `integrations/junos.py` — NETCONF/SNMP enrollment, interfaces/optics, routing-protocol + chassis health, RE redundancy/Virtual Chassis, config backup with candidate/commit history (`junos.backup`), alerts and widget.&lt;/p&gt;&lt;p&gt;Tailscale connectivity monitoring (spec 025): new `integrations/tailscale.py` — tailnet connector, device inventory + posture, subnet-router/exit-node connectivity, ACL policy drift view, workload correlation links, alerts and widget. New permissions `tailscale.connector`, `tailscale.view`.&lt;/p&gt;&lt;p&gt;WireGuard monitoring (spec 026): new `integrations/wireguard.py` — host enrollment, interface/peer/live-state inventory, derived tunnel health, gated peer lifecycle + client-config generation (`wireguard.peer.manage`), Proxmox transport-encryption view, alerts and widget.&lt;/p&gt;&lt;p&gt;VictoriaMetrics metrics source (spec 028): new `integrations/victoriametrics.py` — vmselect/vmsingle connector, MetricsQL/PromQL query endpoint with caps, tenant-aware routing, component/pipeline health, cardinality-safe widgets, VM-specific capability surface. New permissions `metrics.victoriametrics.connector`, `.query`, `.widget`.&lt;/p&gt;&lt;p&gt;Uptime Kuma integration (spec 029): new `integrations/uptimekuma.py` — instance connector, filterable monitor inventory, heartbeat history, inventory-correlation links, maintenance-window/incident sync, catalog-driven monitor creation (`uptimekuma.monitor.create`), alerts and widget.&lt;/p&gt;&lt;p&gt;FreeIPA integration (spec 031): new `integrations/freeipa.py` — HTTPS connector, user/group sync, enrolled-host/DNS visibility, HBAC + sudo rule governance, cert/CA expiry monitoring, replication topology health. New permissions `freeipa.configure`, `.user`, `.group`, `.host`, `.hbac`, `.sudo`, `.topology`, `.cert`.&lt;/p&gt;&lt;p&gt;Authentik integration (spec 033): new `integrations/authentik.py` — OIDC provider registration, user/group sync, flows/stages/providers inventory, outpost health, event/audit ingestion, session invalidation and optional SCIM. New permissions `authentik.configure`, `.user`, `.group`, `.inventory`, `.events`.&lt;/p&gt;&lt;p&gt;Keycloak integration (spec 034): new `integrations/keycloak.py` — realm OIDC provider registration, user/group sync, realm/client/role inventory, active sessions + forced logout, admin/user events, confirm-gated client secret rotation, multi-realm federation status. New permissions `keycloak.configure`, `.user`, `.group`, `.inventory`, `.events`.&lt;/p&gt;&lt;p&gt;OPNsense Manager hardening (plugin spec): all mutations (host add/delete, test connection, DNS override add, log add) now require `plugins.manage`&lt;/p&gt;&lt;p&gt;Stable error codes (`missing_field`, `missing_id`, `invalid_field`, `invalid_host`, `invalid_ip`, `invalid_reference`, `not_found`, `conflict`, `confirmation_required`, `forbidden`)&lt;/p&gt;&lt;p&gt;Host add validates name/address and rejects duplicates with 409&lt;/p&gt;&lt;p&gt;Host delete now requires `confirm: true`&lt;/p&gt;&lt;p&gt;DNS add validates IP/host_record and the target host&lt;/p&gt;&lt;p&gt;Log add bounds messages at 2048 chars&lt;/p&gt;&lt;p&gt;Bounded audit ring records actor/action/target for every mutation. UI converted div tabs to accessible `button role=&quot;tab&quot;` controls with aria-selected/aria-controls and tabpanel roles, in-panel loading/error-with-retry states on all eight lists, a standalone English i18n fallback, namespace loading via `loadPluginNamespaceFull`, aria-live on the message region, and a new `opnsense` i18n namespace (6 keys x 14 locales).&lt;/p&gt;&lt;p&gt;NetApp Storage hardening (plugin spec): all mutations (restore, clone, snapshot/schedule delete, schedule save, snapmirror create) now require `plugins.manage`&lt;/p&gt;&lt;p&gt;Stable error codes (`missing_field`, `missing_id`, `invalid_field`, `out_of_range`, `not_found`, `conflict`, `confirmation_required`, `forbidden`)&lt;/p&gt;&lt;p&gt;Restore now requires `confirm: true` and validates the snapshot exists&lt;/p&gt;&lt;p&gt;Restore/clone accept `idempotency_key` (re-submits return `already_processed`)&lt;/p&gt;&lt;p&gt;Snapmirror duplicate pairs return 409&lt;/p&gt;&lt;p&gt;Retention bounds&lt;/p&gt;&lt;p&gt;Audit ring records actor/action/target. UI gains loading/error-with-retry states on all lists, restore double-confirm sending `confirm: true`, standalone English fallback, ARIA tab wiring, and a new `netapp_storage` i18n namespace (7 keys × 14 locales).&lt;/p&gt;&lt;p&gt;IPAM Manager hardening (plugin spec): subnet POST/PUT/DELETE and IP reserve/release now require `plugins.manage`&lt;/p&gt;&lt;p&gt;All error paths use stable codes (`missing_field`, `missing_id`, `invalid_cidr`, `invalid_field`, `invalid_action`, `invalid_state`, `conflict`, `not_found`, `method_not_allowed`, `forbidden`)&lt;/p&gt;&lt;p&gt;MAC and explicit-IP validation on reserve&lt;/p&gt;&lt;p&gt;Release now requires an identifier&lt;/p&gt;&lt;p&gt;Every mutation writes an audit entry (actor/action/target). UI gains loading/error-with-retry states, standalone English fallback, translated confirms, ARIA tab wiring, and the namespace grew to 11 keys × 14 locales.&lt;/p&gt;&lt;p&gt;IDS/IPS plugin backend hardening (plugin spec): all mutations (segment/rule/policy save+delete, alert ack/ack-all, feed update, rules convert) now require `plugins.manage`&lt;/p&gt;&lt;p&gt;Stable error codes (`missing_field`, `missing_id`, `invalid_cidr`, `invalid_enum`, `invalid_id`, `invalid_field`, `invalid_reference`, `too_large`, `not_found`, `forbidden`)&lt;/p&gt;&lt;p&gt;Real CIDR validation via `ipaddress`, severity/capture-mode/action enums, policy rule_ids checked against known rules, ack returns 404 on unknown alert, deletes are idempotent, bounded audit ring records actor/action/target for every mutation. UI already had the full `ids` i18n namespace (117 keys × 14 locales), embedded fallback map, and a11y tab semantics.&lt;/p&gt;&lt;p&gt;Grafana Data Source hardening (plugin spec): provision/unprovision/test now require `plugins.manage`&lt;/p&gt;&lt;p&gt;Stable error codes (`missing_field`, `invalid_field`, `invalid_url`, `forbidden`)&lt;/p&gt;&lt;p&gt;Datasource URLs restricted to same-origin relative paths or `https://` (blocks `javascript:`/`http:` smuggling)&lt;/p&gt;&lt;p&gt;Cluster access re-checked on provision&lt;/p&gt;&lt;p&gt;New bounded audit ring records provision/unprovision with actor. UI gains loading/error-with-retry states on all panels, standalone English fallback, translated unprovision confirm, ARIA tab wiring, and the namespace grew to 13 keys × 14 locales.&lt;/p&gt;&lt;p&gt;Disaster Recovery Runner hardening (plugin spec): all 8 mutations (scenario create/edit/duplicate/delete, run start/approve/checkpoint/action) now require `plugins.manage` — the proxy only enforces `plugins.view`; audit actor attribution now prefers `g.current_user` and is safe outside request context. New `runs` list route feeds a rebuilt Runs tab showing run status, per-step checkpoint controls, approval button for `pending_approval` runs, the live timeline, RTO/RPO score card, and the post-drill action register. Editor gains requires-approval, change-window, and RTO/RPO target fields (all already supported by the backend). UI adds loading/error-with-retry states, standalone English fallback, and the namespace grew to 44 keys × 14 locales.&lt;/p&gt;&lt;p&gt;Docker / Swarm Manager hardening (plugin spec): all mutations (node drain, service scale, stack deploy/delete, container action, image remove, prune policy save, prune run, settings save) now require `plugins.manage`&lt;/p&gt;&lt;p&gt;Stable error codes (`missing_id`, `missing_field`, `invalid_name`, `invalid_action`, `invalid_field`, `invalid_number`, `invalid_payload`, `out_of_range`, `not_found`, `conflict`, `confirmation_required`, `forbidden`)&lt;/p&gt;&lt;p&gt;Drain/scale/container-action/image-remove now 404 on unknown IDs instead of silently succeeding&lt;/p&gt;&lt;p&gt;Stack deploy rejects duplicate names with 409 and validates name/compose bounds&lt;/p&gt;&lt;p&gt;Prune requires `{&quot;confirm&quot;: true}`&lt;/p&gt;&lt;p&gt;Re-drain is idempotent&lt;/p&gt;&lt;p&gt;Every mutation writes a bounded audit ring entry (actor/action/target). UI gains loading/error-with-retry states on all lists, standalone English i18n fallback, prune confirmation dialog, ARIA tab/tabpanel wiring, and a new `docker_swarm` i18n namespace (34 keys × 14 locales).&lt;/p&gt;&lt;p&gt;Client Billing Portal hardening (plugin spec): invoice create/delete, transitions, payments, mark-paid, void, and dispute creation now require `plugins.manage` on top of the proxy&apos;s `plugins.view`; every error path uses a stable machine-readable code (`missing_client_id`, `missing_id`, `missing_field`, `invalid_total`, `invalid_status`, `invalid_amount`, `invalid_transition`, `role_not_allowed`, `already_finalized`, `already_void`, `not_found`, `method_not_allowed`, `forbidden`). UI gains in-panel error+Retry states for invoices/usage/payments lists, a standalone English i18n fallback, and 7 new keys (`deleted`, `retry`, `loadError`, `loading`, `noInvoices`, `noUsage`, `noPayments`) across all 14 locales.&lt;/p&gt;&lt;p&gt;Capacity Forecast hardening (plugin spec): scenario save/clone/delete/import, schedule create/toggle/delete, and alert create/delete now require `plugins.manage`&lt;/p&gt;&lt;p&gt;All mutation paths use stable error codes (`missing_id`, `missing_name`, `missing_field`, `conflict`, `not_found`, `invalid_resource`, `invalid_cron`, `invalid_payload`, `forbidden`)&lt;/p&gt;&lt;p&gt;Scenario name conflicts return 409&lt;/p&gt;&lt;p&gt;Scenario/schedule mutations are audited (previously only alerts/whatif/snapshot were). UI gains in-panel error+Retry states for forecast, scenarios, trends, schedules, and alerts lists, plus a standalone English i18n fallback and `retry`/`loadError`/`loading` keys in all 14 locales.&lt;/p&gt;&lt;p&gt;RBAC Explorer hardening (plugin spec): role create/update/delete now require `plugins.manage`&lt;/p&gt;&lt;p&gt;Stable error codes (`missing_field`, `missing_id`, `invalid_id`, `invalid_permissions`, `conflict`, `not_found`, `forbidden`)&lt;/p&gt;&lt;p&gt;Role-id and permission-string validation&lt;/p&gt;&lt;p&gt;New persisted audit trail (`audit` route) recording actor/action/target for every role mutation. UI rewritten with Roles/Users/Tenants/Permissions/Audit tabs (all previously dead endpoints now surfaced), role edit support, loading/empty/error+retry states, ARIA tab/panel wiring, standalone English fallback, and the namespace expanded to 40 keys × 14 locales.&lt;/p&gt;&lt;p&gt;Tape Library Manager hardening (plugin spec): mutations now require `plugins.manage` (the proxy only enforces `plugins.view`)&lt;/p&gt;&lt;p&gt;Stable error codes (`missing_id`, `missing_name`, `missing_barcode`, `missing_field`, `conflict`, `not_found`, `drive_not_empty`, `drive_has_history`, `tape_loaded`, `drive_maintenance`, `drive_occupied`, `drive_empty`, `invalid_format`, `invalid_payload`, `method_not_allowed`)&lt;/p&gt;&lt;p&gt;Barcode/drive-id conflicts now return 409. Added the `tape-library-manager` i18n namespace (70 keys × 14 locales) wired via `loadPluginNamespaceFull`, keeping the existing standalone FALLBACK map.&lt;/p&gt;&lt;p&gt;Certificate Manager UX hardening (plugin spec): list loaders (certificates, inventory, ACME requests, expiry check, rollbacks) now render an in-panel error state with a working Retry button instead of only toasting and leaving a blank or stale panel&lt;/p&gt;&lt;p&gt;Added a standalone English fallback dictionary so the plugin renders real strings when opened without the parent i18n bridge&lt;/p&gt;&lt;p&gt;New `retry`/`loadError` keys added to all 14 locales.&lt;/p&gt;&lt;p&gt;VPN Access Portal hardening (plugin spec): `plugins.manage` on client create/update/delete&lt;/p&gt;&lt;p&gt;Stable error codes (`missing_field`, `missing_id`, `invalid_field`, `invalid_type`, `invalid_cidr`, `invalid_email`, `invalid_date`, `conflict`, `not_found`, `client_disabled`, `client_expired`, `forbidden`)&lt;/p&gt;&lt;p&gt;Spec-EARS enforcement so disabled or expired clients can no longer fetch config bundles — the download route previously skipped the check entirely&lt;/p&gt;&lt;p&gt;Download filenames are sanitized&lt;/p&gt;&lt;p&gt;Audit events now record actor and cover config view/download&lt;/p&gt;&lt;p&gt;Deletes are idempotent. UI gains loading/error-with-retry states, an Expired badge, a secret-handling warning + confirm before copy/download, full `vpn-access-portal` i18n namespace (55 keys × 14 locales), and ARIA tab wiring.&lt;/p&gt;&lt;p&gt;VM Hibernate Scheduler fix (plugin spec): `_audit()` now attributes events via the RuntimeError/AttributeError-safe `_get_actor()` instead of reading `request.session` directly — scheduler-triggered runs execute without a request context and previously crashed before writing their audit entry. Adds schedule contract tests (validation, duplicate-id rejection, CRUD detail, toggle, list filtering, audit persistence).&lt;/p&gt;&lt;p&gt;TrueNAS plugin hardening (plugin spec): all mutations (instance/dataset/snapshot/share create+delete, service set, test_connection) now require `plugins.manage` with the API-token role floor&lt;/p&gt;&lt;p&gt;Stable error envelopes (`missing_field`, `missing_id`, `invalid_field`, `invalid_url`, `invalid_type`, `invalid_path`, `unknown_instance`, `unknown_dataset`, `not_found`, `confirmation_required`, `confirmation_mismatch`, `conflict`, `forbidden`)&lt;/p&gt;&lt;p&gt;Https-only instance URLs (TLS per spec), share type enum + traversal-safe path validation, instance-scoped resource checks, instance delete cascades + requires confirm=true, idempotent creates/deletes/toggles, audit events to `audit.jsonl`. New `replications/run` route starts a task, records a job, and rejects duplicate starts with 409. UI gains loading/error-with-retry states, a replication Run control disabled while active, snapshot-delete confirmation, real `&lt;button role=&quot;tab&quot;&gt;` tabs with tabpanel wiring, and a full `truenas` i18n namespace (52 keys × 14 locales) with embedded English fallback.&lt;/p&gt;&lt;p&gt;ZFS Snapshot Manager hardening (plugin spec): mutations (snapshot create/delete, rollback, prune, schedule add/delete) now require `plugins.manage` on top of the proxy&apos;s `plugins.view` gate, with the API-token role floor mirrored from `require_auth`&lt;/p&gt;&lt;p&gt;Stable machine-readable error envelopes (`missing_field`, `invalid_field`, `unknown_dataset`, `dataset_mismatch`, `not_found`, `invalid_number`, `out_of_range`, `invalid_cron`, `confirmation_required`, `forbidden`)&lt;/p&gt;&lt;p&gt;Dataset whitelist + safe-character validation&lt;/p&gt;&lt;p&gt;`age_days` bounded to 0–3650&lt;/p&gt;&lt;p&gt;Non-dry-run rollback/prune require `confirm=true`&lt;/p&gt;&lt;p&gt;Creates and deletes are idempotent (`already_exists`/`already_absent`)&lt;/p&gt;&lt;p&gt;Schedule DELETE route added&lt;/p&gt;&lt;p&gt;Audit events appended to `audit.jsonl` with actor + non-secret detail. UI gains loading/error-with-retry states, full i18n coverage (54 keys × 14 locales with embedded English fallback), tab/tabpanel ARIA wiring, `scope=&quot;col&quot;` headers, and a single-confirm rollback flow (previously double-prompted).&lt;/p&gt;&lt;p&gt;Demo sandbox completion (spec 037): adds an `is_demo` flag on the users table (plus migration) and a built-in read-only `demo-viewer` role&lt;/p&gt;&lt;p&gt;The frontend gains `web/src/services/demo.js` — a bundled module that caches `/api/config` demo flags, exposes `window.ProxmoxVExDemo.isDemoMode()/isDemoBlocked()`, and intercepts `403 demo_forbidden` responses to surface the sandbox block message&lt;/p&gt;&lt;p&gt;VM power actions now short-circuit in demo mode&lt;/p&gt;&lt;p&gt;The demo banner is i18n-ized (`demoModeTitle`/`demoModeBanner`/`demoActionBlocked` in all 14 locales)&lt;/p&gt;&lt;p&gt;Adds `DEMO_SEED_VERSION` tracking, `docs/deploy-demo.md`, and Playwright e2e coverage in `proxmoxvex-landing/tests/e2e/` (launch CTA, sandbox guard, reset/health) with a `playwright.config.ts` + `test:e2e` script.&lt;/p&gt;&lt;p&gt;Eaton IPM / Network Card-MS support (spec 013): new `plugins/eaton-ipm` — real Eaton Intelligent Power Manager REST/JSON client (token/basic auth, explicit TLS-trust opt-in) plus Network Card-MS SNMP polling via net-snmp with SNMPv3 authPriv as the default and v1/v2c gated behind an explicit opt-in; discovers UPS nodes, load segments and outlets, records metrics/sensor snapshots, syncs shutdown-agent policies, detects duplicate serials across transports, gates outlet/segment power commands behind `eaton.outlet.write` with on-battery rejection and linked-workload confirmation, and feeds the unified UPS view shape.&lt;/p&gt;&lt;p&gt;Raritan PDU support (spec 014): new `plugins/raritan-pdu` — JSON-RPC client (`POST /jsonrpc`) and SNMP PDU2-MIB (.1.3.6.1.4.1.13742) transports with v3 authPriv default; per-outlet and per-inlet voltage/current/power/energy metrics, outlet naming + Proxmox workload linking preserved across syncs, gated outlet commands, environmental sensors, transport-conflict detection, bulk /24 discovery, rack power aggregation and a linear capacity forecast.&lt;/p&gt;&lt;p&gt;ServerTech Sentry PDU support (spec 015): new `plugins/servertech-pdu` — Sentry REST client plus SNMP with automatic Sentry3/Sentry4 generation detection (.1718.3 vs .1718.4 MIB trees); per-branch and POPS per-outlet metrics, branch-overload and env-sensor events, gated outlet commands, power-chain topology view and bulk discovery.&lt;/p&gt;&lt;p&gt;Native integrations real UIs: the five lightweight integrations (VMware vCenter, pfSense Manager, VyOS, Zabbix, Active Directory) rendered raw `JSON.stringify` output inside `&lt;pre&gt;` blocks — including the `{&quot;error&quot;:&quot;unconfigured&quot;}` envelope — instead of a usable interface. Adds a shared renderer (`static/js/native-dataview.js` + `native-dataview.css`) on top of `plugin-ui.css` that turns each module&apos;s `{ok, data}` endpoints into KPI cards, KV lists, badge tables, capacity meters and friendly unconfigured/auth/timeout/empty states; each module&apos;s page is now a thin declarative tab config.&lt;/p&gt;&lt;p&gt;Native integration config persistence fix: `persist_native_configs()` now verifies a plugin&apos;s `config.json` symlink actually resolves to the volume copy (`&lt;CONFIG_DIR&gt;/native/&lt;plugin&gt;/config.json`) before treating it as &quot;already linked&quot;. Dev checkouts and test runs leave the symlink pointing at absolute foreign paths (host `config/` dir, pytest tmpdirs); those links were baked into release images and dangled inside the container, so every native config read looked unconfigured (`/api/opnsense/overview` → 400) and every write failed with `EACCES` (`POST /api/native/&lt;m&gt;/config` → 500). The startup pass now replaces stale links, and the Dockerfile normalizes symlinked `config.json` back to `config.example.json` so images always ship a real file.&lt;/p&gt;&lt;p&gt;MFA enforcement (spec 038): `force_2fa` is now a real server-side hard block — unenrolled, non-exempt users are confined to the enrollment endpoints (403 `requires_2fa_setup`) instead of only seeing a frontend banner. Adds step-up MFA for destructive actions: a new `POST /api/auth/stepup/verify` accepts a TOTP code or WebAuthn proof and stamps a session-bound elevation (configurable 0–15 min window)&lt;/p&gt;&lt;p&gt;Guarded routes (VM/snapshot/backup delete, guest power actions, storage delete, user delete, node reboot/shutdown, cluster removal) return 403 `requires_stepup` until elevated. Admins configure the master toggle, per-category toggles and window in Settings → Security&lt;/p&gt;&lt;p&gt;The web UI intercepts `requires_stepup` responses, shows a re-verification modal and retries the original request.&lt;/p&gt;&lt;p&gt;Lint cleanup (spec 039): demo integration mocks now patch client classes via a `_patch()` setattr helper so mypy reports 0 `method-assign` errors&lt;/p&gt;&lt;p&gt;Repo-wide `ruff check` is clean — split 42 multi-statement lines in `plugins/alert-correlator`, dropped the unused `shlex` import in `plugins/apc-ups`, collapsed nested `if`s, sorted imports, and rewrote parenthesized `with` blocks in tests for Python 3.8 compatibility&lt;/p&gt;&lt;p&gt;`scripts/ruff_baseline_check.py` now gates the whole repo (plugins/tests/scripts included), not just `ProxmoxVEx/`.&lt;/p&gt;</description><pubDate>Sat, 12 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.432</title><link>https://proxmoxvex.com/changelog/#release--1-2-432-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-432-2026-09-11/</guid><description>&lt;p&gt;Demo mode banner in app shell&lt;/p&gt;&lt;p&gt;Add demo-mode banner: DemoModeBanner component in the dashboard shell renders a non-dismissible amber notice bar (&quot;DEMO MODE — none of the data is live…&quot;) whenever /api/health reports demo_mode, so sandbox visitors can&apos;t mistake synthetic data for a live system.&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.431</title><link>https://proxmoxvex.com/changelog/#release--1-2-431-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-431-2026-09-11/</guid><description>&lt;p&gt;Demo: admin-role viewer + truenas module-path fix&lt;/p&gt;&lt;p&gt;Demo user elevated to admin role (the demo guard still blocks all mutating requests) so every page — audit log, settings, integrations — renders instead of permission-denied empties; fix TrueNAS mock to patch core.ws_client on the plugin&apos;s own sys.path (truenas_src is added directly, not as a package).&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.430</title><link>https://proxmoxvex.com/changelog/#release--1-2-430-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-430-2026-09-11/</guid><description>&lt;p&gt;Demo: fix integration mock module paths + opnsense fixture shapes&lt;/p&gt;&lt;p&gt;Fix demo integration mocks: patch plugin clients via their top-level sys.path imports (vcenter_src/truenas_src/ad_src) instead of the ProxmoxVEx.native.* module objects; correct OPNsense fixture shapes (system_information/systemResources/systemTime/pf_states/interfaces/gateways) so collectors parse them.&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.429</title><link>https://proxmoxvex.com/changelog/#release--1-2-429-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-429-2026-09-11/</guid><description>&lt;p&gt;Demo: unlock license tier, fix lowercase-method dispatch&lt;/p&gt;&lt;p&gt;Demo mode unlocks all license tiers (get_active_tier returns &apos;corporate&apos; under PROXMOXVEX_DEMO_MODE) so integrations render without license gates; fix mock method-case handling so module-level requests.post()/get() (lowercase verbs) hit the PBS/vCenter/Zabbix handlers.&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.428</title><link>https://proxmoxvex.com/changelog/#release--1-2-428-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-428-2026-09-11/</guid><description>&lt;p&gt;Demo integrations showcase: synthetic configured integrations&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.427</title><link>https://proxmoxvex.com/changelog/#release--1-2-427-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-427-2026-09-11/</guid><description>&lt;p&gt;Demo integrations showcase: populate the demo sandbox with synthetic, fully-configured integrations instead of &apos;unconfigured&apos; placeholders — PBS (seeded pbs_servers row + api2 mock on demo:8007/pbs.demo.lab with datastores, snapshots, tasks, config), vCenter (vSphere REST mock + pyvmomi client patch), TrueNAS (JSON-RPC call() patch), Zabbix (api_jsonrpc), OPNsense/pfSense/VyOS/NetApp (HTTP API mocks), Active Directory (client patch) and Docker Swarm (SSH exec patch). Demo-mode native config persistence now writes synthetic *.demo.lab configs rather than empty host lists, so the integration pages render live-looking data for demos.&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.426</title><link>https://proxmoxvex.com/changelog/#release--1-2-426-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-426-2026-09-11/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.425</title><link>https://proxmoxvex.com/changelog/#release--1-2-425-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-425-2026-09-11/</guid><description>&lt;p&gt;Remove the dashboard Specs tab: drop the `specs` entry from the top-nav `coreTabs`, delete the `SpecsCatalog` panel and `web/src/specs_catalog.js` (no longer bundled), and map legacy `?tab=specs` URLs to Overview via `_normalizeTab` so shared/bookmarked links no longer render an empty panel.&lt;/p&gt;&lt;p&gt;Plugin delivery: log the license server&apos;s response `detail`/body when manifest or download requests fail, so 403 denial reasons (e.g. &quot;License does not cover this plugin&quot;) are diagnosable instead of collapsing to a generic &quot;License server denied download&quot;.&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.424</title><link>https://proxmoxvex.com/changelog/#release--1-2-424-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-424-2026-09-11/</guid><description>&lt;p&gt;Add `scripts/deploy-production.sh` (production compose redeploy on the newest local image with a `/api/health` wait, mirroring `deploy-demo.sh`) and wire `proxmoxVEx: Deploy Production (compose)` + `proxmoxVEx: Deploy Demo Sandbox` into the `workspace: Full Build-Deploy-Test Pipeline` task so one run builds, deploys prod + demo, then updates landing and licensing.&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.423</title><link>https://proxmoxvex.com/changelog/#release--1-2-423-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-423-2026-09-11/</guid><description>&lt;p&gt;Fix storage-health-monitor crash when `data/schedule.json` contains a legacy/corrupt list-shaped `&quot;schedules&quot;` value: `_get_schedule_for_cluster`/`_set_schedule_for_cluster` now normalize it to a dict instead of raising `TypeError`. Also fix `test_storage_health` fixture to redirect the plugin data files after `exec_module` (module-level rebinds made the earlier patching a no-op, so tests touched the real plugin data dir).&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.422</title><link>https://proxmoxvex.com/changelog/#release--1-2-422-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-422-2026-09-11/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.421</title><link>https://proxmoxvex.com/changelog/#release--1-2-421-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-421-2026-09-11/</guid><description>&lt;p&gt;Remove real integration credentials from shipped native configs; persist integration config on the config volume; sanitize demo image&lt;/p&gt;&lt;p&gt;Relicense from AGPL-3.0 to the ProxmoxVEx Source-Available License v1.0: source remains viewable/auditable and non-production use is free, but production use now requires a commercial license. Rewrote LICENSE and NOTICE (attribution requirement retained), and updated license references in README, docs/wiki.md, the RPM spec, the LXC appliance script, the status-page footer, the settings About text, en.json sponsor copy, and pyproject (`LicenseRef-Proprietary`).&lt;/p&gt;&lt;p&gt;Rework README: document the PostgreSQL-only backend (`PROXMOXVEX_DATABASE_URL`/`PG*`), XCP-ng support, and demo mode&lt;/p&gt;&lt;p&gt;Fix the Quick Start to `http://` (compose runs `PROXMOXVEX_BEHIND_PROXY=true`)&lt;/p&gt;&lt;p&gt;Correct stale env vars (`PROXMOXVEX_DISABLE_AUTO_ENCRYPT`, `PROXMOXVEX_SSL_CERT_FILE`, `PROXMOXVEX_HEALTHCHECK_UNSAFE`), the public-endpoint list, the directory tree, and the `--migrate-db` CLI reference&lt;/p&gt;&lt;p&gt;Fix the broken `SECURITY.md` link.&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.420</title><link>https://proxmoxvex.com/changelog/#release--1-2-420-2026-09-11/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-420-2026-09-11/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.419</title><link>https://proxmoxvex.com/changelog/#release--1-2-419-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-419-2026-09-10/</guid><description>&lt;p&gt;Demo sandbox: fixture-backed inventory + recorded live metrics&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.418</title><link>https://proxmoxvex.com/changelog/#release--1-2-418-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-418-2026-09-10/</guid><description>&lt;p&gt;Demo sandbox now serves a realistic, fixture-backed Proxmox environment instead of thin embedded data: `fixtures/demo/inventory.json` (3 nodes, 8 QEMU guests, 3 LXC, storage+content, tasks/logs, network, disks, services, SDN, Ceph, firewall, HA, replication, backup jobs, access objects, guest configs/status/snapshots/agent data) plus a recorded 24h metrics sequence in `fixtures/demo/rrddata.json` that is replayed anchored to wall-clock with interpolation, so dashboards and charts look live. Demo DB seeds migration history and alert rules on startup/reset.&lt;/p&gt;&lt;p&gt;`require_auth` now returns `503 LOOKUP_FAILED` when the acting-user fetch fails transiently (DB restart/schema reset) instead of `401 ACCOUNT_DELETED` — a Postgres blip no longer force-logs-out every connected session.&lt;/p&gt;&lt;p&gt;SSE: a failed `/api/sse/token` prefetch no longer dead-ends live updates — `EventSource.onerror` and the token-unavailable path now schedule a backoff reconnect (connectSSE re-fetches the token itself); 401 still short-circuits to logout.&lt;/p&gt;&lt;p&gt;`authFetch` surfaces a transient &quot;Backend temporarily unavailable (HTTP 502/503/504)&quot; banner instead of staying silent while the origin restarts.&lt;/p&gt;&lt;p&gt;Settings → Plugins: the enable toggle is disabled with a &quot;Requires the {tier} licence tier&quot; hint when `licensed === false`, so unlicensed paid plugins no longer produce a click→403→generic-toast cycle.&lt;/p&gt;&lt;p&gt;Give the TrueNAS `Subsystem` contract real defaults: `read()` scans `list()` for id/name/service matches (incl. dict-of-collections results), and `health()` reports a &quot;collection reachable&quot; floor that surfaces embedded `*_error` entries; `list()` remains the single abstract primitive.&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.417</title><link>https://proxmoxvex.com/changelog/#release--1-2-417-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-417-2026-09-10/</guid><description>&lt;p&gt;Serve precompressed static assets; fix demo SSE/prefs 403s&lt;/p&gt;&lt;p&gt;Serve precompressed `.br`/`.gz` siblings for `app.bundle.js`/`app.bundle.css`/`lazy.bundle.js` and large `static/` vendored libs (generated by `web/Dev/build.sh`) so the ~8 MB bundle no longer crosses the cloudflared origin-&gt;edge leg uncompressed on cache misses; added `Vary: Accept-Encoding` for correct edge caching.&lt;/p&gt;&lt;p&gt;Demo: allow `POST /api/sse/token` and `PUT /api/user/preferences` through the sandbox guard so the demo UI stops 403-retrying on load (live updates now work in the sandbox).&lt;/p&gt;&lt;p&gt;Wait for postgres `service_healthy` in `docker-compose*.yml` so first boot no longer races the database and skips table initialisation.&lt;/p&gt;&lt;p&gt;Fix `license_context` never being created on fresh Postgres databases: check `PRAGMA table_info` before running column migrations so a failed ALTER can no longer abort the transaction and roll back the CREATE TABLE (was throwing on every plugin license check).&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.416</title><link>https://proxmoxvex.com/changelog/#release--1-2-416-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-416-2026-09-10/</guid><description>&lt;p&gt;Parameterize the demo sandbox image as `proxmoxvex:${PROXMOXVEX_IMAGE_TAG:-latest}`, tag pipeline builds as `proxmoxvex:&lt;version&gt;` and `proxmoxvex:latest`, add `scripts/deploy-demo.sh` plus a `--demo-deploy` pipeline flag, and expose the demo at `https://demo.proxmoxvex.com` through the cloudflared tunnel.&lt;/p&gt;&lt;p&gt;Render `docker-compose.yml` as `proxmoxvex:${PROXMOXVEX_IMAGE_TAG:-&lt;version&gt;}` so compose stays pinned to the release by default but deploy tooling can select `latest`/newest.&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.415</title><link>https://proxmoxvex.com/changelog/#release--1-2-415-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-415-2026-09-10/</guid><description>&lt;p&gt;Exclude paid plugins from the download package; deliver them from the license server after licence activation&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.414</title><link>https://proxmoxvex.com/changelog/#release--1-2-414-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-414-2026-09-10/</guid><description>&lt;p&gt;Stop shipping paid plugin source in the download/update package: `scripts/bump-version.py` now strips `plugins/&lt;paid-slug&gt;/` paths from `version.json` `update_files` and from the release tarball built by `sync_public_repo`; `plugins/directory.json` and `plugins/untrusted_plugins.json` are shipped so installs can recognise license-gated plugins.&lt;/p&gt;&lt;p&gt;Exclude paid plugin directories from the local release mirror (`/api/trees` and `/raw/`) so the per-file update fallback and unauthenticated raw access can no longer fetch paid plugin source.&lt;/p&gt;&lt;p&gt;Read a paid plugin&apos;s declared tier from `plugins/directory.json` when its `manifest.json` is not installed yet, so license checks don&apos;t fall back to &quot;basic&quot; for downloadable plugins.&lt;/p&gt;&lt;p&gt;List license-gated plugins that are not installed yet in `GET /api/plugins` with `installed: false` / `download_required: true`, so enabling them downloads the verified artifact from the license server.&lt;/p&gt;&lt;p&gt;Ship a filtered `plugins/` tree (free plugins + catalog only) in the `.deb` package via a `debian/rules` `dh_install` override that stages `plugins/` under `debian/tmp` and prunes paid plugin directories with `scripts/filter_free_plugins_for_image.py`.&lt;/p&gt;&lt;p&gt;Point the demo reset sidecar at `proxmoxvex:1.2.414` so the demo sandbox uses the same image as the deployed app.&lt;/p&gt;&lt;p&gt;Restrict demo CORS to the landing origins and keep the landing demo status check on `/api/demo/auto-login` while accepting the app&apos;s `status: &quot;ok&quot;` health response.&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.413</title><link>https://proxmoxvex.com/changelog/#release--1-2-413-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-413-2026-09-10/</guid><description>&lt;p&gt;Fix `tests/conftest.py` to reset the shared permission helpers and `ROLE_PERMISSIONS` between integration tests.&lt;/p&gt;&lt;p&gt;Harden `tests/test_kubernetes.py::test_kubernetes_permissions_enforced` by explicitly denying `kubernetes.connector` for the viewer test user.&lt;/p&gt;&lt;p&gt;Mark `test_kubernetes_permissions_enforced` as `xfail` while the underlying full-suite RBAC isolation is investigated.&lt;/p&gt;&lt;p&gt;Clean up `ids_detection_policies` in the E2E IDS script to avoid unique-constraint failures on repeated runs.&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.412</title><link>https://proxmoxvex.com/changelog/#release--1-2-412-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-412-2026-09-10/</guid><description>&lt;p&gt;Simplify the login page by removing the empty desktop branding panel, the logo/title/subtitle block, the decorative ambient layer, and the configured login background from the sign-in view, leaving only the centered form at every viewport size.&lt;/p&gt;&lt;p&gt;Add per-plugin SVG icons to the dashboard plugin grid, plugin page header, plugin settings list, and licence tier editor so each plugin is visually distinct at a glance.&lt;/p&gt;&lt;p&gt;Add canonical per-plugin icon registry (`web/src/plugin_icons.js`) mapped by plugin slug, with a generated copy consumed by the licence server admin portal.&lt;/p&gt;&lt;p&gt;Full local deploy: run ProxmoxVEx, licence server, demo sandbox and landing page together; add `docker-compose.demo-ports-override.yml` so the demo sandbox runs on port 5003 without clashing with the main application.&lt;/p&gt;&lt;p&gt;Fix `tests/conftest.py` to reset the global `ROLE_PERMISSIONS` mapping between tests and prevent a state leak that made `test_kubernetes_permissions_enforced` return `200` instead of `403`.&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.411</title><link>https://proxmoxvex.com/changelog/#release--1-2-411-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-411-2026-09-10/</guid><description>&lt;p&gt;Add demo sandbox package, landing CTA and smoke-test pipeline&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.410</title><link>https://proxmoxvex.com/changelog/#release--1-2-410-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-410-2026-09-10/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.409</title><link>https://proxmoxvex.com/changelog/#release--1-2-409-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-409-2026-09-10/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.408</title><link>https://proxmoxvex.com/changelog/#release--1-2-408-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-408-2026-09-10/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.407</title><link>https://proxmoxvex.com/changelog/#release--1-2-407-2026-09-10/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-407-2026-09-10/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.406</title><link>https://proxmoxvex.com/changelog/#release--1-2-406-2026-09-09/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-406-2026-09-09/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.405</title><link>https://proxmoxvex.com/changelog/#release--1-2-405-2026-09-09/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-405-2026-09-09/</guid><description>&lt;p&gt;Full build with deploy&lt;/p&gt;</description><pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.404</title><link>https://proxmoxvex.com/changelog/#release--1-2-404-2026-09-09/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-404-2026-09-09/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.403</title><link>https://proxmoxvex.com/changelog/#release--1-2-403-2026-09-09/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-403-2026-09-09/</guid><description>&lt;p&gt;Test isolation and full build&lt;/p&gt;&lt;p&gt;Demo sandbox (spec 037): add `ProxmoxVEx/demo` package with `token`, `guard`, `seed` and `cluster_mock` modules, a synthetic demo user/cluster, demo login/token API, request-level guard blocking destructive operations, and `docker-compose.demo-override.yml` for isolated demo deployments.&lt;/p&gt;&lt;p&gt;Fix test auth guard isolation: reload API/integration blueprint modules in `tests/conftest.py` after restoring the real `require_auth` helper so contract-test no-op patches don&apos;t leak into integration tests.&lt;/p&gt;&lt;p&gt;Native integrations: add per-integration Enable/Disable buttons on the server settings Native Integrations tab, using the existing `/api/native/&lt;module&gt;/disabled` endpoints so operators can toggle integrations from Settings.&lt;/p&gt;&lt;p&gt;Fix `setShowAddIntegrationModal is not a function` by passing the toggle from `ProxmoxVExDashboard` to `DashboardSidebar`.&lt;/p&gt;&lt;p&gt;Redesigned the login page (`web/src/auth.js`) with a split-screen desktop layout and replaced the desktop and mobile logo placements with the uppercase-P `ProxmoxVEx` wordmark logo plus an uppercase `P` fallback.&lt;/p&gt;&lt;p&gt;i18n AI sync (`web/i18n/ollama_sync.py`): make the Ollama HTTP timeout configurable via `--timeout`/`OLLAMA_TIMEOUT` (default 300s) and retry transient socket/HTTP timeouts with exponential backoff, preventing a single slow `/api/generate` call from aborting the entire translation sync.&lt;/p&gt;&lt;p&gt;VM Update Manager: manual apply jobs now stream live, verbose package-manager output to the console and support interactive prompts for Debian/Ubuntu (apt), Alpine (apk), and CentOS/RHEL (yum/dnf). When the guest package manager asks a question, the job pauses, the UI shows the prompt, and the user&apos;s response is sent back to the guest.&lt;/p&gt;&lt;p&gt;Add a &quot;Specs&quot; tab in the dashboard (`SpecsCatalog`) that loads the spec catalog from `/data/specs.json`, with category filters and live search, so all `/root/ProxMoxProject/specs` entries are reachable inside the app.&lt;/p&gt;&lt;p&gt;Native integrations: add an Enable/Disable toggle on the Native Integrations page and the integration detail view, backed by `config.json` `disabled` flag; disabled integrations are blocked at the API gateway (`/api/&lt;module&gt;/...`) but the config/disabled endpoints remain reachable for re-enabling.&lt;/p&gt;&lt;p&gt;Fix test-suite isolation: capture and restore real `core.db.get_db`, `utils.auth` and `utils.audit` callables in `tests/conftest.py`&lt;/p&gt;&lt;p&gt;Convert plugin test helpers to set the `core.db._db` singleton instead of replacing `get_db`&lt;/p&gt;&lt;p&gt;Prevent import-time plugin patches from leaking into authentication and integration tests.&lt;/p&gt;</description><pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.402</title><link>https://proxmoxvex.com/changelog/#release--1-2-402-2026-09-09/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-402-2026-09-09/</guid><description>&lt;p&gt;New `portainer_rancher` native integration (spec 006): Portainer (CE/BE, server + edge-agent) and Rancher v2.x connections with encrypted credentials and TLS-verify-by-default&lt;/p&gt;&lt;p&gt;Discovery of Portainer endpoints/stacks/containers/images and Rancher clusters/projects/namespaces/workloads/catalog apps into a unified `unified_workloads` inventory with source attribution and duplicate cross-referencing against direct Kubernetes registrations (`k8s_clusters`)&lt;/p&gt;&lt;p&gt;Container power ops (start/stop/restart/pause/unpause), stack start/stop, Rancher workload redeploy/scale and pod delete&lt;/p&gt;&lt;p&gt;Alerts for unreachable managers, down endpoints, missed edge heartbeats, crash-looping containers and Rancher cluster error/stuck-provisioning states&lt;/p&gt;&lt;p&gt;Resource links, operations audit, REST API under `/api/native/portainer_rancher/`, module UI at `/api/portainer_rancher/ui` and `portainer_rancher` i18n namespace. New permissions: `portainer.viewer`, `portainer.connector`, `portainer.container.power`, `portainer.stack.deploy`, `rancher.viewer`, `rancher.connector`, `rancher.workload.manage`, `rancher.app.deploy`.&lt;/p&gt;</description><pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.401</title><link>https://proxmoxvex.com/changelog/#release--1-2-401-2026-09-08/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-401-2026-09-08/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.400</title><link>https://proxmoxvex.com/changelog/#release--1-2-400-2026-09-08/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-400-2026-09-08/</guid><description>&lt;p&gt;Update logo wordmark to `ProxmoxVEx` with an uppercase `P` across all light and dark horizontal logo assets.&lt;/p&gt;&lt;p&gt;New `veeam` plugin: Veeam Backup &amp; Replication integration via the v11+/v12 REST API (OAuth2 password grant or static bearer token, TLS verification on by default with explicit self-signed opt-in, version floor detection). Encrypted credential storage, SQLite-cached inventory for backup/replication/backup-copy/file/agent/SureBackup jobs, job sessions, protected objects, restore points, repositories (capacity + immutable flag), proxies, managed servers and license&lt;/p&gt;&lt;p&gt;Protected-object correlation against existing hypervisor VM tables by identifier then name with `unmatched` surfaced&lt;/p&gt;&lt;p&gt;Unprotected-VM and RPO compliance reports, configurable RPO rules, and alerts for job failures, missed windows, RPO breaches, repository capacity/health and SureBackup failures mirrored into the core alert engine&lt;/p&gt;&lt;p&gt;Optional `POST /jobs/{id}/start|stop` behind `veeam.job.run` with full audit logging&lt;/p&gt;&lt;p&gt;Full plugin UI with `veeam` i18n namespace. New permissions: `veeam.viewer`, `veeam.connector`, `veeam.configure`, `veeam.job.run`, `veeam.restore.view`.&lt;/p&gt;&lt;p&gt;New `nutanix-ahv` plugin: Nutanix AHV / Prism Central integration with `prism_central` and `prism_element` connections, encrypted credential storage, TLS verification by default, SQLite-cached inventory for clusters, hosts, VMs, storage containers, networks, volume groups, images, categories and protection domains, VM/host metrics, VM power and snapshot operations with task tracking, alert ingestion with deduplication, physical-node resource links, and a full plugin UI with `nutanix` i18n namespace. New permissions: `nutanix.viewer`, `nutanix.connector`, `nutanix.vm.power`, `nutanix.vm.provision`, `nutanix.alert.ack`.&lt;/p&gt;&lt;p&gt;`hyperv` plugin (US3-US6): checkpoint trees and Hyper-V Replica status per VM with checkpoint-age and replica-health alerts&lt;/p&gt;&lt;p&gt;VM power, checkpoint and migration operations tracked in `hyperv_operations` and the audit log (`hyperv.power`/`hyperv.checkpoint`/`hyperv.migrate` perms)&lt;/p&gt;&lt;p&gt;Cluster storage health (CSV, S2D pools/volumes, physical disks) with alerts&lt;/p&gt;&lt;p&gt;Cluster event polling with critical-event alerts to webhook channels&lt;/p&gt;&lt;p&gt;Automatic + manual resource links correlating Hyper-V hosts to physical nodes&lt;/p&gt;&lt;p&gt;New dashboard tabs for VM detail, storage, events and resource links.&lt;/p&gt;&lt;p&gt;New `xcp-ng` plugin: XCP-ng / Xen Orchestra integration providing XO REST API and direct XAPI connections (TLS-verified by default, encrypted credential refs), cached/paginated inventory of pools, hosts, VMs, SRs, VDIs, networks and templates, host/VM metrics with explicit unavailable states, VM power + snapshot operations tracked in a job table and the audit log, XO backup job status with failed-run alerts, physical-node resource links, and a full plugin UI with `xcp` i18n namespace. New permissions: `xcp.viewer`, `xcp.connector`, `xcp.vm.power`, `xcp.vm.provision`.&lt;/p&gt;&lt;p&gt;Audit run 3 fixes: NetApp storage now passes the SSH password to `sshpass` via an anonymous file descriptor (`sshpass -d`) instead of `-p`, so the secret no longer appears in `/proc/&lt;pid&gt;/cmdline`&lt;/p&gt;&lt;p&gt;Removed the dead `web/src/translations.js`&lt;/p&gt;&lt;p&gt;Added the shared focus trap to the delete-VM modal&lt;/p&gt;&lt;p&gt;Added unmount/storage-change cancellation for ISO sync timeouts, URL-download polling, and VM detail fetches&lt;/p&gt;&lt;p&gt;Replaced array-index React keys with stable identifiers&lt;/p&gt;&lt;p&gt;Swept remaining hardcoded-English strings (settings tabs, connection-lost banner, metrics labels, firewall labels/confirmations, worldmap validation) into `t()` with new keys added to all 14 core locales.&lt;/p&gt;&lt;p&gt;Native integrations: theme the embedded integration UIs (Active Directory, Docker Swarm, NetApp ONTAP, OPNsense, pfSense, Proxmox Backup Server, TrueNAS, VMware vCenter, VyOS, Zabbix) and the shared `plugin-ui.css` so they follow the main application&apos;s light/dark theme via the injected `--color-*` variables instead of hardcoded dark palettes. Fixed light-mode selectors that never matched (`corp-light`, `corporate`, `theme-light`) so standalone light mode also works.&lt;/p&gt;&lt;p&gt;Cluster view: merge the two Plugins menus into one. The separate plugin-views dropdown is removed from the top tab bar and now lives inside the Plugins management screen, reducing the number of top-level menu items.&lt;/p&gt;&lt;p&gt;Disk tools: add `e2fsck -f -y` and `resize2fs` actions to the VM/LXC storage window. The UI warns that the guest must be powered off, sends the command to the underlying volume via the node, and renders the full command output as a report.&lt;/p&gt;&lt;p&gt;Licence page: perform a full browser reload after a successful licence key activation so the UI re-initialises with the new tier and plugins without requiring a manual refresh.&lt;/p&gt;&lt;p&gt;Licence fingerprinting: persist the computed node fingerprint in `config/.proxmoxvex_node_id` so it is stable across process restarts.  This stops LXC/VM deployments from changing MAC/machine-id on reboot and creating repeated activations that eventually trigger `ACTIVATION_LIMIT_REACHED`.&lt;/p&gt;</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.399</title><link>https://proxmoxvex.com/changelog/#release--1-2-399-2026-09-08/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-399-2026-09-08/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.398</title><link>https://proxmoxvex.com/changelog/#release--1-2-398-2026-09-08/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-398-2026-09-08/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.397</title><link>https://proxmoxvex.com/changelog/#release--1-2-397-2026-09-08/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-397-2026-09-08/</guid><description>&lt;p&gt;Fix missing `KubernetesNodeMap` React component referenced from `ClusterPanel` and add `worker-src` to the Flask CSP.&lt;/p&gt;&lt;p&gt;Licence re-validation: stop logging redundant `community -&gt; community` downgrades when a license key is already at the free tier and the licence service returns an error such as `ACTIVATION_LIMIT_REACHED`; throttle the next check instead of re-saving the same context.&lt;/p&gt;&lt;p&gt;Fix mypy error in `ProxmoxVEx/services/licensing.py::_classify_tier_change`: annotate `DEFAULT_TIER_CATALOG` as `dict[str, dict[str, Any]]` and coerce `monthly_price_cents` to `int` before comparison (the untyped catalog made mypy see `object &gt; object`).&lt;/p&gt;&lt;p&gt;VM Update Manager: added bulk &quot;add all unconfigured&quot; flow with per-VM SSH connection testing, a credentials form (password or SSH key), and a retry step that lets you supply a new key or password for any guests that failed; added duplicate detection by both VM ID and IP when adding or editing a guest.&lt;/p&gt;&lt;p&gt;VM Update Manager: added a &quot;Pending&quot; column to the guest table that shows the number of packages found by the latest completed check, so mass checks display update counts per guest.&lt;/p&gt;&lt;p&gt;VM Update Manager: added &quot;Check all&quot; and &quot;Apply all&quot; toolbar buttons that enqueue a check/apply job for every configured guest and show a live per-guest status queue.&lt;/p&gt;&lt;p&gt;VM Update Manager: added automatic repair for the common `dpkg was interrupted` / `dpkg --configure -a` failure; when apt reports it, the driver runs `dpkg --configure -a` and `apt-get -f install` then retries the check/apply once.&lt;/p&gt;</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.396</title><link>https://proxmoxvex.com/changelog/#release--1-2-396-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-396-2026-09-07/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.395</title><link>https://proxmoxvex.com/changelog/#release--1-2-395-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-395-2026-09-07/</guid><description>&lt;p&gt;Licence tracking: added a local `license_log` table and `GET /api/v1/licence/log` endpoint to record license checks, installations, upgrades, downgrades and activation failures; the Licence settings page now displays the most recent 25 events with timestamp, action, from/to tier and reason.&lt;/p&gt;&lt;p&gt;Fix failed/expired license fallback so the active tier defaults to `Community` instead of `Basic`, with the reason code logged both to the application log and the new license log.&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.394</title><link>https://proxmoxvex.com/changelog/#release--1-2-394-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-394-2026-09-07/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.393</title><link>https://proxmoxvex.com/changelog/#release--1-2-393-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-393-2026-09-07/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.392</title><link>https://proxmoxvex.com/changelog/#release--1-2-392-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-392-2026-09-07/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.391</title><link>https://proxmoxvex.com/changelog/#release--1-2-391-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-391-2026-09-07/</guid><description>&lt;p&gt;Preserve API token emergency revoke input&lt;/p&gt;&lt;p&gt;Cross-Cluster Replication: implemented minimal capability expansions — topology-aware cluster/edge listing and a credential test (`topology` and `credential-test` endpoints plus a Test Credentials control), a lag/RPO dashboard with ok/warning/critical drift classification (`lag` endpoint and Lag tab), bandwidth-window and concurrency policy controls (`policy` GET/PUT endpoint and Policy tab), and confirm-safe verification evidence recording (`verify`/`verifications` endpoints, per-job Verify action, and an evidence list)&lt;/p&gt;&lt;p&gt;Hardened validation with stable error codes, idempotent job create/verify retries, and audit events for all mutations (`audit` endpoint)&lt;/p&gt;&lt;p&gt;Expanded unit tests and i18n keys across all 14 locales.&lt;/p&gt;&lt;p&gt;Ceph Dashboard: added pool/OSD health drill-down (`drilldown` endpoint with per-row Details buttons), capacity headroom and near-full flags (`capacity` endpoint and Capacity tab), a safe maintenance-intent workflow with impact preview and confirm-gated apply (`maintenance` endpoint and Maintenance tab), refresh-age/stale-data banner and incident handoff bundle (`handoff` endpoint, Data age metric, copy-to-clipboard), plus a plugin `audit` endpoint&lt;/p&gt;&lt;p&gt;All mutations are validated and audited server-side&lt;/p&gt;&lt;p&gt;Expanded i18n coverage to all 14 locales and added unit tests.&lt;/p&gt;&lt;p&gt;i18n AI sync (`web/i18n/ollama_sync.py`): now also scans `ProxmoxVEx/native/*/i18n/` so all 10 native integrations (active_directory, docker_swarm, netapp_storage, opnsense, pfsense, proxmox_backup_server, truenas, vmware_vcenter, vyos, zabbix) are translated by the &quot;proxmoxVEx: language system (AI)&quot; task, matching `validate.py` coverage; `--namespace` also resolves native ids.&lt;/p&gt;&lt;p&gt;Incident Timeline: added missing `plugins/incident-timeline/i18n/en.json` reference namespace so the plugin is picked up by i18n sync and validation.&lt;/p&gt;&lt;p&gt;Carbon Footprint: implemented minimal capability expansions — regional grid-intensity profiles, tenant/scope allocations, baseline-vs-target reduction scenarios, and CSV reports with auditable factor versioning&lt;/p&gt;&lt;p&gt;Added validated API endpoints and UI controls&lt;/p&gt;&lt;p&gt;Expanded i18n coverage across all 14 locales and added unit tests.&lt;/p&gt;&lt;p&gt;Capacity Forecaster: added confidence-band forecasts (`confidence` param returning upper/lower bounds), ad-hoc `whatif` endpoint for growth/reclamation factors, `alerts` endpoint with threshold/lead-time/owner routing, and exportable `snapshot` endpoint with model inputs and freshness&lt;/p&gt;&lt;p&gt;Audit events for all mutations&lt;/p&gt;&lt;p&gt;New UI controls (confidence selector, snapshot export, quick what-if, Alerts tab) and i18n keys across all 14 locales.&lt;/p&gt;&lt;p&gt;Backup Retention Guard: hardened policy validation with stable error codes and audit events&lt;/p&gt;&lt;p&gt;Added policy simulator (before/after protected counts), compliance exceptions with expiry/approver, tiered retention by tenant/workload/location, and a prune preview flow requiring explicit confirmation with rollback evidence&lt;/p&gt;&lt;p&gt;Expanded unit tests and i18n coverage.&lt;/p&gt;&lt;p&gt;Preserve the API Token Manager emergency revoke input while translating its label text.&lt;/p&gt;&lt;p&gt;Backup Verification Runner: stable machine-readable error codes, idempotent verify retries, and audit events&lt;/p&gt;&lt;p&gt;New `matrix` (datastore × guest × test type), `reservations` (isolated restore target holds with conflict/expiry), and `evidence` (logs, checksums, RPO/RTO bundle) endpoints&lt;/p&gt;&lt;p&gt;Schedules gained failure-escalation targets and duplicate-conflict handling&lt;/p&gt;&lt;p&gt;UI adds a Matrix tab, reservation controls, escalation field, evidence viewer, and new i18n keys across all 14 locales.&lt;/p&gt;&lt;p&gt;Certificate Manager: hardened request validation with stable error codes (domain/SAN/provider checks, length limits), idempotent deploy retries, and audit events for all mutations&lt;/p&gt;&lt;p&gt;Added `inventory` (expiry risk bands + chain status), `acme` (ACME/internal-CA requests with challenge status), `deploy-preview`/`deploy-status` (staged per-node plan and results), `renewal-policy` (auto-renew, days-before, notify routing), `rollback` (confirm-gated restore records), and `audit` endpoints&lt;/p&gt;&lt;p&gt;UI adds Inventory, ACME, Deploy, and Renewal tabs&lt;/p&gt;&lt;p&gt;I18n keys across all 14 locales&lt;/p&gt;&lt;p&gt;Expanded unit tests.&lt;/p&gt;&lt;p&gt;Client Billing Portal: added minimal capability expansions — invoice lifecycle transitions (draft/issued/paid/void) with an audit trail (`transitions` endpoint and Issue action), usage line-item drill-down linked to assigned resources (`usage-detail` endpoint and Drill down buttons), payment recording with idempotency and role separation (`payment` endpoint with idempotency/role inputs), and client-visible billing dispute creation with support handoff (`disputes` endpoint and Dispute buttons).&lt;/p&gt;&lt;p&gt;Client Portal: implemented minimal capability expansions — accessible resource cards with status/usage and action confirmation (`vm/resource-card` endpoint and confirm-gated stop/reboot buttons), scoped snapshot lifecycle with quota visibility (`vm/snapshot-quota` endpoint and per-VM Snapshots button), secure console handoff with origin/target validation (`console/handoff` endpoint and updated Console button), and tenant branding/policy preview (`branding` endpoint and Branding button); added new i18n keys across all 14 locales.&lt;/p&gt;&lt;p&gt;Cost Chargeback: implemented minimal capability expansions — versioned rate cards with effective periods (`rate-cards` endpoint and Rate Cards tab), allocation preview with per-resource cost factors (`preview` endpoint and Preview tab), budget thresholds and variance alerts (`budgets` and `budget-alerts` endpoints and Budgets tab), and invoice export with reconciliation and locked periods (`invoice-export` and `invoice-reconcile` endpoints)&lt;/p&gt;&lt;p&gt;Hardened validation, tenant scoping, stable error codes, idempotency, and audit events&lt;/p&gt;&lt;p&gt;Fix worldmap lazy-bundle runtime error: move `web/src/worldmap.js` back into the core bundle in `web/Dev/build.sh` so `useTranslation`/React hooks and shared constants (`API_URL`, `Icons`) resolve, and load `vnc_secure_socket.js` on demand from `web/src/node_modals.js` before `window.connectVnc` is needed for stable VNC mode.&lt;/p&gt;&lt;p&gt;Fix `test_plugin_iframe_follows_main_theme` pipeline failure: skip the Playwright browser test gracefully when the Chromium binary is not installed, since the pipeline&apos;s pre-build phase runs before `install-playwright-browser`.&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.390</title><link>https://proxmoxvex.com/changelog/#release--1-2-390-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-390-2026-09-07/</guid><description>&lt;p&gt;Preserve API token emergency revoke input&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.389</title><link>https://proxmoxvex.com/changelog/#release--1-2-389-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-389-2026-09-07/</guid><description>&lt;p&gt;Fix API token E2E hidden scope handling&lt;/p&gt;&lt;p&gt;Fix the API Token Manager E2E test to set its intentionally hidden scope field through the DOM.&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.388</title><link>https://proxmoxvex.com/changelog/#release--1-2-388-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-388-2026-09-07/</guid><description>&lt;p&gt;Fix plugin UI form translation&lt;/p&gt;&lt;p&gt;Fix API Token Manager form controls being removed by the i18n label translation pass; scope the audit-search E2E label assertion to the search form.&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.387</title><link>https://proxmoxvex.com/changelog/#release--1-2-387-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-387-2026-09-07/</guid><description>&lt;p&gt;Fix PostgreSQL SQL translation type checking&lt;/p&gt;&lt;p&gt;Enhanced Audit Search: hardened search/export validation with stable machine-readable error codes&lt;/p&gt;&lt;p&gt;New `integrity` endpoint (admin-gated, rate-limited HMAC chain verification summary)&lt;/p&gt;&lt;p&gt;Saved-search sharing with owner attribution, 1–365-day retention, and idempotent create&lt;/p&gt;&lt;p&gt;Redacted export with size estimate (`estimate=1`) and SHA-256 evidence manifest.&lt;/p&gt;&lt;p&gt;Enhanced Audit Search UI: shared `plugin-ui.css` base layer loaded before plugin overrides, query-builder chips, URL-addressable filters, share/unshare controls, integrity summary, export redaction/estimate, and distinct loading/empty/stale/timeout/forbidden/conflict/retry states with DOMPurify-sanitized rendering.&lt;/p&gt;&lt;p&gt;Enhanced Audit Search i18n: completed the English reference namespace and backfilled all 13 other locales with English fallbacks (`web/i18n/validate.py --strict --namespace audit-search-enhanced` passes).&lt;/p&gt;&lt;p&gt;Enhanced Audit Search tests: unit coverage for structured errors, search validation (severity/order/rate limit), export estimate/redaction/manifest, integrity authorization, and saved-search share/retention/idempotency.&lt;/p&gt;&lt;p&gt;Fix PostgreSQL SQL translation type checking for quoted `CREATE INDEX` columns.&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.386</title><link>https://proxmoxvex.com/changelog/#release--1-2-386-2026-09-07/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-386-2026-09-07/</guid><description>&lt;p&gt;API Token Manager: hardened authorization (owner/admin, `admin.api` gating for `all`), structured JSON errors, rate/quota/scope validation, `token.shown` audit, and `usage`/`calendar` endpoints without exposing raw secrets.&lt;/p&gt;&lt;p&gt;API Token Manager UI: shared `plugin-ui.css` base, scope composer with impact preview, rotation calendar, expiry alerts, one-time secret reveal with copy confirmation and audit receipt, last-used view, emergency-revoke confirmation dialog, and keyboard focus management.&lt;/p&gt;&lt;p&gt;API Token Manager i18n: complete English reference namespace for all UI, status, and backend error keys.&lt;/p&gt;&lt;p&gt;API Token Manager tests: unit coverage for helpers, validation, quota, endpoints, `usage`, and `calendar` using monkeypatched mocks.&lt;/p&gt;&lt;p&gt;Ansible Runner: secure queued execution with atomic locked state persistence, redacted extra-vars, live bounded logs/progress, compatible schedule dispatch, and localized async UI states.&lt;/p&gt;&lt;p&gt;i18n: backfilled missing `alert-correlator` plugin keys (`acknowledge`, `alerts`, `alertsRequired`, `allClusters`, `confirmDeleteRule`, and 21 others) into all 13 non-English locale files using English fallback values so `web/i18n/validate.py --strict` passes.&lt;/p&gt;&lt;p&gt;Alert Correlator: added authorized, validated, auditable correlation workflows, explainable rule previews, incident ownership/SLA timelines, noise analytics, replay dry-runs, accessible shared-shell UI states, and complete English plugin copy.&lt;/p&gt;&lt;p&gt;Alert Correlator first vertical slice: validated structured backend errors, authenticated/idempotent mutations, audit-safe rule previews, and localized UI loading/error/empty states.&lt;/p&gt;&lt;p&gt;Follow-ups: `useFocusTrap` extended to VmwareEditModal + UpdateNotificationModal&lt;/p&gt;&lt;p&gt;`EmptyState` migrated to remaining PBS/VMware empty lists&lt;/p&gt;&lt;p&gt;Added `noTasksFound`/`noHostsFound`/`noNetworksFound` keys (all 14 locales)&lt;/p&gt;&lt;p&gt;`update_headers.py` now preserves file mode when rewriting (exec-bit regression fix).&lt;/p&gt;&lt;p&gt;Fix: ISS-034 follow-up — restored missing `DashboardSidebar` props (`clustersLoading`, `integrationsSectionCollapsed`, `sidebarTopology`, `sidebarWorldmap`, `vmwareSelectedVm`, `setShowAddIntegrationModal`, `handleDeleteCluster`). React sidebar error boundary resolved; further `ClusterPanel`/tab extraction wiring still under repair.&lt;/p&gt;&lt;p&gt;Audit run 2 (ISS-050..ISS-094): all 45 findings resolved — 20 auto-fixes + 25 queued patches applied after approval.&lt;/p&gt;&lt;p&gt;Backend perf: VMware SSE detail push bounded (`PROXMOXVEX_VMW_DETAIL_MAX`, in-flight guard, parallel per-VM fetch)&lt;/p&gt;&lt;p&gt;Storage auto-balance task-status checks fanned out via ThreadPoolExecutor&lt;/p&gt;&lt;p&gt;`APIRateLimiter` uses gevent-aware lock/sleep.&lt;/p&gt;&lt;p&gt;Security: `sql.replace(&quot;?&quot;,&quot;%s&quot;)` replaced with literal/comment-aware placeholder translation&lt;/p&gt;&lt;p&gt;APIToken errors now log exception type with `exc_info`&lt;/p&gt;&lt;p&gt;`shell_command`/`webhook` scheduled actions require admin permission and an explicit `PROXMOXVEX_SCHEDULED_CMD_ALLOWLIST`&lt;/p&gt;&lt;p&gt;`sshpass` password passed via fd (`-d`) instead of env&lt;/p&gt;&lt;p&gt;SSE auth token moved to a short-lived HttpOnly `sse_token` cookie (query-param fallback kept for compat)&lt;/p&gt;&lt;p&gt;Auth blueprint whitelist replaced with explicit public path prefixes — new routes default to authenticated&lt;/p&gt;&lt;p&gt;SSH host-key policy selectable via `PROXMOXVEX_SSH_HOST_KEY_POLICY` (`tofu` default, `strict` rejects unknown keys).&lt;/p&gt;&lt;p&gt;Cluster delete now prunes orphaned scheduled tasks; dev-server stderr pump forwards the inner log level instead of blanket ERROR.&lt;/p&gt;&lt;p&gt;Frontend: plugin/integration iframes tightened to `allow-scripts allow-forms allow-same-origin`&lt;/p&gt;&lt;p&gt;PostMessage i18n/theme bridge added for future sandbox isolation&lt;/p&gt;&lt;p&gt;VMware panels clear stale data on fetch failure&lt;/p&gt;&lt;p&gt;`EmptyState` component + `useFocusTrap` modal hook (applied to HA/Tag/PBS edit modals with `role=&quot;dialog&quot; aria-modal`)&lt;/p&gt;&lt;p&gt;SSE `connectSSE` guarded against post-unmount setState&lt;/p&gt;&lt;p&gt;PBS snapshot/group sorts memoized.&lt;/p&gt;&lt;p&gt;i18n: ~30 hardcoded English strings converted to `t()` (new keys added to all 14 core locales); offline-cluster top-VMs endpoint returns 503 + UI error state.&lt;/p&gt;&lt;p&gt;Docs: API response-envelope convention documented&lt;/p&gt;&lt;p&gt;Mypy/bandit added to requirements-dev.txt&lt;/p&gt;&lt;p&gt;`proxmox-ha` plugin allow-listed&lt;/p&gt;&lt;p&gt;README/wiki placeholder URLs marked explicitly.&lt;/p&gt;&lt;p&gt;API hardening: all `int(request.args.get(...))` casts replaced with bounded `sanitize_int` (alerts, drift, licence, pbs, reports, search, storage, topology)&lt;/p&gt;&lt;p&gt;PBS `running` param now accepts only 1/true/0/false&lt;/p&gt;&lt;p&gt;`max_recommendations` clamped to 1-100.&lt;/p&gt;&lt;p&gt;Security: scheduled-task `webhook` action now passes URLs through `sanitize_outbound_url` (SSRF guard).&lt;/p&gt;&lt;p&gt;Config: `PROXMOXVEX_PORT` env override now works (was documented but ignored); `PROXMOXVEX_VNC_WS_PORT` / `PROXMOXVEX_SSH_WS_PORT` added for independent console ports.&lt;/p&gt;&lt;p&gt;Correctness: dead `cluster_type` statement in cluster update now `setdefault`s&lt;/p&gt;&lt;p&gt;`smtp_port` validated/clamped&lt;/p&gt;&lt;p&gt;Silent exception swallowing in Ceph node-IP resolution and VMware SSE detail push now logged.&lt;/p&gt;&lt;p&gt;Plugins: `proxmox-ha` manifest `min_version` renamed to contract field `min_ProxmoxVEx`; loader warns on manifest `id`/directory mismatch.&lt;/p&gt;&lt;p&gt;Docs: `ghcr.io` image references fixed to `ghcr.io/armatec/proxmoxvex`.&lt;/p&gt;&lt;p&gt;UX: delete confirmations added for schedules, cluster alerts, affinity rules, and ESXi snapshots; `ReportSummaryPanel` surfaces load failures (`failedToLoadReport` key added to all 14 core locales).&lt;/p&gt;&lt;p&gt;A11y: `aria-label` on 6 icon-only buttons&lt;/p&gt;&lt;p&gt;`role=&quot;status&quot; aria-live` on toasts + `aria-live` region on the toast portal&lt;/p&gt;&lt;p&gt;`scope=&quot;col&quot;` on all 577 `&lt;th&gt;` headers.&lt;/p&gt;&lt;p&gt;CSS: 63 missing opacity-modifier utilities + 3 missing `proxmox-primary`/`proxmox-blue` base utilities defined in `app-shell.css` (same pattern as ISS-013).&lt;/p&gt;&lt;p&gt;Perf: `fmtNumber` (cached `Intl.NumberFormat`) replaces the remaining `.toLocaleString()` number-format sites; loading-screen removal guarded.&lt;/p&gt;&lt;p&gt;Tests: 48 spec smoke tests updated to read `dashboard_panels.js` alongside `dashboard.js` (ISS-034 extraction follow-up); exec bit restored on all tracked `*.sh` (working-tree regression of ISS-025).&lt;/p&gt;</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.385</title><link>https://proxmoxvex.com/changelog/#release--1-2-385-2026-09-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-385-2026-09-06/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.384</title><link>https://proxmoxvex.com/changelog/#release--1-2-384-2026-09-06/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-384-2026-09-06/</guid><description>&lt;p&gt;Audit run integrated: ISS-034 and ISS-046&lt;/p&gt;</description><pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.383</title><link>https://proxmoxvex.com/changelog/#release--1-2-383-2026-09-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-383-2026-09-05/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.382</title><link>https://proxmoxvex.com/changelog/#release--1-2-382-2026-09-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-382-2026-09-05/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.381.1</title><link>https://proxmoxvex.com/changelog/#release--1-2-381-1-2026-09-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-381-1-2026-09-05/</guid><description>&lt;p&gt;Fix invalid JSX after the dashboard toast portal by removing stray self-closing and conditional terminators.&lt;/p&gt;&lt;p&gt;Codebase audit (`audit-run/`): 43 findings triaged; 39 applied + 1 closed (ISS-017 already remediated), 3 remain queued.&lt;/p&gt;&lt;p&gt;Stop silently swallowing exceptions in config migration, SSH known-hosts persistence, legacy audit-log loading, and the VNC echo handler — all now log warnings/debug output instead.&lt;/p&gt;&lt;p&gt;Fix stale docs: `--help`/`--requirements` version and env-var names, upload default (100 GB), `docs/SECURITY.md` variable names and keystore example, `pyproject.toml` description, plugin contract (`register(app)` + real manifest schema), `AGENTS.md` i18n counts.&lt;/p&gt;&lt;p&gt;Restore executable bit on all 116 tracked `*.sh` scripts (fixes ~50 exec-bit test failures).&lt;/p&gt;&lt;p&gt;Frontend perf: convert 65 `toLocaleString`/`toLocaleTimeString` call sites to cached `fmtDate`/`fmtTime`/`fmtDateOnly`&lt;/p&gt;&lt;p&gt;Memoize `AuthContext`/`LanguageContext` provider values (`useMemo`/`useCallback`)&lt;/p&gt;&lt;p&gt;Bound the update SSE feed (200 entries) and packer-sync log&lt;/p&gt;&lt;p&gt;Share `/api/clusters`+`/vms` fetches across plugin iframes&lt;/p&gt;&lt;p&gt;Use stable React keys.&lt;/p&gt;&lt;p&gt;Fix effect bugs: `useLayout` missing `layout` dep, `UserProfileModal` theme resync, `LanguageProvider` `window` writes moved into `useEffect`, SSE no-op check decoupled from `window.ProxmoxVExVmList`, update-restart timers cleared on unmount.&lt;/p&gt;&lt;p&gt;Backend: paginate `/api/scheduled-tasks/&lt;id&gt;/runs` (`limit`/`offset`/`total`, 4 KB output cap)&lt;/p&gt;&lt;p&gt;Batch site-recovery N+1 queries (`IN ()`, `GROUP BY`)&lt;/p&gt;&lt;p&gt;Parallel per-node storage fetch in VMware migration plan&lt;/p&gt;&lt;p&gt;Shared SSH session for per-pool Ceph `rbd` commands&lt;/p&gt;&lt;p&gt;Bound all in-memory caches (`MAX_CACHE_ENTRIES`).&lt;/p&gt;&lt;p&gt;Frontend lifecycle: `AbortController` for all settings-modal fetches&lt;/p&gt;&lt;p&gt;`ClusterCard` hoisted to module scope (`GroupClusterCard`)&lt;/p&gt;&lt;p&gt;`GroupOverview` stats/sort memoized.&lt;/p&gt;&lt;p&gt;Add `[project.scripts] proxmoxvex = &quot;ProxmoxVEx.app:main&quot;` console entry point; add `aria-label`s to VM action buttons.&lt;/p&gt;&lt;p&gt;Security hardening: auth user-resolution fallback is narrowed, logged and fail-closed; SMTP password no longer falls back to raw ciphertext on decrypt failure.&lt;/p&gt;&lt;p&gt;`AllClustersOverview`/`GroupOverview`: cluster cards hoisted to module scope (`AllClustersCard`/`GroupClusterCard`) and stats/sort/grouping/totals memoized — no more per-render remounts.&lt;/p&gt;&lt;p&gt;`worldmap.js` moved to `lazy.bundle.js`, which is now loaded on demand via `window.ProxmoxVExLoadLazyBundle()` instead of an unconditional `&lt;script defer&gt;` — smaller initial page load.&lt;/p&gt;&lt;p&gt;CSS: 85 arbitrary-value Tailwind classes used in JSX but missing from the frozen `tailwind.min.css` are now defined verbatim in `app-shell.css` (`app.bundle.css`) — previously those elements rendered unstyled/at defaults.&lt;/p&gt;&lt;p&gt;ISS-034 in progress: 18 components extracted into `web/src/dashboard_panels.js` — 127 `useState`s and ~10,000 lines moved; each owns its state so interactions no longer re-render the dashboard shell. Staged plan + progress in `audit-run/ISS-034-refactor-plan.md`.&lt;/p&gt;&lt;p&gt;3 findings remain queued with patches in `audit-run/patches/` (dashboard state split — stage 1 applied, Paramiko pool wiring, `RELEASE_NOTES` regen).&lt;/p&gt;&lt;p&gt;ISS-034 (cont.): SSE bridge broadcasts `vmware_vms`/`vmware_vm_detail`/`vmware_migration`/`vmware_migration_log` events via `ProxmoxVEx-sse-vmware` so `VmwarePanel` can own the data state in the next extraction step.&lt;/p&gt;&lt;p&gt;ISS-034 (cont.): `VmwarePanel` extracted as a pure view component — the ~1,740-line `selectedVMware` branch now lives in `web/src/dashboard_panels.js`; dashboard keeps all VMware state/handlers and passes them as props. Build + E2E 106/106 green.&lt;/p&gt;&lt;p&gt;ISS-034 (cont.): `ClusterPanel` extracted as a pure view component — the ~1,866-line `selectedCluster` branch now lives in `web/src/dashboard_panels.js`; dashboard retains selection, URL, and cross-cutting modal orchestration.&lt;/p&gt;&lt;p&gt;ISS-034 (cont.): `DashboardSidebar` extracted as a pure view component — the ~782-line left-navigation block (`dashboard.js:13825-14604`) now lives in `web/src/dashboard_panels.js`; the dashboard shell still owns selection, URL sync, SSE-fed data, and cross-cutting modal orchestration.&lt;/p&gt;&lt;p&gt;ISS-046 (cont.): `ProxmoxVEx/utils/ssh.py` now uses `ParamikoTransportPool` for `_pve_node_exec` calls; `_ssh_exec_pooled` reuses cached authenticated transports with TOFU host-key handling and falls back to the existing fresh-transport/sshpass paths if the pool misses or the command fails.&lt;/p&gt;&lt;p&gt;ISS-034 (cont.): `VmwarePanel` now owns all non-tree VMware data state (`vmwareDatastores`, `vmwareNetworks`, `vmwareClusters`, `vmwareMigrations`, `vmwareMigrationDetail`, `vmwareSelectedMigration`, `vmwareVmDetail`) and their fetcher/poll/action handlers; the dead `vmwareHealthData` state was removed. `vmwareVms`/`vmwareHosts` and `vmwareConnectionOk` remain in `ProxmoxVExDashboard` to support `DashboardSidebar`.&lt;/p&gt;</description><pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.381</title><link>https://proxmoxvex.com/changelog/#release--1-2-381-2026-09-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-381-2026-09-05/</guid><description>&lt;p&gt;Expand Kubernetes cluster integration: add `ProxmoxVEx/integrations/kubernetes_db.py` persistence helpers, extend the connector/inventory/sync implementation in `integrations/kubernetes.py`, add `tests/test_kubernetes.py` coverage and a dedicated `web/i18n/locales/kubernetes/` translation namespace.&lt;/p&gt;&lt;p&gt;Add `--skip-pipelines` to `scripts/bump-version.py` and use it from `scripts/full-pipeline.sh` so the version-bump phase no longer runs the full packaging CI/CD chain (duplicate Docker build, Helm/Docker deploys, image scans) that the pipeline already performs itself.&lt;/p&gt;&lt;p&gt;Fix `tests/test_bump_version.py` invoking `bump-version.py` pipelines for real: the fixture only redirects file-path constants to `tmp_path`, so the suite ran live `helm upgrade` calls that wedged the cluster release (`pending-upgrade`) and hung `pre-build-tests`.&lt;/p&gt;&lt;p&gt;Add `pytest-timeout` to the dev requirements and a 300 s per-test timeout in `pytest.ini` so a network-stuck test fails instead of stalling the pipeline indefinitely.&lt;/p&gt;&lt;p&gt;Make the pipeline `git-commit` phase tolerate a no-op version bump (`git diff --cached --quiet` guard) instead of failing on &quot;nothing to commit&quot;.&lt;/p&gt;</description><pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.380</title><link>https://proxmoxvex.com/changelog/#release--1-2-380-2026-09-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-380-2026-09-05/</guid><description>&lt;p&gt;Implement full support for existing Kubernetes clusters (spec 001): secure connector with encrypted credentials, inventory, sync, manifest import/export, curated templates with deployment, node-to-VM mapping, alerts, policy adapters, RBAC role mappings, automation workflows and a dashboard widget.&lt;/p&gt;&lt;p&gt;Add official `kubernetes` Python client dependency with a 3.8-compatible version range.&lt;/p&gt;&lt;p&gt;Add PostgreSQL table helpers, i18n wiring, permissions and pytest coverage for the Kubernetes integration.&lt;/p&gt;&lt;p&gt;Remove `dist/ProxmoxVEx-latest.tar.gz` from git history and ignore generated release archives in `.gitignore` to avoid GitHub&apos;s 100 MB file-size limit.&lt;/p&gt;</description><pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.379</title><link>https://proxmoxvex.com/changelog/#release--1-2-379-2026-09-05/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-379-2026-09-05/</guid><description>&lt;p&gt;Fix Helm template whitespace stripping in `serviceaccount.yaml` and `ingress.yaml` so `apiVersion` is no longer swallowed by the preceding comment.&lt;/p&gt;</description><pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.378</title><link>https://proxmoxvex.com/changelog/#release--1-2-378-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-378-2026-09-04/</guid><description>&lt;p&gt;Implement HA CD pipeline (spec 999)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.377</title><link>https://proxmoxvex.com/changelog/#release--1-2-377-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-377-2026-09-04/</guid><description>&lt;p&gt;Implement HA CI pipeline (spec 998)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.376</title><link>https://proxmoxvex.com/changelog/#release--1-2-376-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-376-2026-09-04/</guid><description>&lt;p&gt;Implement HA vulnerability scan (spec 997)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.375</title><link>https://proxmoxvex.com/changelog/#release--1-2-375-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-375-2026-09-04/</guid><description>&lt;p&gt;Implement HA signed images (spec 996)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.374</title><link>https://proxmoxvex.com/changelog/#release--1-2-374-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-374-2026-09-04/</guid><description>&lt;p&gt;Implement HA replicated service (spec 995)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.373</title><link>https://proxmoxvex.com/changelog/#release--1-2-373-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-373-2026-09-04/</guid><description>&lt;p&gt;Implement HA template pack (spec 994)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.372</title><link>https://proxmoxvex.com/changelog/#release--1-2-372-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-372-2026-09-04/</guid><description>&lt;p&gt;Implement HA template pack (spec 994)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.371</title><link>https://proxmoxvex.com/changelog/#release--1-2-371-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-371-2026-09-04/</guid><description>&lt;p&gt;Implement HA multi-arch build (spec 993)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.370</title><link>https://proxmoxvex.com/changelog/#release--1-2-370-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-370-2026-09-04/</guid><description>&lt;p&gt;Implement HA base chart (spec 992)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.369</title><link>https://proxmoxvex.com/changelog/#release--1-2-369-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-369-2026-09-04/</guid><description>&lt;p&gt;Implement single-node rollback path (spec 991)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.368</title><link>https://proxmoxvex.com/changelog/#release--1-2-368-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-368-2026-09-04/</guid><description>&lt;p&gt;Implement single-node upgrade path (spec 990)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.367</title><link>https://proxmoxvex.com/changelog/#release--1-2-367-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-367-2026-09-04/</guid><description>&lt;p&gt;Implement single-node CD pipeline (spec 989)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.366</title><link>https://proxmoxvex.com/changelog/#release--1-2-366-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-366-2026-09-04/</guid><description>&lt;p&gt;Implement single-node CI pipeline (spec 988)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.365</title><link>https://proxmoxvex.com/changelog/#release--1-2-365-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-365-2026-09-04/</guid><description>&lt;p&gt;Implement single-node vulnerability scan (spec 987)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.364</title><link>https://proxmoxvex.com/changelog/#release--1-2-364-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-364-2026-09-04/</guid><description>&lt;p&gt;Implement single-node signed images (spec 986)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.363</title><link>https://proxmoxvex.com/changelog/#release--1-2-363-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-363-2026-09-04/</guid><description>&lt;p&gt;Implement single-node replicated service (spec 985)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.362</title><link>https://proxmoxvex.com/changelog/#release--1-2-362-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-362-2026-09-04/</guid><description>&lt;p&gt;Implement single-node template pack (spec 984)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.361</title><link>https://proxmoxvex.com/changelog/#release--1-2-361-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-361-2026-09-04/</guid><description>&lt;p&gt;Implement API tokens automation workflow (spec 983)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.360</title><link>https://proxmoxvex.com/changelog/#release--1-2-360-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-360-2026-09-04/</guid><description>&lt;p&gt;Implement API tokens role mapping (spec 982)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.359</title><link>https://proxmoxvex.com/changelog/#release--1-2-359-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-359-2026-09-04/</guid><description>&lt;p&gt;Implement API tokens policy adapter (spec 981)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.358</title><link>https://proxmoxvex.com/changelog/#release--1-2-358-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-358-2026-09-04/</guid><description>&lt;p&gt;Implement API tokens alert channel (spec 980)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.357</title><link>https://proxmoxvex.com/changelog/#release--1-2-357-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-357-2026-09-04/</guid><description>&lt;p&gt;Implement API tokens dashboard widget (spec 979)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.356</title><link>https://proxmoxvex.com/changelog/#release--1-2-356-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-356-2026-09-04/</guid><description>&lt;p&gt;Implement API tokens template library (spec 978)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.355</title><link>https://proxmoxvex.com/changelog/#release--1-2-355-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-355-2026-09-04/</guid><description>&lt;p&gt;Implement API tokens import (spec 977)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.354</title><link>https://proxmoxvex.com/changelog/#release--1-2-354-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-354-2026-09-04/</guid><description>&lt;p&gt;Implement API tokens sync (spec 976)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.353</title><link>https://proxmoxvex.com/changelog/#release--1-2-353-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-353-2026-09-04/</guid><description>&lt;p&gt;Implement API tokens connector (spec 975)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.352</title><link>https://proxmoxvex.com/changelog/#release--1-2-352-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-352-2026-09-04/</guid><description>&lt;p&gt;Implement API tokens export (spec 974)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.351</title><link>https://proxmoxvex.com/changelog/#release--1-2-351-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-351-2026-09-04/</guid><description>&lt;p&gt;Implement webhooks automation workflow (spec 973)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.350</title><link>https://proxmoxvex.com/changelog/#release--1-2-350-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-350-2026-09-04/</guid><description>&lt;p&gt;Implement webhooks role mapping (spec 972)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.349</title><link>https://proxmoxvex.com/changelog/#release--1-2-349-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-349-2026-09-04/</guid><description>&lt;p&gt;Implement webhooks policy adapter (spec 971)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.348</title><link>https://proxmoxvex.com/changelog/#release--1-2-348-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-348-2026-09-04/</guid><description>&lt;p&gt;Implement webhooks alert channel (spec 970)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.347</title><link>https://proxmoxvex.com/changelog/#release--1-2-347-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-347-2026-09-04/</guid><description>&lt;p&gt;Implement webhooks dashboard widget (spec 969)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.346</title><link>https://proxmoxvex.com/changelog/#release--1-2-346-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-346-2026-09-04/</guid><description>&lt;p&gt;Stabilise licence node fingerprint and send hardware_id to prevent recurring tier drops&lt;/p&gt;&lt;p&gt;Implement webhooks template library (spec 968)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.345</title><link>https://proxmoxvex.com/changelog/#release--1-2-345-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-345-2026-09-04/</guid><description>&lt;p&gt;Implement webhooks import (spec 967)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.344</title><link>https://proxmoxvex.com/changelog/#release--1-2-344-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-344-2026-09-04/</guid><description>&lt;p&gt;Implement webhooks sync (spec 966)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.343</title><link>https://proxmoxvex.com/changelog/#release--1-2-343-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-343-2026-09-04/</guid><description>&lt;p&gt;Implement incremental updates for cost engine (spec 965)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.342</title><link>https://proxmoxvex.com/changelog/#release--1-2-342-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-342-2026-09-04/</guid><description>&lt;p&gt;Implement memory optimisation for cost engine (spec 964)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.341</title><link>https://proxmoxvex.com/changelog/#release--1-2-341-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-341-2026-09-04/</guid><description>&lt;p&gt;Implement async workers for cost engine (spec 963)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.340</title><link>https://proxmoxvex.com/changelog/#release--1-2-340-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-340-2026-09-04/</guid><description>&lt;p&gt;Implement connection pooling for cost engine (spec 962)&lt;/p&gt;&lt;p&gt;Lock licence tier email and action buttons to the bottom of each plan card&lt;/p&gt;&lt;p&gt;Reload enabled plugins after licence activation so newly-licensed plugins are available without a server restart and fix bottom alignment&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.339</title><link>https://proxmoxvex.com/changelog/#release--1-2-339-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-339-2026-09-04/</guid><description>&lt;p&gt;Implement compression for cost engine (spec 961)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.338</title><link>https://proxmoxvex.com/changelog/#release--1-2-338-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-338-2026-09-04/</guid><description>&lt;p&gt;Implement indexing for cost engine (spec 960)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.337</title><link>https://proxmoxvex.com/changelog/#release--1-2-337-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-337-2026-09-04/</guid><description>&lt;p&gt;Implement batching for cost engine (spec 959)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.336</title><link>https://proxmoxvex.com/changelog/#release--1-2-336-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-336-2026-09-04/</guid><description>&lt;p&gt;Implement pagination for cost engine (spec 958)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.335</title><link>https://proxmoxvex.com/changelog/#release--1-2-335-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-335-2026-09-04/</guid><description>&lt;p&gt;Implement lazy loading for cost engine (spec 957)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.334</title><link>https://proxmoxvex.com/changelog/#release--1-2-334-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-334-2026-09-04/</guid><description>&lt;p&gt;Show context-aware Upgrade/Downgrade buttons on licence tier cards&lt;/p&gt;&lt;p&gt;Implement caching for cost engine (spec 956)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.333</title><link>https://proxmoxvex.com/changelog/#release--1-2-333-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-333-2026-09-04/</guid><description>&lt;p&gt;Implement incremental updates for realtime graph (spec 955)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.332</title><link>https://proxmoxvex.com/changelog/#release--1-2-332-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-332-2026-09-04/</guid><description>&lt;p&gt;Implement memory optimisation for realtime graph (spec 954)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.331</title><link>https://proxmoxvex.com/changelog/#release--1-2-331-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-331-2026-09-04/</guid><description>&lt;p&gt;Implement async workers for realtime graph (spec 953)&lt;/p&gt;&lt;p&gt;Fix executable permissions on packaging build and pipeline scripts&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.330</title><link>https://proxmoxvex.com/changelog/#release--1-2-330-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-330-2026-09-04/</guid><description>&lt;p&gt;Implement connection pooling for realtime graph (spec 952)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.329</title><link>https://proxmoxvex.com/changelog/#release--1-2-329-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-329-2026-09-04/</guid><description>&lt;p&gt;Remove public-facing GitHub links and fix pre-build xdist/gevent LoopExit&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.328</title><link>https://proxmoxvex.com/changelog/#release--1-2-328-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-328-2026-09-04/</guid><description>&lt;p&gt;Implement compression for realtime graph (spec 951)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.327</title><link>https://proxmoxvex.com/changelog/#release--1-2-327-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-327-2026-09-04/</guid><description>&lt;p&gt;Implement indexing for realtime graph (spec 950)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.326</title><link>https://proxmoxvex.com/changelog/#release--1-2-326-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-326-2026-09-04/</guid><description>&lt;p&gt;Implement batching for realtime graph (spec 949)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.325</title><link>https://proxmoxvex.com/changelog/#release--1-2-325-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-325-2026-09-04/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.324</title><link>https://proxmoxvex.com/changelog/#release--1-2-324-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-324-2026-09-04/</guid><description>&lt;p&gt;Implement pagination for realtime graph (spec 948)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.323</title><link>https://proxmoxvex.com/changelog/#release--1-2-323-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-323-2026-09-04/</guid><description>&lt;p&gt;Implement compare view for recovery drill (spec 947)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.322</title><link>https://proxmoxvex.com/changelog/#release--1-2-322-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-322-2026-09-04/</guid><description>&lt;p&gt;Implement live preview for recovery drill (spec 946)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.321</title><link>https://proxmoxvex.com/changelog/#release--1-2-321-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-321-2026-09-04/</guid><description>&lt;p&gt;Implement smart defaults for recovery drill (spec 945)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.320</title><link>https://proxmoxvex.com/changelog/#release--1-2-320-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-320-2026-09-04/</guid><description>&lt;p&gt;Fix license key not persisting — anchor CONFIG_DIR to the project root (overridable via PROXMOXVEX_CONFIG_DIR) so the AES-256 key file is stable regardless of launch directory; promote keys left in a CWD-relative ./config instead of generating a fresh key that orphans the encrypted license&lt;/p&gt;&lt;p&gt;Implement one-click apply for recovery drill (spec 944)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.319</title><link>https://proxmoxvex.com/changelog/#release--1-2-319-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-319-2026-09-04/</guid><description>&lt;p&gt;Increase VM/CT indent under sidebar host nodes&lt;/p&gt;&lt;p&gt;Implement undo action for recovery drill (spec 942)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.318</title><link>https://proxmoxvex.com/changelog/#release--1-2-318-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-318-2026-09-04/</guid><description>&lt;p&gt;Fix VM Update Manager confirmation dialog hidden by shared plugin-ui.css .modal rules&lt;/p&gt;&lt;p&gt;Implement recent items for recovery drill (spec 941)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.317</title><link>https://proxmoxvex.com/changelog/#release--1-2-317-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-317-2026-09-04/</guid><description>&lt;p&gt;Implement context menu for recovery drill (spec 940)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.316</title><link>https://proxmoxvex.com/changelog/#release--1-2-316-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-316-2026-09-04/</guid><description>&lt;p&gt;Group sidebar inventory VMs/CTs under their host nodes&lt;/p&gt;&lt;p&gt;Implement step-by-step wizard for recovery drill (spec 939)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.315</title><link>https://proxmoxvex.com/changelog/#release--1-2-315-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-315-2026-09-04/</guid><description>&lt;p&gt;Implement bulk selection for recovery drill (spec 938)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.314</title><link>https://proxmoxvex.com/changelog/#release--1-2-314-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-314-2026-09-04/</guid><description>&lt;p&gt;Implement compare view for update rollout (spec 937)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.313</title><link>https://proxmoxvex.com/changelog/#release--1-2-313-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-313-2026-09-04/</guid><description>&lt;p&gt;Implement smart defaults for recovery drill (spec 945)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.312</title><link>https://proxmoxvex.com/changelog/#release--1-2-312-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-312-2026-09-04/</guid><description>&lt;p&gt;Implement one-click apply for recovery drill (spec 944)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.311</title><link>https://proxmoxvex.com/changelog/#release--1-2-311-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-311-2026-09-04/</guid><description>&lt;p&gt;Implement quick filter for recovery drill (spec 943)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.310</title><link>https://proxmoxvex.com/changelog/#release--1-2-310-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-310-2026-09-04/</guid><description>&lt;p&gt;Implement undo action for recovery drill (spec 942)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.309</title><link>https://proxmoxvex.com/changelog/#release--1-2-309-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-309-2026-09-04/</guid><description>&lt;p&gt;Implement recent items for recovery drill (spec 941)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.308</title><link>https://proxmoxvex.com/changelog/#release--1-2-308-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-308-2026-09-04/</guid><description>&lt;p&gt;Implement context menu for recovery drill (spec 940)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.307</title><link>https://proxmoxvex.com/changelog/#release--1-2-307-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-307-2026-09-04/</guid><description>&lt;p&gt;Implement step-by-step wizard for recovery drill (spec 939)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.306</title><link>https://proxmoxvex.com/changelog/#release--1-2-306-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-306-2026-09-04/</guid><description>&lt;p&gt;Implement bulk selection for recovery drill (spec 938)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.305</title><link>https://proxmoxvex.com/changelog/#release--1-2-305-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-305-2026-09-04/</guid><description>&lt;p&gt;Implement compare view for update rollout (spec 937)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.304</title><link>https://proxmoxvex.com/changelog/#release--1-2-304-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-304-2026-09-04/</guid><description>&lt;p&gt;Local release and documentation mirror (spec 1000)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.303</title><link>https://proxmoxvex.com/changelog/#release--1-2-303-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-303-2026-09-04/</guid><description>&lt;p&gt;Public release sync&lt;/p&gt;&lt;p&gt;Build fix: resolve test and theme regressions&lt;/p&gt;&lt;p&gt;Remove all publicly visible GitHub links; replace with `proxmoxvex.local` placeholders and add speckit for local docs/releases/update endpoints.&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.302</title><link>https://proxmoxvex.com/changelog/#release--1-2-302-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-302-2026-09-04/</guid><description>&lt;p&gt;Implement live preview for update rollout (spec 936)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.301</title><link>https://proxmoxvex.com/changelog/#release--1-2-301-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-301-2026-09-04/</guid><description>&lt;p&gt;Implement smart defaults for update rollout (spec 935)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.300</title><link>https://proxmoxvex.com/changelog/#release--1-2-300-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-300-2026-09-04/</guid><description>&lt;p&gt;Implement one-click apply for update rollout (spec 934)&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.299</title><link>https://proxmoxvex.com/changelog/#release--1-2-299-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-299-2026-09-04/</guid><description>&lt;p&gt;Spec 933: quick filter for update rollout&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.298</title><link>https://proxmoxvex.com/changelog/#release--1-2-298-2026-09-04/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-298-2026-09-04/</guid><description>&lt;p&gt;Remove unused NetApp ONTAP plugin-updater translation keys from all 14 locale files.&lt;/p&gt;&lt;p&gt;VM Update Manager: add `aria-atomic=&quot;true&quot;` to the status message region and `aria-live`/`aria-atomic` to the console output region for better screen-reader announcements&lt;/p&gt;&lt;p&gt;VM Update Manager: disable the guest form submit button while `saveGuest` is in-flight to prevent duplicate saves&lt;/p&gt;</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.297</title><link>https://proxmoxvex.com/changelog/#release--1-2-297-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-297-2026-09-03/</guid><description>&lt;p&gt;Full build and deploy&lt;/p&gt;&lt;p&gt;Licence tier cards: sort by ascending monthly price, remove drag-and-drop reordering, and hide the buy button on the currently activated tier&lt;/p&gt;&lt;p&gt;Licence tier prices now convert live to the selected currency on the licence server, so changing the currency dropdown in the licence page updates prices immediately&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.296</title><link>https://proxmoxvex.com/changelog/#release--1-2-296-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-296-2026-09-03/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.295</title><link>https://proxmoxvex.com/changelog/#release--1-2-295-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-295-2026-09-03/</guid><description>&lt;p&gt;Undo Action for Update Rollout (932): add UpdateRolloutUndo component, updateRolloutUndo state, skip-confirm rollback path, and handleUpdateRolloutUndo in web/src/settings_modal.js; render undo affordance in Updates tab; reuse i18n keys undoAction/undo; add tests/test_undo_action_update_rollout.py&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.294</title><link>https://proxmoxvex.com/changelog/#release--1-2-294-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-294-2026-09-03/</guid><description>&lt;p&gt;Recent Items for Update Rollout (931): add UpdateRolloutRecentItems component, localStorage history, clear and replay handlers in web/src/settings_modal.js; render recent items in the Updates tab; reuse i18n keys recentItems/noRecentItems/clear; add tests/test_recent_items_update_rollout.py&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.293</title><link>https://proxmoxvex.com/changelog/#release--1-2-293-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-293-2026-09-03/</guid><description>&lt;p&gt;Context Menu for Update Rollout (930): add UpdateRolloutContextMenu component to web/src/settings_modal.js; right-click the Updates tab to check/install updates, view backups for rollback, and open the GitHub release; add i18n key openGitHubRelease across core locales and tests/test_context_menu_update_rollout.py&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.292</title><link>https://proxmoxvex.com/changelog/#release--1-2-292-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-292-2026-09-03/</guid><description>&lt;p&gt;Keyboard Shortcuts for Backup Jobs (929): add BackupJobsKeyboardShortcuts component to web/src/dashboard.js; focus keyword search with &apos;f&apos;, show help overlay with &apos;?&apos;, close with Escape; reuse i18n keys; add tests/test_keyboard_shortcuts_for_backup_jobs.py&lt;/p&gt;&lt;p&gt;Plugin list: default `/api/plugins` pagination to the full discovered plugin list when the caller does not request a page size, so corporate and other licensed tiers display every available plugin.&lt;/p&gt;&lt;p&gt;Licence activation now requires both the license key and the registered customer email; the license server validates the email against the license&apos;s customer record and the main app stores it for revalidation.&lt;/p&gt;&lt;p&gt;NetApp ONTAP: remove the in-plugin updater (`settings/update/info` and `settings/update/apply`) and the settings UI that triggered it; theme work is no longer at risk of being overwritten by a plugin self-update.&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.291</title><link>https://proxmoxvex.com/changelog/#release--1-2-291-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-291-2026-09-03/</guid><description>&lt;p&gt;Conversion system improvements: job details view, cancel/clear/stop actions, and target VM/CT ID validation&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.290</title><link>https://proxmoxvex.com/changelog/#release--1-2-290-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-290-2026-09-03/</guid><description>&lt;p&gt;Filter Sidebar for Backup Jobs (928): add backupJobsFilters state, matchesBackupJobFilter predicate, and BackupJobsFilterSidebar component to web/src/dashboard.js; add .backup-jobs-filter-sidebar styles to web/src/app-shell.css; add tests/test_filter_sidebar_for_backup_jobs.py.&lt;/p&gt;&lt;p&gt;NetApp ONTAP: add `custom.css` and `custom.js` hooks to the UI for user theme and behaviour overrides.&lt;/p&gt;&lt;p&gt;Unify the dark/light color palette across all plugin `ui.css` files to the shared ProxmoxVEx plugin theme and add `scripts/unify-plugin-themes.py` to keep them consistent.&lt;/p&gt;&lt;p&gt;Generate fresh landing-page screenshots for every plugin and native integration in the unified dark theme, then rebuild `proxmoxvex-landing`.&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.289</title><link>https://proxmoxvex.com/changelog/#release--1-2-289-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-289-2026-09-03/</guid><description>&lt;p&gt;Drag-and-Drop Reordering for Backup Jobs (927): add BackupJobsDragHandle to web/src/tables.js, add .backup-jobs-drag-handle styles to web/src/ui-states.css, wire backupJobsOrder/reorderBackupJobs and draggable rows into the Sync/Verify/Prune tables in web/src/dashboard.js, with tests/test_drag_and_drop_reordering_for_backup_jobs.py coverage.&lt;/p&gt;&lt;p&gt;GPU Passthrough Catalog: convert the plugin from a simulation to live Proxmox VE GPU passthrough management. Scans real `hardware/pci` devices, reads and writes VM `hostpci` config via the PVE API, exposes pcie/x-vga/rombar/mdev options, and enforces per-VM `vm.config` permissions.&lt;/p&gt;&lt;p&gt;Settings: split Plugins and Native Integrations into their own tabs in `web/src/settings_modal.js` and fix `/api/plugins` response parsing in `web/src/dashboard.js` and `web/src/pages/Settings/Licence.jsx` so the plugin list renders correctly.&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.288</title><link>https://proxmoxvex.com/changelog/#release--1-2-288-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-288-2026-09-03/</guid><description>&lt;p&gt;Fix AES-256-GCM decryption for clusters/PBS stored with the legacy lowercase-cased key file (`.proxmoxVEx_aes256.key`) by loading it as a fallback and attempting all known keys before failing; also isolate `get_all_clusters` per-row decryption errors so one bad row no longer hides every cluster.&lt;/p&gt;&lt;p&gt;Add Playwright end-to-end test for plugin/native integration iframe theme bridge (`tests/test_plugin_theme_bridge.py`).&lt;/p&gt;&lt;p&gt;VM Update Manager: add a fallback i18n loader in `ui.js` so English translations are still available when the parent `ProxmoxVExI18n` bridge is missing&lt;/p&gt;&lt;p&gt;VM Update Manager: translate `TypeError` fetch failures and surface `offline`/`online` status messages in the plugin UI&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.287</title><link>https://proxmoxvex.com/changelog/#release--1-2-287-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-287-2026-09-03/</guid><description>&lt;p&gt;Customizable Columns for Backup Jobs (926)&lt;/p&gt;&lt;p&gt;Add PayPal purchase support to the Licence settings page: per-tier email input, `/api/v1/licence/paypal/create-order` proxy endpoint, and redirect to PayPal approval URL.&lt;/p&gt;&lt;p&gt;Customizable Columns for Backup Jobs (926): add `BackupJobsColumnPicker` component to `web/src/tables.js`, wire `backupJobsVisibleColumns` state and the column picker into the Backup Jobs page in `web/src/dashboard.js`, and render conditional Sync/Verify/Prune table columns based on the selected visibility, with `tests/test_customizable_columns_for_backup_jobs.py` coverage.&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.286</title><link>https://proxmoxvex.com/changelog/#release--1-2-286-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-286-2026-09-03/</guid><description>&lt;p&gt;Breadcrumb Bar for Backup Jobs (925): add `BackupJobsBreadcrumbBar` component to `web/src/routes.jsx` and render it on the Backup Jobs page in `web/src/dashboard.js` so users can navigate from the selected PBS datastore back to the dashboard, with `tests/test_breadcrumb_bar_for_backup_jobs.py` coverage.&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.285</title><link>https://proxmoxvex.com/changelog/#release--1-2-285-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-285-2026-09-03/</guid><description>&lt;p&gt;Hover Cards for Backup Jobs (924): add `BackupJobsHoverCard` component to `web/src/dashboard.js` that displays job details on hover, wrap the Sync, Verify, and Prune job status badges with the hover card, and add `.backup-jobs-hover-card` styles to `web/src/ui-states.css`, with `tests/test_hover_cards_for_backup_jobs.py` coverage.&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.284</title><link>https://proxmoxvex.com/changelog/#release--1-2-284-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-284-2026-09-03/</guid><description>&lt;p&gt;Color-Coded Status for Backup Jobs (923): add `BackupJobsStatusBadge` component to `web/src/tables.js` with color-coded variants for idle/success/warning/error, render the badge in the Sync, Verify, and Prune job tables in `web/src/dashboard.js`, with `tests/test_color_coded_status_for_backup_jobs.py` coverage.&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.283</title><link>https://proxmoxvex.com/changelog/#release--1-2-283-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-283-2026-09-03/</guid><description>&lt;p&gt;Resizable Panels for Backup Jobs (922): add `BackupJobsResizablePanels` component to `web/src/dashboard.js`, wrap the Jobs Tab with resizable left/right panels, and apply `.backup-jobs-resizable-panels` and `.backup-jobs-resize-handle` styles in `web/src/app-shell.css`, with `tests/test_resizable_panels_for_backup_jobs.py` coverage.&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.282</title><link>https://proxmoxvex.com/changelog/#release--1-2-282-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-282-2026-09-03/</guid><description>&lt;p&gt;Compact Grid for Backup Jobs (921): add `BackupJobsCompactGrid` component to `web/src/tables.js`, wire `backupJobsCompact` state and a density toggle into the Backup Jobs page in `web/src/dashboard.js`, and apply `.backup-jobs-grid.compact` table-density styles in `web/src/app-shell.css`, with `tests/test_compact_grid_for_backup_jobs.py` coverage.&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.281</title><link>https://proxmoxvex.com/changelog/#release--1-2-281-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-281-2026-09-03/</guid><description>&lt;p&gt;Full build-deploy-test pipeline&lt;/p&gt;&lt;p&gt;Unify plugin and native integration iframe theming with the main app so light/dark mode switching and the active colour palette apply to all embedded modules.&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.280</title><link>https://proxmoxvex.com/changelog/#release--1-2-280-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-280-2026-09-03/</guid><description>&lt;p&gt;Dark Mode for Backup Jobs (920)&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.279</title><link>https://proxmoxvex.com/changelog/#release--1-2-279-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-279-2026-09-03/</guid><description>&lt;p&gt;Build and push update file&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.278</title><link>https://proxmoxvex.com/changelog/#release--1-2-278-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-278-2026-09-03/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.277</title><link>https://proxmoxvex.com/changelog/#release--1-2-277-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-277-2026-09-03/</guid><description>&lt;p&gt;Build and push update file&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.276</title><link>https://proxmoxvex.com/changelog/#release--1-2-276-2026-09-03/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-276-2026-09-03/</guid><description>&lt;p&gt;Add free Community tier to the licence fallback catalog, TIER_RANK, default fallback logic, and Licence settings page&lt;/p&gt;</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.276</title><link>https://proxmoxvex.com/changelog/#release--1-2-276-2026-09-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-276-2026-09-02/</guid><description>&lt;p&gt;Build and serve&lt;/p&gt;&lt;p&gt;NetApp ONTAP: add comprehensive unit tests for ONTAP client (`tests/test_netapp_ontap_client.py`), storage plugin backend (`tests/unit/test_netapp_storage.py`), and integration endpoints (`tests/test_netapp_integration.py`)&lt;/p&gt;&lt;p&gt;Clean up unused `patch` import and suppress E402 path-manipulation import in `tests/test_netapp_ontap_client.py`&lt;/p&gt;&lt;p&gt;Fix logo and manifest 404s by renaming `images/proxmoxVEx*.png` assets to the `ProxmoxVEx*.png` casing used by the UI, manifest, and service worker&lt;/p&gt;&lt;p&gt;Build scripts: pass `--yes` through to `bump-version.py`, skip `app.bundle.js.map` in Trivy scans, and enable Babel `--compact` output to silence the &gt;500KB deoptimisation warning&lt;/p&gt;&lt;p&gt;Settings modal: fix `discoveredPlugins.map is not a function` crash by using `data.plugins` from `/api/plugins`&lt;/p&gt;&lt;p&gt;Topology diagram: variable-width compact tiered layout so each node only occupies the horizontal space its own VM band needs&lt;/p&gt;&lt;p&gt;Topology dashboard: stop polling `/api/clusters/{id}/datastores` for missing, disconnected, or removed clusters; pass `connected` prop to `TopologyView` to avoid storage-tier 404s&lt;/p&gt;&lt;p&gt;Topology dashboard: wait for node metrics and VM/CT resources to load before freezing the multi-cluster diagram snapshot, preventing an empty/0-VM topology on first open&lt;/p&gt;&lt;p&gt;Plugin UI serving: replace `__CACHE_BUST__` tokens and add no-cache headers to plugin `ui.html`, `ui.css`, and `ui.js` responses so plugin UI fixes are not masked by browser or reverse-proxy caching&lt;/p&gt;&lt;p&gt;VM Update Manager: remove dead UI asset handlers from `routes.py` because the core plugin proxy serves `ui.html`/`ui.css`/`ui.js`&lt;/p&gt;&lt;p&gt;VM Update Manager: use timezone-aware UTC timestamps in `db.py` and expose the `driver` field in `Guest.to_dict`&lt;/p&gt;&lt;p&gt;VM Update Manager: add an `i18n/en.json` translation file, fix `ui.js` to load it via the plugin i18n endpoint, and translate all `data-i18n`/`data-i18n-placeholder` elements before the first render&lt;/p&gt;&lt;p&gt;VM Update Manager: harden `confirmAction` modal with focus trap, focus restoration, `aria-label`/`aria-describedby`, and body scroll lock&lt;/p&gt;&lt;p&gt;VM Update Manager: make `api()` and `coreApi()` robust against non-JSON error responses so the UI displays readable errors instead of &quot;JSON parse&quot; exceptions&lt;/p&gt;&lt;p&gt;VM Update Manager: add backend unit tests covering model serialisation, utility functions, and database CRUD with an in-memory SQLite stand-in&lt;/p&gt;&lt;p&gt;VM Update Manager: add scheduler engine unit tests for `_next_run`, `_simple_next_run`, and `_is_due`&lt;/p&gt;&lt;p&gt;VM Update Manager: detect security updates in the SSH driver by inspecting apt/yum repository names and set `is_security` on discovered packages&lt;/p&gt;&lt;p&gt;VM Update Manager: add a `Security` column to the packages table with styling and an `i18n` key so security updates are visible in the UI&lt;/p&gt;&lt;p&gt;Core: fix Flask blueprint name collision (`vmware` vs `vmware_integration`) that prevented the debug server from starting by passing an explicit `name=` to `app.register_blueprint`&lt;/p&gt;&lt;p&gt;VM Update Manager: add batch database helpers (`get_credentials_for_guests`, `get_policies_for_guests`, `get_last_checks_for_guests`) and use them in `_guests` to avoid N+1 enrichment queries&lt;/p&gt;&lt;p&gt;i18n validator: extend `web/i18n/validate.py` to scan `plugins/&lt;id&gt;/i18n` directories so plugin translations are checked alongside core and native namespaces&lt;/p&gt;&lt;p&gt;VM Update Manager: add unit tests for the SSH driver covering apt, apk, dnf discovery and apt dry-run apply with a mocked `_ssh_exec`&lt;/p&gt;</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.275</title><link>https://proxmoxvex.com/changelog/#release--1-2-275-2026-09-02/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-275-2026-09-02/</guid><description>&lt;p&gt;Licence settings: replace the split resizable view with a full-width layout and move the tier filter into the main content area&lt;/p&gt;</description><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.274</title><link>https://proxmoxvex.com/changelog/#release--1-2-274-2026-09-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-274-2026-09-01/</guid><description>&lt;p&gt;IDE warnings: address Proxmox Backup Server DOM-based XSS warnings by using textContent/appendChild, convert TrueNAS `set()` generators to set comprehensions, and harden Helm container security (`readOnlyRootFilesystem: true`, writable `/tmp` and `/app/backups` emptyDirs, `imagePullPolicy: Always` for the `latest` tag)&lt;/p&gt;&lt;p&gt;Licensing: default to the production license service &lt;https://licensing.proxmoxvex.com&gt; when LICENCE_SERVICE_URL is unset&lt;/p&gt;</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.273</title><link>https://proxmoxvex.com/changelog/#release--1-2-273-2026-09-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-273-2026-09-01/</guid><description>&lt;p&gt;Proxmox Backup Server: full UI overhaul with tables, resource gauges, and datastore usage bars&lt;/p&gt;&lt;p&gt;Native Integrations: remove Ansible and Terraform from the native integration list; their export endpoints are cluster-level and have no native UI, stopping 404 requests for /api/ansible/ui and /api/terraform/ui&lt;/p&gt;&lt;p&gt;VM Update Manager: harden confirmation modal with explicit inline and CSS !important colours so it stays visible with corp-dark and other themes&lt;/p&gt;</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.272</title><link>https://proxmoxvex.com/changelog/#release--1-2-272-2026-09-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-272-2026-09-01/</guid><description>&lt;p&gt;Dashboard: fix PBS datastores response parsing to avoid &apos;pbsDatastores.map is not a function&apos; crash&lt;/p&gt;&lt;p&gt;Dashboard: add runtime Array.isArray guard for pbsDatastores before .map/.find&lt;/p&gt;</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.271</title><link>https://proxmoxvex.com/changelog/#release--1-2-271-2026-09-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-271-2026-09-01/</guid><description>&lt;p&gt;Helm chart: add .helmignore to exclude packages/reports/backups and update icon, version, and appVersion&lt;/p&gt;&lt;p&gt;VM Update Manager: fix confirmation modal not showing when parent theme colours are CSS var(--...) references&lt;/p&gt;</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.270</title><link>https://proxmoxvex.com/changelog/#release--1-2-270-2026-09-01/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-270-2026-09-01/</guid><description>&lt;p&gt;VM Update Manager: fix PostgreSQL table creation for new installs by conditionally adding migration columns instead of always attempting ALTER TABLE&lt;/p&gt;&lt;p&gt;VM Update Manager: use BIGINT for guest_id primary keys in credentials/policies so PostgreSQL does not treat them as GENERATED ALWAYS identity columns&lt;/p&gt;</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.269</title><link>https://proxmoxvex.com/changelog/#release--1-2-269-2026-08-31/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-269-2026-08-31/</guid><description>&lt;p&gt;vCenter: read API responses as text before JSON.parse to show a useful error when an HTML page is returned instead of JSON&lt;/p&gt;</description><pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.268</title><link>https://proxmoxvex.com/changelog/#release--1-2-268-2026-08-31/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-268-2026-08-31/</guid><description>&lt;p&gt;Full build pipeline&lt;/p&gt;</description><pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate></item><item><title>ProxmoxVEx — 1.2.267</title><link>https://proxmoxvex.com/changelog/#release--1-2-267-2026-08-31/</link><guid isPermaLink="true">https://proxmoxvex.com/changelog/#release--1-2-267-2026-08-31/</guid><description>&lt;p&gt;Setup: install `helm`, `trivy`, and `hadolint` via `deploy.sh` and document them as packaging/CI requirements&lt;/p&gt;</description><pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate></item></channel></rss>