MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-364

6.6.1 Schema + TOTAL trigger/action registries + `automationRulesService` CRUD (admin-gated, enable/disable, caps) + routes

Done
Description

Estimate: 32m · Depends on: 6.1.1

The rule model and the authoring surface's backend. Pure backend — no UI, no engine yet.

Schema (FKs as Prisma relations, both sides): automation_rule — projectId, name, enabled, triggerType + typed triggerConfig JSON, conditionAst JSON (the 6.1 FilterAST shape, row-capped 20), actions JSON (ordered, max 10), ownerId (the rule actor — the recorded deviation), consecutiveFailureCount, timestamps; automation_rule_execution — ruleId, status enum (success / failure / no_actions), triggering workItemId (nullable — the item may be deleted later), error text, durationMs, createdAt; indexed [ruleId, createdAt] for the paged log + the retention sweep.

lib/automation/registry.ts — TWO TOTAL registries (mistake #29). Triggers: created / transitioned (optional from/to status-id narrowing) / field-changed (built-in-field picker config; assignee is the "assigned" preset) / commented — each entry = config schema + validate + the event name it consumes + the UI editor kind. Actions (this subtask: the shipped-substrate entries): transition (target status id) / set-field (built-ins the shipped workItemsService.update accepts — assignee, priority, due date, story points) — each entry = config schema + validate + execute fn signature + editor kind. Unknown trigger/action/field ids → typed 422; the enumeration test fails on any registry gap. Epic-5 entries (watcher / comment / label / custom-field) land in 6.6.3 as registry EXTENSIONS — same pattern as 6.1.1→6.1.2.

automationRulesService + routes (4-layer): create / update / enable / disable / delete / list / get, gated by the shipped 6.4 manage-project permission (viewers get 403/404 per the 6.4 read rules); validation composes the trigger/action registries + the 6.1.1 condition-AST validation (built-in fields here); caps — 100 rules/project, 10 actions/rule, 20 condition rows — as typed 422s; enable resets consecutiveFailureCount; delete cascades the execution log (Prisma onDelete).

Acceptance criteria

  • Migration applies cleanly (re-run: no drift); both tables modelled with relations on both sides per the CLAUDE.md FK rule.
  • Both registries are TOTAL with enumeration tests (every entry's config-schema × validate × editor triple); malformed/unknown configs → 422 (fuzzed, incl. smuggled field ids through the condition AST — the 6.1 injection posture extends here).
  • CRUD + enable/disable + caps behave per the description; admin-gating asserted per 6.4 role (admin yes, member no, viewer no); a rule referencing a since-deleted status/field degrades typed (the 6.1 stale-referent rule), never crashes validation.
  • pnpm test:coverage ≥90% on the new files.

Context refs

  • 6.1.1 lib/filters/ast.ts + registry (the condition substrate + the totality/422 pattern to mirror)
  • lib/services/projectAccessService.ts + the 6.4 role checks (the admin gate); workflowsService (status ids for transition configs)
  • The verified Jira anatomy/scope/actor facts in the Story 6.6 description
  • motir-core/CLAUDE.md (4-layer, FK-as-relation, required-tx)
Status
Done
Type
Sub-task