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-363

6.6 Automation rules

Done
Description

The when/then rule engine, project-scoped: a rule = one trigger → one flat condition group → an ordered action list, evaluated asynchronously off the channel-agnostic job events (Epics 2+5) through the Story 1.6 Inngest pipeline — the queued execution model the mirror runs (verified: Jira rules execute through a rule-processing queue, never inline). Actions execute through the SHIPPED services — workflowsService transitions, workItemsService field writes, watchersService, commentsService, labelsService, customFieldsService — never raw writes, so workflow legality, permissions, revision rows, and downstream notifications behave exactly as if a person did it.

The verified core sets (rung 1 — Atlassian cloud-automation docs, 2026-06-10). Triggers: work item created / transitioned (with optional from→to status narrowing) / field value changed (field picker; "assigned" is its assignee preset) / commented — Jira's four most-used issue-event triggers. Actions: transition, set field (built-ins + every 5.3 custom-field type), add watcher, add comment, add label — the stub's set grown to Jira's verified top tier. Conditions: ONE flat match all / match any group of 6.1 FilterAST rows evaluated against the triggering item — the issue-fields/JQL-condition analogue, reusing the 6.1.1/6.1.2 TOTAL operator registry + safe compiler wholesale (zero new predicate machinery; the registry stays the single predicate authority).

The rule actor (recorded deviation). Jira defaults rule attribution to a synthetic "Automation for Jira" user with a configurable actor; ours runs actions as the rule owner (the project admin who created it — Jira's own configurable-actor shape minus the synthetic default), because a per-workspace synthetic user would leak into every member-bounded surface the shipped reads enumerate (assignable members, mention pickers, boards) for no stated use case. The audit log + event provenance keep attribution honest; the dedicated system actor is the documented extension.

Loop prevention (the Jira default, hard-shipped). Every event emitted from an automation-executed action carries viaAutomationRuleId provenance, and the engine NEVER fires a rule off a provenance-carrying event — the verified Jira default (rules don't trigger rules). The "Allow rule trigger" opt-in chain + depth-10 loop detection is the documented extension when a chaining use case lands.

Real-product operations (finding #57 — bounded everywhere). A per-rule execution audit log (automation_rule_execution: success / failure / no-actions-performed, error detail, duration, the triggering item), paginated, with the Jira-verified 90-day retention enforced by a cron sweep; auto-disable at 10 consecutive failures (the Jira number) with the disabled state surfaced in the UI; an owner error email on the first failure after a success (the verified dedupe rule) via the 1.6 email pipeline; idempotent execution per (event × rule) so Inngest replays never double-fire actions; caps — 100 rules per project, 10 actions per rule, the 6.1 20-row condition cap — each a typed 422 with a designed state, never a silent truncation.

Out of scope (documented extension slots, each justified): scheduled/cron triggers (need a saved-filter item-selection substrate — Story 6.2 ships it; revisit after); branches + smart values (where a rule engine becomes a scripting language); global / multi-project rules (Jira gates these on site admins; Motir's admin substrate is project-scoped today); the "Allow rule trigger" chaining opt-in (above); send-email / webhook / create-item actions (each drags a new surface — templates, outbound HTTP policy, kind-parent rules — additive registry entries when a use case lands); monthly usage quotas (the open-core billing boundary — Epic 8.1 owns metering); a manual "run rule now" trigger (cheap but unowned by any use case yet).

Verification

  • Pull the Story branch, pnpm install, pnpm prisma migrate dev (the 6.6.1 automation tables apply cleanly; re-run reports "No difference detected"), pnpm db:seed, pnpm dev (+ the Inngest dev server per the E2E harness notes).
  • pnpm test:coverage — Vitest (real Postgres) over the registries, the engine (trigger × condition × action matrix, loop prevention, idempotency, auto-disable), and the CRUD service ≥90% per-file branch/fn/line.
  • Author flow: sign in as zhuyue@motir.co / !QAZ1qaz → project settings → Automation (matching design/projects/automation.mock.html). Create: "when an item transitions to Done, if Kind is any of (Bug), then add watcher Bo + comment 'Verify the fix'" — the editor builds when/if/then rows from the registries; the rule lists enabled with owner + last-run.
  • Firing: move a Bug to Done in the board → within the async window the watcher appears, the comment lands attributed to the rule owner, and the rule's audit log shows a Success row (trigger item, duration); a non-Bug transition logs no-actions-performed (condition gated); a viewer (6.4 role) cannot see or edit the Automation settings surface.
  • Set-field + CF: a field-changed-trigger rule (Priority changed → set CF "Severity" to High) fires off a priority edit and writes the CF value through the 5.3 service (revision row + rail update included); the assignee preset fires on assignment.
  • Loop prevention: a rule whose action would satisfy its own trigger (transition → transition) executes ONCE — the provenance-carrying event is skipped, the audit log shows the single run, no cascade.
  • Failure handling: point a rule's transition action at a workflow-illegal target → the run logs Failure with the typed error; the owner email arrives once (first-failure-after-success dedupe); after 10 consecutive failures the rule shows auto-disabled with the designed banner; re-enabling resets the counter.
  • Caps + retention: the 101st rule / 11th action / 21st condition row each hit the typed 422 + designed state; the retention sweep deletes only audit rows older than 90 days (spot-check with a back-dated row).
  • pnpm test:e2e --grep automation — Playwright over the real stack: the author → fire → audit journey.
  • a11y check: the Automation settings pages (list, editor with every row kind open, audit log) pass the strict axe sweep; fully keyboard-operable; colour via --el-*, shape via element tokens.