fix(arch-001,arch-002): permissions before on_activate; activate once per process

This commit is contained in:
Agent Zero
2026-08-23 15:04:51 +02:00
parent 337d78ef53
commit 1d6152fb82
3 changed files with 28 additions and 11 deletions
+9 -8
View File
@@ -284,22 +284,23 @@ async def lifespan(app: FastAPI):
logger.info(f"Plugin {name} is inactive — skipping activation")
continue
# Activate plugin with a FRESH session per plugin to avoid RLS state leakage
# RLS fail-closed requires app.current_tenant_id for tenant-table writes.
# Plugin activation may fail on duplicate cron job inserts — this is harmless
# since cron jobs already exist from previous startups.
# Activate plugin ONCE per process (ARCH-002 fix): a fresh session with
# the first tenant's RLS context satisfies fail-closed RLS for any
# tenant-table writes during activation. Plugins that need per-tenant
# data must seed it themselves (e.g. via the default-tenant mechanism).
# Calling on_activate once prevents duplicate event listeners, cron
# jobs, mini-apps and other contributions at multi-tenant startups.
plugin_activated = False
for tenant_id in all_tenant_ids:
if all_tenant_ids:
try:
async with async_session() as plugin_db:
await set_tenant_context(plugin_db, tenant_id)
await set_tenant_context(plugin_db, all_tenant_ids[0])
await plugin.on_activate(plugin_db, container, event_bus)
await plugin_db.flush()
await plugin_db.commit()
plugin_activated = True
except Exception as exc:
logger.warning(f"[STARTUP] Plugin {name} activation issue for tenant {tenant_id}: {exc}")
break
logger.warning(f"[STARTUP] Plugin {name} activation issue: {exc}")
if plugin_activated:
plugin_record.status = "active"
+16
View File
@@ -608,6 +608,22 @@ class PluginRegistry:
for warning in perm_warnings:
logger.warning(warning)
# Register permissions and entity models BEFORE on_activate (ARCH-001 fix):
# the activation hook may already rely on its own permissions/entities being
# resolvable (e.g. tools declaring required_permission).
from app.core.permission_registry import (
get_permission_registry,
register_plugin_permissions,
)
if plugin.manifest.permissions:
register_plugin_permissions(name, plugin.manifest.permissions)
get_permission_registry()._active_plugins.add(name)
for entity_type, model_class in plugin.get_entity_models().items():
from app.services.entity_permission_service import register_entity_model
register_entity_model(entity_type, model_class)
# Call on_activate hook (registers event listeners)
await plugin.on_activate(db, self._container, self._event_bus)
+3 -3
View File
@@ -530,7 +530,7 @@ class TestAgentPermissions:
) as mock_resolve:
mock_resolve.return_value = _permissions(permissions=["mail:read"])
with patch(
"app.plugins.builtins.ai_assistant.contracts.get_tool_registry",
"app.ai.tool_registry.get_tool_registry",
return_value=tool_registry,
):
ctx = await resolve_agent_permissions(
@@ -575,7 +575,7 @@ class TestAgentPermissions:
) as mock_resolve:
mock_resolve.return_value = _permissions(permissions=["mail:read"])
with patch(
"app.plugins.builtins.ai_assistant.contracts.get_tool_registry",
"app.ai.tool_registry.get_tool_registry",
return_value=tool_registry,
):
ctx = await resolve_agent_permissions(
@@ -1015,7 +1015,7 @@ class TestContextBuilder:
])
with patch(
"app.plugins.builtins.ai_assistant.contracts.get_tool_registry",
"app.ai.tool_registry.get_tool_registry",
return_value=tool_registry,
):
messages = await build_agent_context(