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
Add a 'scope' TEXT column (default 'homelab') to the tools table so tools
can be namespaced by scope (e.g. "homelab:restart", "datacenter:reboot").
Backward-compat: bare name defaults to "homelab" scope.
Changes:
- Migration #1: ALTER TABLE tools ADD COLUMN scope
- store.Tool: add Scope field, update all SQL and scanTool()
- ipc.Tool DTO and request types: add Scope field
- CoreAPI interface: pass scope in ProposeTool/EnableTool
- storeAPI adapters: forward scope
- cmd/mavend/voice: pass scope (empty → homelab)
- cmd/mavweb/tools: show scope column in UI tables, hidden fields
- All tests updated for scope field
- Migration test made dynamic (startVer = len(migrations))
Two spine infra items (feature-ranking #1, part of the migration prereq):
- migrations.go: PRAGMA user_version runner, empty (no-op) migration slice,
one tx per step, fail-closed. Mechanism in place before any real schema
change needs it.
- crypt.go: file-level at-rest encryption. On-disk file is always AES-256-GCM
ciphertext; decrypted to a tmpfs working copy modernc sqlite operates on;
re-encrypted atomically on Close, plaintext wiped, key zeroed. Pure stdlib,
CGO stays off. Fails closed on wrong key/tamper, never falls back to
plaintext. Key is a 32-byte seam (config db_key_b64/db_key_env today; the
passkey-derived L3 cold-start key plugs into the same seam later).
Chosen over cgo SQLCipher (would force libsqlcipher + CGO across the project)
and over the ncruces page-level VFS (swaps the driver project-wide); noted as
the upgrade path in a ponytail: comment. Threat model is disk-at-rest only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>