Add Task source column and filter

Task 5363 · Done · Coder · created · updated

Epic 5336 — Event-driven Task triggers. Upstream (read, don't re-derive): PRD = Task 5337 artifact `prd.md`; Architecture = Task 5338 artifact `architecture.md` (id 3566) — fetch with `task_read` / `task_read_artifact`. § numbers below are the architecture's. ## Original requirement (epic body, verbatim) > a `source` column on the filed Task so the board filters by origin. ## Requirement (PRD R17, verbatim) A `source` column on `tasks`, set at filing and never changed. Values: `console`, `cron`, `agent`, and `trigger:<kind>` (`trigger:webhook`, `trigger:github`, `trigger:gmail`, `trigger:slack`). Existing rows are backfilled: `created_by_cron_id` set → `cron`; `created_by_task_id` set → `agent`; else `console`. A trigger-filed Task also links to its trigger. ## Approach (architecture §3.1 `tasks` C1 part, §3.2, §3.5 last paragraph, §7 "The board") - Migration: `tasks.source` text NOT NULL, CHECK in (`console`,`cron`,`agent`,`trigger:webhook`,`trigger:github`,`trigger:gmail`,`trigger:slack`), server default `'console'` (additive rule — an old release still inserting stays valid). Backfill in batched UPDATEs, in this order: (1) a row with a parent epic takes the epic's source (after the epics are backfilled); (2) `created_by_cron_id` set → `cron`; (3) `created_by_task_id` set → `agent`; (4) `console`. Index (`project_id`, `source`). - New `app/task_source.py` is the single writer: `SOURCES`, `SOURCE_LABELS = {console: Console, cron: Schedule, agent: Agent, trigger:webhook: Webhook, trigger:github: GitHub, trigger:gmail: Email, trigger:slack: Slack}`, `trigger_source(kind)`, and `source_for(parent_epic, filer_source)` — a row with a parent epic takes `epic.source`, otherwise the filer's own. - `routes/tasks.py::new_top_level_task` (~:7017) gains required keyword `source: str`, validated against `SOURCES`; every caller passes one (console create → `console`, cron runner → `cron` — the ONLY permitted cron-runner edit, agent `task_create` → `agent`). Every child-row creation path (BA/architecture/plan/coder/finish children, plan/finish filings under an epic) applies `source_for`. - Task read gains `source` + `source_label`. `GET /api/tasks` gains repeated `source` query param, applied in `board_filtered_select` (~:8337) as `Task.source.in_(...)`, same as `created_by_id`. `GET /api/tasks/sources` serves the labels — the web never copies them by hand. - Web: `apps/web/src/lib/taskUrlFilters.ts` + `components/tasks/shared.tsx`: URL param `source` (repeated) → wire param `source`; a multi-select showing the readable labels; counts in the toolbar filter badge; combines with existing filters. ## Acceptance (PRD, verbatim) - **AC-17.1** `/tasks` on dev has a **Source** filter (multi-select, shown as readable labels: "Console", "Schedule", "Agent", "Webhook", "GitHub", "Email", "Slack"). It is reflected in the URL, and it combines with the existing filters. → verify: filter to "Webhook" and observe only trigger-filed Tasks. Reload the URL and observe the filter kept. - **AC-17.3** Every pre-existing Task has a non-null source after migration. → verify: on the dev DB, `select count(*) from tasks where source is null` returns 0. Note: no trigger-filed Task exists yet in this phase, so verify AC-17.1 with "Schedule" and "Console" (the Webhook end-to-end re-verify is owned by the Triggers-console child). The "Webhook" option must still render and return an empty state worded as a sentence, never a blank list. Out of scope: Do NOT touch the `crons` table, cron health, or the cron runner except setting `source='cron'` at filing. Do NOT build any trigger table, route or UI (later children). PRD §5 (verbatim, binds): No deferred source in this epic (no GitLab, Jira, Linear, PagerDuty or CloudWatch receivers, no `jaah-handoff` skill or personal-access-token mechanism). No skill attachment. No budget enforcement. No GitHub reads that decide filing (ADR-0112). No public exposure beyond `/api/public/triggers/*` and `/api/public/email/gmail-push` on dev. Cron behaviour is unchanged. No new licence for any persona other than advisor. Slack: no Events API and no HTTP Slack ingress. Secrets: no webhook secret, bearer token or credential in any prompt, task body, log, digest, deliveries log or artifact. R16 (memory/last report) is dropped entirely. Done means: - AC-17.3 — run `select count(*) from tasks where source is null` on the dev DB; observe 0. Also a migration test showing each backfill rule (epic child inherits epic; cron; agent; console). - AC-17.1 — on dev in a real browser: open `/tasks`, select "Schedule", observe only cron-filed Tasks; add a second label and combine with `creator`; reload the URL and observe the filter kept; observe readable labels (never raw `trigger:webhook` codes) and the badge count. - A test that every `new_top_level_task` caller passes a valid `source`, and that an epic's BA/architecture/plan children carry the epic's source. - A test that `GET /api/tasks?source=cron&source=console` filters server-side and `GET /api/tasks/sources` returns all seven labels.

Outcome

Added tasks.source (revision 0383 with backfill, every epic child inherits its epic's source), source filtering on /api/tasks, its counts and tag-counts, GET /api/tasks/sources, and the /tasks Source filter. Full gate green; PR #4670 merged. Dev-DB and dev-browser checks are still to run after deploy.

Pull request

Artifacts

Activity

  • Draft
  • Ready
  • Runner started
  • Runner ended (done)