Move serverkit-gpu from CONVERTED_BUILTIN_SLUGS into GATED_BUILTIN_SLUGS behind
a _gpu_present() predicate (shutil.which('nvidia-smi')), mirroring the email
gate: fresh installs stay marketplace-only, upgraded panels without a GPU no
longer get it, upgraded panels with a GPU keep it (D3 continuity). Rewrite the
12 builtin + baked extension descriptions that used em dashes into plain
sentences, keeping every pair in sync. New test_extension_manifests.py walks
both manifest trees to assert no em/en dashes and per-slug description parity.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- plugin.json: lifecycle hooks were keyed on_install/on_uninstall, but the
loader reads lifecycle.get('install')/('uninstall') — the hooks (notify-event
registration, container teardown) would have silently never fired. The
manifest validator only vets the values, not the keys, so this slipped past.
- stalwart_service.get_status: read the engine version from a dedicated
read-only /status endpoint instead of GET-ing the /reload endpoint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>