Files
FabledSteward/steward/migrations/versions/0025_drop_http_plugin_setting.py
bvandeusenandClaude Opus 5 59fece855d
CI / lint (push) Successful in 3s
CI / unit (push) Successful in 46s
CI / integration (push) Successful in 2m22s
CI / publish (push) Successful in 1m16s
fix(plugins): remove the phantom http plugin; surface orphaned plugin settings
Operator saw the `http` plugin -- deleted when ping/dns/http were unified into
the Monitor entity -- still reported as enabled, with no way to clear it.

It was never an orphaned row. `plugin.http` was hardcoded in core DEFAULTS, so
deleting the app_settings row did nothing: the default reasserted it on the
next settings load. That is precisely why no cleanup UI could have fixed it.
Rule 22 says the removed subsystem should have taken its setting with it.

Purged the surviving references -- the DEFAULTS key, "http" in
CAPABILITY_PLUGINS, the stale plugin_manager docstring example, and the
capabilities blurb still advertising HTTP/uptime as a bundled capability --
plus a migration dropping any stored plugin.http row. Untouched: `http` as a
MONITOR TYPE (icmp/tcp/dns/http) everywhere it appears, and http_001_initial,
which is kept deliberately so existing DBs resolve the revision graph.

For the general case, cleanup splits by provenance rather than being uniformly
automatic or uniformly manual:

  Bundled plugins ship in the image and version atomically with core, so they
  cannot be transiently missing -- a plugin.* default with no bundled directory
  is unambiguously a bug. Guarded by a unit test that fails CI, which is the
  only part safe to automate.

  External plugins live in operator-mounted /data/plugins, where absence is
  ambiguous: unmounted volume, failed install, mid-upgrade. Auto-deleting their
  config would silently destroy unrecoverable credentials on a transient
  condition, so Settings now lists them under "Configured but not installed"
  with an explicit, confirmed, audit-logged Remove. The section hides entirely
  when empty, and the remove route refuses if the plugin is actually installed.

Orphan detection reads STORED rows, never the DEFAULTS-merged view -- offering
to remove a default-only key would be a lie -- and returns names only, since
plugin config can hold credentials and listing orphans never needs their values.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 23:41:06 -04:00

41 lines
1.5 KiB
Python

"""Drop the stored setting for the removed http plugin
The standalone `http` plugin was dissolved into the unified Monitor entity
(0022_unify_monitors), but its `plugin.http` settings key outlived it: the key
stayed in core DEFAULTS, so the operator saw a plugin that no longer exists
reported as "enabled" with no way to clear it — deleting the row alone did
nothing, because the default reasserted it on the next settings load.
The DEFAULTS entry is removed in the same change; this migration clears any row
an operator's database still carries so the two agree. Per family rule 22, the
removed subsystem takes its setting row with it.
Note: `http` remains a valid MONITOR TYPE (icmp/tcp/dns/http). This touches only
the plugin-enablement key, never monitor data.
Revision ID: 0025_drop_http_plugin_setting
Revises: 0024_plugin_metrics_hourly
Create Date: 2026-08-13
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "0025_drop_http_plugin_setting"
down_revision: Union[str, None] = "0024_plugin_metrics_hourly"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.execute(
sa.text("DELETE FROM app_settings WHERE key = 'plugin.http'")
)
def downgrade() -> None:
# Deliberately empty. Re-inserting `plugin.http` would recreate the exact
# phantom this migration exists to remove, and the plugin it configured no
# longer exists to read it.
pass