Add Slack mention trigger source
Epic 5336 — Event-driven Task triggers. Upstream: PRD = Task 5337 `prd.md`; Architecture = Task 5338 `architecture.md` (artifact 3566). Read §3.4 (natural dedup key), §3.6 (processor), and §4 "Matching a paused trigger" + "Slack (C5)".
## Original requirement (epic body, verbatim)
> Sources: … Slack mention …
## Requirement (PRD R7, verbatim)
A Slack trigger declares a project and a **channel** match (D11). An `@Jaah` mention in a matched channel files a Task, and Jaah replies in-thread with a link to it. That replaces today's "Mentions aren't supported yet" reply **only** where a trigger matches. Elsewhere the existing reply stays, reworded to say no trigger covers this channel. A mention from a Slack user not linked to a Jaah member of the org is `filtered` (A12).
R4: Slack exposes `{{slack.text}}`, `{{slack.user}}`, `{{slack.channel}}` and `{{slack.permalink}}`.
## Approach (architecture §4, settled)
- `slack_inbound`'s `app_mention` handler (~:486-500) calls `trigger_intake.ingest_slack(event)`: first `slack` trigger by id ascending whose `channel_ids` contains the channel — considering every trigger, paused or not; a matching Paused/Failed trigger TAKES the message and logs `skipped` "trigger paused" (no fall-through).
- Match → `received` row with `{slack: {text (mention stripped), user, channel, permalink, ts, thread_ts}}`, `slack_event_id`, natural dedup key `"<channel>:<ts>"`; fire `trigger-intake` early so AC-7.1's 30 s holds.
- No match → `unmatched` row (trigger NULL, `org_id = ORG_ZERO_ID`), reply reworded: "No Jaah trigger covers this channel — an org admin can add one under Automation › Triggers."
- User gate: `notify_targets.users_for_slack_member_id` (~:898) must return a member of the trigger's org, else `filtered` "Slack user not linked to a member of <org>".
- After filing, the processor posts a thread reply with the Task's console link through `slack_inbound._reply` (~:94-127). Socket Mode transport unchanged.
## Acceptance (PRD, verbatim)
- **AC-7.1** On dev, `@Jaah fix the login copy` in a matched channel files a Task, and the bot posts a thread reply with a working link within 30 s. → verify: do it in the dev Slack workspace.
- **AC-7.2** The same in an unmatched channel files nothing and gets the "no trigger" reply. → verify: observe.
Out of scope: Do NOT change the Slack transport (no Events API, no HTTP Slack ingress), Slack app scopes, or other Slack inbound handlers. Do NOT build the engine, console or other sources. PRD §5 (verbatim, binds): No deferred source in this epic. No skill attachment. No budget enforcement. No GitHub reads that decide filing. No public exposure beyond the two fenced paths. Cron behaviour is unchanged. No new licence for any persona other than advisor. Slack: no Events API and no HTTP Slack ingress. The Socket Mode transport is unchanged. Secrets: none in any prompt, task body, log or reply.
Done means:
- AC-7.1 — in the dev Slack workspace, with a dev Slack trigger on a test channel: post `@Jaah fix the login copy`; observe a Task filed whose body carries "fix the login copy" (mention stripped), and a thread reply within 30 s whose link opens that Task in the dev console. Cite timestamps.
- AC-7.2 — the same in an unmatched channel: no Task, thread reply is the reworded "No Jaah trigger covers this channel…" text, and an `unmatched` delivery row exists.
- Unit tests: first-by-id matching; a paused matching trigger takes the message (`skipped` "trigger paused", no fall-through); unlinked user → `filtered` with the readable reason; dedup on `<channel>:<ts>`; `{{slack.*}}` namespace renders.
- If the dev Slack workspace/app is unreachable to you, ASK (a human-only blocker), deliver the tests, and name the remaining oracle.
- `./dc check full` green.
Outcome
Slack @Jaah mention trigger source built (matching, unmatched/paused, sender gate, in-thread link reply); full gate green, security gate approved; PR #4692 merged. Remainder: dev AC-7.1/7.2 not run (needs a human Slack post; operator chose skip). Follow-up: channel-ownership check at trigger save.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Awaiting response
- Awaiting response
- Runner resumed
- Awaiting response
- Awaiting response
- Runner resumed
- Runner ended (done)