1eca17f37b
Add cron expression support for recurring reminders using robfig/cron/v3. Changes: - Migration #2: ALTER TABLE reminders ADD COLUMN cron TEXT + next_fire_ts INTEGER - Reminder struct: add Cron and NextFireTs fields - scanReminder helper extracts full row including nullable cron - CreateReminder: accept optional cron param, store next_fire_ts = fire_ts - DueReminders: query on next_fire_ts instead of fire_ts - RescheduleReminder: new method — parse cron, compute next fire, update next_fire_ts or mark fired if no more valid times - Dispatcher: call RescheduleReminder for cron reminders, MarkReminder for one-shots (preserving existing behavior for ID=0 digest skip) - ReminderCompleter interface: add RescheduleReminder method - storeAPI adapter: forward RescheduleReminder - All callers updated: CreateReminder signature includes cron param - Tests: TestRecurringReminder (store), TestDispatchRecurringReminderReschedules - Existing tests updated for new signature
84 lines
4.2 KiB
SQL
84 lines
4.2 KiB
SQL
-- maven core schema — append-only, three shapes.
|
|
-- a wrong fact is superseded, never overwritten. current value = latest non-voided row for a key.
|
|
|
|
PRAGMA journal_mode=WAL;
|
|
PRAGMA synchronous=NORMAL;
|
|
PRAGMA foreign_keys=ON;
|
|
PRAGMA busy_timeout=5000;
|
|
|
|
-- facts — substrate, all observations (self + env + config).
|
|
-- ts = valid-time (true-as-of), not insert-time.
|
|
-- source: tap:* | infer:* | poll:* | ambient | promote | feedback
|
|
-- confidence = 1.0 for taps only; <1 for inferred.
|
|
-- voids_id points at the fact this one cancels (correction).
|
|
CREATE TABLE IF NOT EXISTS facts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ts INTEGER NOT NULL, -- unix epoch millis (valid-time)
|
|
kind TEXT NOT NULL CHECK (kind IN ('self','env','config')),
|
|
key TEXT NOT NULL,
|
|
value TEXT NOT NULL, -- json if structured
|
|
source TEXT NOT NULL,
|
|
confidence REAL NOT NULL DEFAULT 1.0 CHECK (confidence > 0.0 AND confidence <= 1.0),
|
|
voids_id INTEGER REFERENCES facts(id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_facts_key_ts ON facts (key, ts DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_facts_voids ON facts (voids_id);
|
|
|
|
-- reminders — user intent, fires once or recurring (if cron set).
|
|
-- relative→absolute happens at capture ("in 4h" → store now+4h, never the string).
|
|
CREATE TABLE IF NOT EXISTS reminders (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_ts INTEGER NOT NULL, -- epoch millis
|
|
fire_ts INTEGER NOT NULL, -- epoch millis
|
|
payload TEXT NOT NULL, -- json
|
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','fired','cancelled'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_reminders_fire ON reminders (fire_ts) WHERE status = 'pending';
|
|
CREATE INDEX IF NOT EXISTS idx_reminders_status ON reminders (status);
|
|
|
|
-- nudges — every proactive send + outcome. this table IS the restraint memory.
|
|
CREATE TABLE IF NOT EXISTS nudges (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ts INTEGER NOT NULL, -- sent ts, epoch millis
|
|
rule TEXT NOT NULL,
|
|
channel TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
outcome TEXT NOT NULL DEFAULT 'pending' CHECK (outcome IN ('pending','acted','snoozed','ignored')),
|
|
outcome_ts INTEGER
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_nudges_rule_ts ON nudges (rule, ts DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_nudges_outcome ON nudges (outcome);
|
|
|
|
-- notes — semantic recall/preference store. no predicate reads these (that's
|
|
-- what facts are for); query-answering does brute-force cosine over embedding.
|
|
-- embedding is a little-endian float32 BLOB. no index — personal-scale scan.
|
|
CREATE TABLE IF NOT EXISTS notes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ts INTEGER NOT NULL, -- epoch millis
|
|
text TEXT NOT NULL,
|
|
embedding BLOB NOT NULL, -- little-endian float32[]
|
|
source TEXT NOT NULL
|
|
);
|
|
|
|
-- tools — the act allowlist maven executes. proposed rows are scaffolds maven
|
|
-- drafts when she hits an act she can't run; enabling (filling cmd + flipping
|
|
-- status) is a human act through an authed surface, NEVER the voice path. a
|
|
-- proposed row drives nothing — the executor only runs status='enabled' rows.
|
|
CREATE TABLE IF NOT EXISTS tools (
|
|
name TEXT PRIMARY KEY, -- the spoken verb ("restart")
|
|
cmd TEXT NOT NULL DEFAULT '[]', -- json argv prefix; args appended at run
|
|
destructive INTEGER NOT NULL DEFAULT 0, -- 1 ⇒ needs a confirm turn before it runs
|
|
status TEXT NOT NULL DEFAULT 'proposed' CHECK (status IN ('proposed','enabled')),
|
|
utterance TEXT NOT NULL DEFAULT '', -- the utterance that scaffolded a proposal (provenance)
|
|
created_ts INTEGER NOT NULL,
|
|
updated_ts INTEGER NOT NULL
|
|
);
|
|
|
|
-- presence_state — the only stateful bit of presence (a pure function otherwise).
|
|
-- hysteresis bucket; updated each tick after resolve().
|
|
CREATE TABLE IF NOT EXISTS presence_state (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1), -- singleton row
|
|
last_bucket TEXT NOT NULL CHECK (last_bucket IN ('present','away')),
|
|
last_score REAL NOT NULL,
|
|
updated_ts INTEGER NOT NULL
|
|
); |