Files
Maven/internal/store/schema.sql
kami 1eca17f37b reminders: add recurring reminder support
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
2026-07-05 11:46:12 +04:00

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
);