Capture tasks, with one intake seam mail can call later (#130)
A task is not a fact and not a note. A fact is a claim about the world that a correction supersedes; a note is something to recall by meaning. A task is work with a lifecycle, and the read that matters is "everything outstanding right now" — which over an append-only log would mean replaying history on every question. So: a tasks table, migration #14, statuses candidate/open/done/dropped that each move forward exactly once. Dedupe is on normalised text among LIVE rows only, via a partial unique index. That is the property the mail side needs: an extractor may call CaptureTask for every message it reads, as often as it likes, without growing the list — while a weekly errand is still capturable again once the last one is done. Three ways in, one seam. ipc.CaptureTaskReq is it: the voice path (router.ParseTaskCapture on an explicit marker — "добавь в задачи …", never "надо бы поспать"), the /tasks form, and the email reader from #246 when it exists. Mail-derived items set Source "email:<account>", Status "candidate" and Evidence to whatever makes the row reviewable; a candidate is inert until he confirms it on /tasks, and Maven names it as unconfirmed when she recites the list rather than putting words in his mouth. No new intent — the router enum is a contract with the relabelling prompt, so capture rides the note intent and the list rides a query source, both matched deterministically like the calendar and plan matchers already are. Nothing here speaks. No tick rule reads tasks; the list is answered when asked about, which is why /tasks POST is not step-up gated the way /tools and /routines are — a task write moves no boundary. Vikunja #130
This commit is contained in:
@@ -131,6 +131,35 @@ ALTER TABLE reminders ADD COLUMN next_fire_ts INTEGER;`, // #2
|
||||
expires_ts INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_digest_entries_status ON digest_entries (status);`,
|
||||
|
||||
// #14 — the task capture store (Vikunja #130). Deliberately NOT facts:
|
||||
// a fact is a claim about the world that gets superseded, a task is a
|
||||
// piece of work with a lifecycle (captured → open → done), and the
|
||||
// prioritiser needs to read the live set cheaply.
|
||||
//
|
||||
// status: 'candidate' is a task Maven derived from something she read
|
||||
// (mail, later) and has NOT been confirmed by the owner; 'open' is a task
|
||||
// he actually stated (or confirmed). Nothing schedules or announces off
|
||||
// this table — capture is not a nag.
|
||||
//
|
||||
// norm is the normalised dedupe key. The unique index is PARTIAL, over
|
||||
// live rows only: re-capturing "купить молоко" after last week's one is
|
||||
// done must work, while the same mail arriving twice must not produce two
|
||||
// rows.
|
||||
`CREATE TABLE IF NOT EXISTS tasks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts INTEGER NOT NULL,
|
||||
text TEXT NOT NULL,
|
||||
norm TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
evidence TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('candidate','open','done','dropped')),
|
||||
due_ts INTEGER,
|
||||
weight INTEGER NOT NULL DEFAULT 0,
|
||||
resolved_ts INTEGER
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_live_norm ON tasks (norm) WHERE status IN ('candidate','open');
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks (status, created_ts DESC);`,
|
||||
}
|
||||
|
||||
// migrate applies every migration with a number greater than the DB's current
|
||||
|
||||
Reference in New Issue
Block a user