fix(plugin): add version comparison and migration run on version mismatch

Bug 6: install() and activate() now compare the plugin's manifest
version with the stored DB version. If they differ, migrations are
re-run to bring the schema up to date and the DB version field is
updated. This ensures that updating a plugin with a new manifest
version triggers the migration runner automatically.
This commit is contained in:
2026-07-03 16:56:30 +00:00
parent f9508d17de
commit 5138590277
+49 -2
View File
@@ -211,12 +211,53 @@ class PluginRegistry:
)
return warnings
# ── Version Comparison & Update Path ──
async def _check_and_run_version_migrations(
self, db: AsyncSession, name: str, record: PluginModel
) -> bool:
"""Compare manifest version with DB version and run migrations if different.
If the plugin's manifest version differs from the stored DB version,
re-run all migrations to bring the schema up to date, then update
the DB record's version field.
Returns True if migrations were run, False otherwise.
"""
plugin = self.get_plugin(name)
if plugin is None:
return False
manifest_version = plugin.manifest.version
db_version = record.version
if manifest_version == db_version:
return False
logger.info(
f"Plugin '{name}' version mismatch: DB={db_version}, manifest={manifest_version}. "
f"Running migrations to update."
)
# Re-run migrations to apply any new migration files
if plugin.manifest.migrations:
await self.migration_runner.run_all_migrations(db, name, plugin.manifest.migrations)
# Update DB version to match manifest
record.version = manifest_version
await db.flush()
self._db_status[name] = record
logger.info(f"Plugin '{name}' updated to version {manifest_version}.")
return True
# ── Install / Activate / Deactivate / Uninstall ──
async def install(self, db: AsyncSession, name: str) -> PluginModel:
"""Install a plugin: run migrations and create DB record.
Idempotent: if already installed, returns existing record.
Idempotent: if already installed, checks for version updates and
returns existing record.
Checks that all declared dependencies are installed first.
"""
plugin = self.get_plugin(name)
@@ -226,6 +267,8 @@ class PluginRegistry:
# Check if already installed in DB
existing = await self._get_plugin_record(db, name)
if existing is not None:
# Check for version update — run migrations if version changed
await self._check_and_run_version_migrations(db, name, existing)
return existing
# Check dependencies are installed
@@ -256,7 +299,8 @@ class PluginRegistry:
async def activate(self, db: AsyncSession, name: str) -> PluginModel:
"""Activate a plugin: register routes, event listeners, set status=active.
Idempotent: if already active, returns existing record without error.
Idempotent: if already active, checks for version updates and
returns existing record without error.
Checks that all declared dependencies are active first.
Performs a soft permission check and logs warnings for unknown permissions.
"""
@@ -268,6 +312,9 @@ class PluginRegistry:
if record is None:
raise ValueError(f"Plugin '{name}' is not installed — install first")
# Check for version update — run migrations if version changed
await self._check_and_run_version_migrations(db, name, record)
# Idempotent: already active
if record.active and record.status == "active":
return record