SurfSense/surfsense_backend/app/automations/triggers/store.py

24 lines
683 B
Python
Raw Normal View History

"""In-memory trigger registry. Populated once at process startup."""
feat(automation): add empty Capability / Action / Trigger registries Three registries under app/automations/registries/, each as its own folder with the same SRP-per-file split (types.py for the dataclass, store.py for the in-memory dict + register/get/all functions). All three start empty; concrete entries land when the user signs off on which capabilities / actions / triggers to include (step 2). Capability (locked at v1-minimum five fields — see commit 2): - id, description, input_schema, output_schema, handler - CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] - Frozen, slotted dataclass (immutable post-registration). ActionDefinition (v1-trim of design plan §4): - type, name, description, config_schema, handler - Defers output_contract (handled per-step by agent_task's config.output_schema), uses_capabilities (no static analysis needed until >1 action ships), and produces_artifacts (deferred alongside the artifact pipeline). TriggerDefinition (declarative, no handler): - type, description, config_schema, payload_schema - No handler field — firing is a single dispatcher's responsibility, not a per-trigger one. store.py contract for all three: - register_*: idempotent at process startup, raises on duplicate - get_*: returns None on miss - all_*: returns a defensive copy of the registry dict Verified by an inline smoke test (10 checks): empty initial state, registration and lookup work, duplicates raise, frozen dataclasses reject mutation, snapshots are copies, handlers are awaitable. Isolation invariant audit: grep across the full app/automations/ tree shows only three app.* imports, all of them ``from app.db import BaseModel, TimestampMixin`` in the model files. No imports from app.agents.*, app.services.*, app.tasks.*, app.routes.*, or any other business-logic module.
2026-05-26 22:54:17 +02:00
from __future__ import annotations
from .types import TriggerDefinition
_REGISTRY: dict[str, TriggerDefinition] = {}
def register_trigger(trigger: TriggerDefinition) -> None:
"""Register a trigger. Raises on duplicate type."""
feat(automation): add empty Capability / Action / Trigger registries Three registries under app/automations/registries/, each as its own folder with the same SRP-per-file split (types.py for the dataclass, store.py for the in-memory dict + register/get/all functions). All three start empty; concrete entries land when the user signs off on which capabilities / actions / triggers to include (step 2). Capability (locked at v1-minimum five fields — see commit 2): - id, description, input_schema, output_schema, handler - CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] - Frozen, slotted dataclass (immutable post-registration). ActionDefinition (v1-trim of design plan §4): - type, name, description, config_schema, handler - Defers output_contract (handled per-step by agent_task's config.output_schema), uses_capabilities (no static analysis needed until >1 action ships), and produces_artifacts (deferred alongside the artifact pipeline). TriggerDefinition (declarative, no handler): - type, description, config_schema, payload_schema - No handler field — firing is a single dispatcher's responsibility, not a per-trigger one. store.py contract for all three: - register_*: idempotent at process startup, raises on duplicate - get_*: returns None on miss - all_*: returns a defensive copy of the registry dict Verified by an inline smoke test (10 checks): empty initial state, registration and lookup work, duplicates raise, frozen dataclasses reject mutation, snapshots are copies, handlers are awaitable. Isolation invariant audit: grep across the full app/automations/ tree shows only three app.* imports, all of them ``from app.db import BaseModel, TimestampMixin`` in the model files. No imports from app.agents.*, app.services.*, app.tasks.*, app.routes.*, or any other business-logic module.
2026-05-26 22:54:17 +02:00
if trigger.type in _REGISTRY:
raise ValueError(f"Trigger already registered: {trigger.type!r}")
feat(automation): add empty Capability / Action / Trigger registries Three registries under app/automations/registries/, each as its own folder with the same SRP-per-file split (types.py for the dataclass, store.py for the in-memory dict + register/get/all functions). All three start empty; concrete entries land when the user signs off on which capabilities / actions / triggers to include (step 2). Capability (locked at v1-minimum five fields — see commit 2): - id, description, input_schema, output_schema, handler - CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] - Frozen, slotted dataclass (immutable post-registration). ActionDefinition (v1-trim of design plan §4): - type, name, description, config_schema, handler - Defers output_contract (handled per-step by agent_task's config.output_schema), uses_capabilities (no static analysis needed until >1 action ships), and produces_artifacts (deferred alongside the artifact pipeline). TriggerDefinition (declarative, no handler): - type, description, config_schema, payload_schema - No handler field — firing is a single dispatcher's responsibility, not a per-trigger one. store.py contract for all three: - register_*: idempotent at process startup, raises on duplicate - get_*: returns None on miss - all_*: returns a defensive copy of the registry dict Verified by an inline smoke test (10 checks): empty initial state, registration and lookup work, duplicates raise, frozen dataclasses reject mutation, snapshots are copies, handlers are awaitable. Isolation invariant audit: grep across the full app/automations/ tree shows only three app.* imports, all of them ``from app.db import BaseModel, TimestampMixin`` in the model files. No imports from app.agents.*, app.services.*, app.tasks.*, app.routes.*, or any other business-logic module.
2026-05-26 22:54:17 +02:00
_REGISTRY[trigger.type] = trigger
def get_trigger(trigger_type: str) -> TriggerDefinition | None:
return _REGISTRY.get(trigger_type)
def all_triggers() -> dict[str, TriggerDefinition]:
"""Defensive snapshot of the registry."""
feat(automation): add empty Capability / Action / Trigger registries Three registries under app/automations/registries/, each as its own folder with the same SRP-per-file split (types.py for the dataclass, store.py for the in-memory dict + register/get/all functions). All three start empty; concrete entries land when the user signs off on which capabilities / actions / triggers to include (step 2). Capability (locked at v1-minimum five fields — see commit 2): - id, description, input_schema, output_schema, handler - CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] - Frozen, slotted dataclass (immutable post-registration). ActionDefinition (v1-trim of design plan §4): - type, name, description, config_schema, handler - Defers output_contract (handled per-step by agent_task's config.output_schema), uses_capabilities (no static analysis needed until >1 action ships), and produces_artifacts (deferred alongside the artifact pipeline). TriggerDefinition (declarative, no handler): - type, description, config_schema, payload_schema - No handler field — firing is a single dispatcher's responsibility, not a per-trigger one. store.py contract for all three: - register_*: idempotent at process startup, raises on duplicate - get_*: returns None on miss - all_*: returns a defensive copy of the registry dict Verified by an inline smoke test (10 checks): empty initial state, registration and lookup work, duplicates raise, frozen dataclasses reject mutation, snapshots are copies, handlers are awaitable. Isolation invariant audit: grep across the full app/automations/ tree shows only three app.* imports, all of them ``from app.db import BaseModel, TimestampMixin`` in the model files. No imports from app.agents.*, app.services.*, app.tasks.*, app.routes.*, or any other business-logic module.
2026-05-26 22:54:17 +02:00
return dict(_REGISTRY)