diff --git a/app/plugins/base.py b/app/plugins/base.py index 745201c..560f5bf 100644 --- a/app/plugins/base.py +++ b/app/plugins/base.py @@ -58,10 +58,7 @@ class BasePlugin(ABC): ``dashboard_widgets`` entries (alias — one contribution type, #359 philosophy). Registered automatically here; no per-plugin code needed. """ - for event_name in self.manifest.events: - handler = self._make_event_handler(event_name) - self._event_handlers[event_name] = handler - event_bus.subscribe(event_name, handler) + self._register_manifest_events(event_bus) self._container = service_container self._register_manifest_miniapps() @@ -157,9 +154,31 @@ class BasePlugin(ABC): The worker calls this on every active plugin at startup so plugins can subscribe to events even when the web process is separate. - Default: no-op. Override to subscribe handlers. + + F06 (Astra P1): the default now subscribes the plugin's manifest + events via the SAME idempotent path as ``on_activate`` — previously + this was a no-op, so the worker registered 0 of the 44 declared + event handlers and Outbox events reached no plugin handler. Plugins + that override this MUST call ``await super().register_event_handlers( + event_bus)`` to keep the manifest subscription. """ - return None + self._register_manifest_events(event_bus) + + def _register_manifest_events(self, event_bus: EventBus) -> None: + """Subscribe to manifest events — shared, idempotent (F06). + + Used by BOTH the API activation path (``on_activate``) and the + worker startup hook (``register_event_handlers``). Idempotent: an + event already subscribed in this instance is not subscribed twice. + DB-writing lifecycle work (seeding, cron registration) stays in + ``on_activate`` — the worker path deliberately skips it. + """ + for event_name in self.manifest.events: + if event_name in self._event_handlers: + continue # already subscribed — idempotent + handler = self._make_event_handler(event_name) + self._event_handlers[event_name] = handler + event_bus.subscribe(event_name, handler) # ─── Job Modules ───