refactor(automations): park manual trigger pending Run-now redesign

Manual-as-a-standalone-trigger conflates "user clicks Run now" with the
trigger model and forces ad-hoc input plumbing on the caller. Remove the
unreachable surface so the tree reflects reality (schedule is the only
v1 trigger).

- Unregister `manual`: drop import from triggers/__init__.py
- Delete `app/automations/triggers/manual/`
- Drop `RunService.dispatch_manual` (RunService is now read-only)
- Drop `POST /automations/{id}/run` and `RunDispatched` schema
- Keep `TriggerType.MANUAL` Python + PG enum value (reserved, documented)
  to avoid an Alembic round-trip when Run-now is redesigned
This commit is contained in:
CREDO23 2026-05-27 22:29:51 +02:00
parent 8fb65d7188
commit c0232fdcfe
13 changed files with 18 additions and 176 deletions

View file

@ -1,6 +1,6 @@
"""Triggers domain: registry surface + built-in trigger packages.
Each trigger lives in its own subpackage (``manual/``, ``schedule/``, ...) and
Each trigger lives in its own subpackage (``schedule/``, ...) and
self-registers at import time via its ``definition`` module.
"""
@ -17,4 +17,4 @@ __all__ = [
]
# Built-in triggers self-register at import time.
from . import manual, schedule # noqa: E402, F401
from . import schedule # noqa: E402, F401

View file

@ -1,11 +0,0 @@
"""``manual`` trigger: fired by a user clicking ``Run now``."""
from __future__ import annotations
from .dispatch import dispatch_manual_run
from .params import ManualTriggerParams
__all__ = ["ManualTriggerParams", "dispatch_manual_run"]
# Side-effect: register on the triggers store.
from . import definition # noqa: E402, F401

View file

@ -1,15 +0,0 @@
"""``manual`` ``TriggerDefinition`` registration."""
from __future__ import annotations
from ..store import register_trigger
from ..types import TriggerDefinition
from .params import ManualTriggerParams
MANUAL_TRIGGER = TriggerDefinition(
type="manual",
description="Fire on a user-initiated 'Run now' invocation.",
params_model=ManualTriggerParams,
)
register_trigger(MANUAL_TRIGGER)

View file

@ -1,72 +0,0 @@
"""Manual ``Run now`` dispatch adapter: load + guard, then call generic dispatch."""
from __future__ import annotations
from typing import Any
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.automations.dispatch import DispatchError, dispatch_run
from app.automations.persistence.enums.automation_status import AutomationStatus
from app.automations.persistence.enums.trigger_type import TriggerType
from app.automations.persistence.models.automation import Automation
from app.automations.persistence.models.run import AutomationRun
from app.automations.persistence.models.trigger import AutomationTrigger
async def dispatch_manual_run(
*,
session: AsyncSession,
automation_id: int,
runtime_inputs: dict[str, Any] | None,
) -> AutomationRun:
"""Find the automation + its enabled manual trigger, then run the generic dispatch.
``runtime_inputs`` is the caller-supplied payload (e.g. an HTTP body for a
"Run now" API call); it is merged with the trigger's ``static_inputs`` by
the generic dispatcher, with static winning on key collision.
"""
automation = await _load_automation(session, automation_id)
if automation is None:
raise DispatchError(f"automation {automation_id} not found")
if automation.status != AutomationStatus.ACTIVE:
raise DispatchError(
f"automation {automation_id} is {automation.status.value}, not active"
)
trigger = await _find_manual_trigger(session, automation_id)
if trigger is None:
raise DispatchError(
f"automation {automation_id} has no enabled manual trigger"
)
return await dispatch_run(
session=session,
automation=automation,
trigger=trigger,
runtime_inputs=runtime_inputs,
)
async def _load_automation(
session: AsyncSession, automation_id: int
) -> Automation | None:
stmt = select(Automation).where(Automation.id == automation_id)
return (await session.execute(stmt)).scalar_one_or_none()
async def _find_manual_trigger(
session: AsyncSession, automation_id: int
) -> AutomationTrigger | None:
stmt = (
select(AutomationTrigger)
.where(
AutomationTrigger.automation_id == automation_id,
AutomationTrigger.type == TriggerType.MANUAL,
AutomationTrigger.enabled.is_(True),
)
.limit(1)
)
return (await session.execute(stmt)).scalar_one_or_none()

View file

@ -1,9 +0,0 @@
"""``ManualTriggerParams`` — params for the ``manual`` trigger (empty in v1)."""
from __future__ import annotations
from pydantic import BaseModel, ConfigDict
class ManualTriggerParams(BaseModel):
model_config = ConfigDict(extra="forbid")