Commit Graph

88 Commits

Author SHA1 Message Date
kami 4c40a183cf feat: event store, pattern inference, and routine proposals
- Add events table (migration #4): stores normalized (action, object, ts)
  triples extracted from facts, indexed for recurrence detection.
- Add proposed_routines table: stores inferred recurring patterns with
  proposed/accepted/dismissed status and optional linked reminder.
- Add pattern package: Extractor normalizes fact text into (action, object)
  pairs with TTS normalization; Detector groups events to find recurring
  patterns and proposes routines.
- Add internal/ttsnorm: text normalization pipeline for Russian/English
  (lowercase, punctuation strip, number normalization, stopword removal).
- Add chat seed file for LLM phraser.
2026-07-10 15:49:03 +04:00
kami c0c11cd36d feat: LLM phraser, shared LLM client, and LLM replier
- Add llmphraser: LFM-based phraser implementing Phraser interface with
  PhraseChat, PhraseNudge, PhraseReactive, and PhraseReminder methods.
- Add shared internal/llm/client: llama-server completion client used by
  both the phraser (talking back) and router (routing), sharing one model.
- Add LLMReplier in mavend: replaces StubReplier for chat/nudge/reactive
  replies, falls back to stub on model errors.
- Update Phraser interface: add PhraseChat method, update stub to match.
- Wire LLM phaser into mavend voice init, plumb LLM config from JSON.
2026-07-10 15:48:55 +04:00
kami 28a940ebbe feat: LLM router with chat intent and Cyrillic wake-word support
- Add LLMRouter: grammar-constrained LFM call for intent classification
  after stage-0, before classifier cascade. Errors fall through gracefully.
- Add IntentChat: conversational intent with no store side-effect, routed
  through LLM -> phraser chat endpoint.
- Extract slots for Chat: no structured slots, full utterance is payload.
- Extend stage-0 grammars to fire through Cyrillic wake-word spellings
  (Мэйвен/Мейвен/Майвен/etc.) produced by Russian STT model.
- StripWakeToken helper strips leading wake in any script so time/date
  grammars still match when wake is present.
- Add classifier examples for chat utterances (EN + RU).
- Wire LLMRouter into Router.Config; optional, nil-safe.
2026-07-10 15:48:48 +04:00
kami 6bab68e96d reminders: add ListReminders IPC + /reminders web page
Store layer: ListReminders returns the n most recent reminders
(newest first). IPC: new MethodListReminders wired through server,
client, and lockedAPI. Web: /reminders page with table of created
time, fire time, status badge, and payload text; empty state with
prompt to ask maven for a reminder. Sidebar entry under Automation.
2026-07-06 22:12:10 +04:00
kami d493be34b2 dateparser: python shell-out with two-step parse + russian qualifier pre-processing
PythonDateParser shells out to python3 with the dateparser library for
full natural-language date/time extraction. Two-step approach:
1. search_dates() finds the date substring in surrounding text
2. parse() re-parses the substring for correct time resolution

Russian time qualifiers (утра/вечера/дня/ночи) are pre-processed to
AM/PM before parsing — dateparser drops them during substring extraction.

Falls back to StubDateTimeParser when python3 or dateparser isn't
available (graceful degradation, no hard runtime dependency).

Dockerfile updated: python3 + dateparser==1.4.1 in runtime stage.
2026-07-06 19:09:09 +04:00
kami bf4009ca4f voice/seeds: track seed files, add russian time parser, guard replySystem
five fixes spotted during routing investigation:

- gitignore: replace blanket models/ ignore with per-dir exceptions
  (/models/embedder/, /models/stt/, /models/tts/) so the seed text
  files under models/seeds/ are tracked in version control
- query.txt: fix merged line — 'сколько стоит свет в этом месяце' and
  'найди заметку про сервер' were fused with no separator
- reminder.txt: add 11 pure-verb reminder seeds without time expressions
  to shift centroid toward the reminding intent rather than time-lexicon
- StubDateTimeParser: add Russian 'через <N> <unit>'/'через час'/'через
  полчаса', 'сегодня'/'завтра'/'послезавтра' with optional clock, and
  'в <clock>' scan. Add Russian word numbers (один-десять) and unit
  inflections (час/часа/часов, минута/минуты/минут, день/дня/дней,
  неделя/недели/недель). Also adds missing English day/week units.
- replySystem: guard time branch against duration queries ('сколько
  времени прошло') reaching it via the classifier path after the
  stage-0 grammar's build filter rejects them. Mirrors stage0.go
  duration keywords.
2026-07-06 18:38:48 +04:00
kami ce3d8e65f2 voice/routing: fix time-query misroute (seed collision, threshold, stage-0 grammars)
three bugs causing time queries to land on reminder or fact intent:

- seed collision: query.txt and system.txt shared identical time/date
  seeds (который час, сколько времени), making system intent
  indistinguishable from query intent in centroid space
- threshold (0.35) too low for ONNX embedder — cosine similarities
  cluster 0.5-0.7 for related intents, so Clarify never fired
- reminder centroid contaminated by time-lexicon (every seed has a time
  expression), pulling any time-word utterance toward reminder intent

fixes:
- remove 3 duplicate time/date seeds from query.txt (keep in system.txt)
- DefaultRouterThreshold 0.35 -> 0.55
- stage-0 grammar for напомни/remind me -> IntentReminder, bypasses
  classifier (fixes 'напомни через час' being misrouted to fact)
- stage-0 grammars for time/date system queries (сколько времени,
  который час, какой сегодня день) -> IntentSystem, with Build filter
  to exclude elapsed/duration queries (сколько времени прошло)
- time parser fallback in applyAction for stage-0 reminder matches
  (extractor doesn't run on stage-0 decisions)
2026-07-06 18:23:13 +04:00
kami fe2903e878 progress+roadmap: sync with third-session work, mark stale items
PROGRESS.md:
- remove Kuma from 'Wired but needs deploy' (key wired in eda434f)
- remove cold-start unlock from 'Not built yet' (built in b0932a1+15fe7bb)
- add third-session block: cold-start unlock, conversation depth,
  routing+persona — with the cold-start test gap called out
- update gap #2: anaphora + cross-intent landed (05236ad)
- update gap #6: note download-embedder + query_min_score knob
- remove 'Personality prompt' from Future (landed in b7eb53a)
- add caveat: cold-start unlock tests missing (3 required cases absent)

ROADMAP.md:
- add Status line under every item header with commit SHAs + verdict
- summary table gains a Status column
- replace stale 'Recommended order' with 'Remaining work' priority list
2026-07-06 14:40:18 +04:00
kami 5d1850c203 fix: mavwaked is a client binary, not a docker daemon
- Revert Dockerfile: no mavwaked build, no alsa-utils runtime dep
- Revert .dockerignore: no /mavwaked entry
- PROGRESS.md: deploy note says systemd user unit on a client box
  (desk PC, pi), connects to mavend over wg or local net — never on
  the homesrv or in docker
2026-07-06 14:12:33 +04:00
kami c822cdf673 progress: always-on listening (P3.1 MVP), update gap + deploy notes 2026-07-06 14:10:12 +04:00
kami e57647c9a3 3.1 always-on listening: mavwaked with energy VAD + SurfaceVoice
New cmd/mavwaked — always-on voice listening client that:
- Captures PCM from arecord subprocess (16kHz mono int16)
- Runs energy-based VAD in 30ms windows (RMS threshold, adaptive floor)
- Buffers utterances (300ms min speech, 800ms silence end, 10s max)
- Sends complete utterances as PushToTalk with Surface=SurfaceVoice (L0)
- Plays reply audio through aplay subprocess
- No new CGo/onnxruntime deps — pure Go
- 10 VAD tests with -race (speech detect, silence, max duration, reset, adaptive floor)
- Makefile build-waked target + Dockerfile integration + alsa-utils runtime dep
2026-07-06 14:09:34 +04:00
kami 05236ad480 3.2 conversation depth: cross-intent anaphora + fact-by-key query
- session.go: add History []Turn + Turn type for multi-turn context
- slots.go: add AnaphoraResolver with Resolve() for RU pronoun detection
  (это/он/она/оно/тот/мой and inflected forms)
- followup.go: extend followUpMerge with cross-intent inheritance:
  Query/Fact/Reminder after a Fact with anaphora inherits the key.
  Same-intent path unchanged. Anaphora detection from utterance.
- voice.go: add fact-by-key lookup path in applyAction for IntentQuery
  when dialogue resolved an anaphoric reference (calls LatestFact,
  formats with formatTime helper). History tracked in Session.History
  capped at 4 most recent turns.
- followup_test.go: 7 new test cases: anaphora query-after-fact,
  no-inheritance-without-anaphora, three-turn break, anaphora in
  reminder, anaphora in fact, explicit key wins, time inheritance.
  make test green (303+, -race, all 29 packages).
2026-07-06 13:40:16 +04:00
kami b7eb53a3b4 4.1 routing quality + 4.4 persona prompt
- VoiceConfig: add QueryMinScore (default 0.55) + Persona config fields
- voice.go: remove queryMinScore const, wire from cfg.Voice.QueryMinScore
  as reactiveHandler field
- llmphraser.go: add Persona to Config, prepend to system prompts in
  chat and query paths (systemPrompt/querySystemPrompt methods)
- main.go: pass personaFromCfg into both phraser config blocks
- Makefile: add download-embedder target (Xenova/paraphrase-multilingual-
  MiniLM-L12-v2, ~90MB ONNX)
- AGENTS.md: document embedder model download + libonnxruntime setup
- server.go: fix pre-existing wg.Add vs wg.Wait data race using accept
  mutex. make test green, zero races across all 29 packages.
2026-07-06 13:35:39 +04:00
kami 15fe7bbc74 mavweb: passkey enrollment wraps encryption key, assertion unlocks daemon
- webauthn.go: keyIPC interface for StoreEncryptionKey/Unlock, wired
  through PasskeyHandle. RegisterFinish calls StoreEncryptionKey with
  the credential's public key after successful enrollment. AssertFinish
  calls Unlock with the stored public key after assertion (alongside
  existing AssertStepUp call).
- server.go: fix data race on s.api by switching from bare CoreAPI field
  to atomic.Value. SetAPI uses Store(), dispatch uses Load(). No more
  race-flagged tests.
- make test green (303+, -race)
2026-07-06 13:29:31 +04:00
kami b0932a19df cold-start unlock: key wrap/unwrap, locked-mode daemon, IPC unlock methods
- internal/webauthn/keywrap.go: HKDF-SHA256 + AES-256-GCM WrapKey/UnwrapKey
- internal/ipc/: MethodStoreEncryptionKey/MethodUnlock wire, api structs,
  server dispatch callbacks (WrapKeyFn/UnlockFn), client stubs
- internal/config/config.go: DefaultWrappedKeyPath() method
- cmd/mavend/main.go: locked-mode boot path - detects wrapped key, starts
  locked with lockedAPI stub, wires UnlockFn that opens store + replaces
  CoreAPI on passkey assertion. env-key path stores WrapKeyFn for enrollment.
  make test green (303+, -race)
2026-07-06 13:24:49 +04:00
kami eda434fe0b ops: kuma api key, voice verification, desk_active doc
- created kuma API key uk5_mavpoll-key, wired into mavpoll
- fixed basic auth field (kuma expects key as password, not username)
- switched mavpoll to network_mode: host (compose bridge can't reach host)
- fixed stale voice bind comment in docker-compose.yml
- verified voice listening on :9100, cross-container reachable
- updated PROGRESS.md ops footnote
- added ROADMAP.md
2026-07-06 13:13:23 +04:00
kami 59a4e06615 maven: scheduled routines + persistent long-term memory
Two additive proactive/recall features.

Routines (internal/routine): a third proactive class beside reminders
(user-stated) and care rules (world-state) — operator-declared clockwork.
config.routines[] (cron + literal RU body + severity) fire through the
normal dispatcher on schedule. Bodies are literal, not LLM-phrased (can't
hallucinate); rule name routine:<name> keeps them out of the care
autotuner; a cold-start guard seeds on first sight so a restart never
replays a missed schedule. Pure routine.Due + config validation, unit-
tested; the tick driver holds the last-fired map and calls fireRoutines.

Persistent memory (internal/store/memory.go): store.MemoryStore backs the
memory.Store interface with the SAME encrypted sqlite db — survives
restarts and recall text inherits at-rest encryption (no plaintext
sidecar). float32-blob vectors, brute-force cosine (ANN is a later swap
behind the interface), upsert-by-id. The daemon wires st.VectorMemory()
into wireVoice; the in-memory impl stays the test/no-store floor. Closes
the "in-memory only, lost on restart" gap (PROGRESS #8).

Gate green: gofmt/vet clean, -race across routine/config/store/mavend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2PNdwDj2Gt8YW294J7oSc
2026-07-06 12:37:28 +04:00
kami 186bbb960b progress: dialogue and memory now wired (tasks 6/7 follow-up)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:15:56 +04:00
kami b25377b6ca maven: recall long-term memory in the query path (task 7)
Task 7 inserted note embeddings into the memory Store but nothing read them
back, and facts weren't indexed at all. Complete the read side:

- Facts are now embedded and inserted into memStore on capture (best-effort,
  never fails the fact write) — the notes table can't answer fact questions
  ("когда я пил воду?"), so memStore is their only recall path.
- Insert meta now carries text/ts/type so a Search hit is self-describing.
- IntentQuery consults memStore.Search after notes-RAG misses and before the
  general-knowledge phraser fallback (bestRecall, unit-tested). Strictly
  additive: it only runs once the notes path has already given up, so it can't
  regress existing recall. Note hits here overlap notes-RAG by design; the
  payoff is fact recall and a real read seam for a future persistent backend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:15:18 +04:00
kami 388d4257ee maven: wire dialogue slot carry-over into the voice path (task 6)
The dialogue library (internal/dialogue, task 6) shipped tested but unwired.
Wire it: reactiveHandler now holds a 2-min SessionStore, and each turn fills
its missing slots from a prior same-intent, non-expired turn via InheritSlots
before acting, then records itself for the next follow-up. Single-user box →
one session slot (voiceDialogueID).

Guardrails (followUpMerge, unit-tested): only same-intent turns inherit (a new
intent is a fresh command); clarify turns and expired/nil priors never inherit;
InheritSlots fills gaps only, so a fully-slotted turn is untouched; the fact
Value (router-only) survives the dialogue.Slots round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:15:17 +04:00
kami 9c2ccad7ef progress: mark revert step-up gate and go.mod deps as closed
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:04:36 +04:00
kami f2b97e4ab9 go.mod: mark onnxruntime_go, websocket, cron as direct
All three are imported by non-test code (router/onnxembedder.go,
mavweb/main.go, store/reminders.go) but were labeled // indirect. `go mod
tidy` can't run in this repo (it walks the vendored deps/go toolchain tree
and errors), so correct the labels by hand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:04:12 +04:00
kami 4793d77fa9 mavweb: gate /api/revert behind passkey step-up
RevertFact voids the latest fact for a key — a store mutation — but
/api/revert had no step-up gate, while POST /tools required L3. Close the
inconsistency: thread the same *webauthn.PasskeySession into handleRevert
and reject with 403 when a configured session isn't asserted. nil session
(WebAuthn unconfigured) keeps prior behavior — transport-level auth only.

Tests: un-asserted session → 403 and RevertFact not called; asserted → 200.
The RevertFact mock now records its key so the gate assertion is meaningful.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:04:11 +04:00
kami af0a0d07cd progress: refresh for overnight-jul6 (calendar/weather/knowledge query surface)
Add the jul6 done-section (7 tasks), bump date/LOC/test counts (303 tests),
and correct the now-stale gap claims: query surface #5 (calendar+weather+
knowledge landed), act allowlist #4 (seeded), dialogue scaffold #2 and
memory interface #8 (exist but unwired/in-memory only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 11:57:54 +04:00
kami 9488225556 maven: dedupe knowledge prompt to router.KnowledgePrompt (task 4 follow-up)
Task 4 created and tested router.KnowledgePrompt() but the live path in
LLMPhraser.PhraseQuery used a separate hardcoded copy of the same RU
anti-hallucination prompt, leaving KnowledgePrompt() as dead code and two
strings that could drift. Point the phraser at the tested helper so there
is a single source of truth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 11:51:31 +04:00
kami 9ffefd9e8c maven: gofmt handlers_test.go (fix stale alignment)
The Task-7 verification commit (d52f60c) added the CalendarEvents mock
method but left fakeCore's struct block misaligned, so `gofmt -l` still
flagged this file despite the "all gates green" claim. Realign it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 11:49:03 +04:00
kami cda614d1bb maven: fix нью-йорк transliteration in weather location map 2026-07-06 11:01:49 +04:00
kami 5934a8a644 maven: update session status board with commit SHAs 2026-07-06 04:20:41 +04:00
kami d52f60c54e maven: fix test mocks for CalendarEvents interface (verification)
- Add CalendarEvents method to recordingAPI in auth_test.go
- Add CalendarEvents method to fakeCore in handlers_test.go

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:20:16 +04:00
kami 880715fad4 maven: long-term memory vector-store interface (task 7)
- New internal/memory/ package: Store interface, InMemoryStore (cosine sim)
- Tests: insert→search roundtrip, topK truncation, empty store, cosine edges
- Wire into voice: memStore on reactiveHandler, insert note embedding after WriteNote
- Memory Insert is best-effort, log-and-continue on error

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:18:50 +04:00
kami 79eb43e9b9 maven: dialogue state scaffold (task 6)
- New internal/dialogue/ package: Session, SessionStore, InheritSlots
- Session with Intent, Slots, Timestamp, TTL
- SessionStore: in-memory map with TTL expiry, thread-safe
- InheritSlots: carries forward slots from previous turn
- Tests: expiry, store put/get/expiry, default TTL, slot inheritance

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:16:47 +04:00
kami e030466cac maven: weather module skeleton with open-meteo provider (task 5)
- New internal/weather/ package: Provider interface, Weather struct, StubProvider
- OpenMeteoProvider with geocoding + current weather (keyless, free API)
- Config: WeatherConfig in VoiceConfig (provider, default_location)
- Wire in voice.go as weatherProvider on reactiveHandler
- Handle weather queries in IntentQuery (before notes RAG)
- Helper: isWeatherQuery / extractWeatherLocation
- Tests: mocked HTTP round-trip for OpenMeteo, stub ErrNotConfigured, config tests
- No real network calls in any test

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:15:54 +04:00
kami 428af3f3c6 maven: general-knowledge phraser routing (task 4)
- Stub.PhraseQuery: empty notes → "не знаю." (was: "no notes")
- LLMPhraser.PhraseQuery: empty notes → general knowledge prompt to LLM
- Voice handler: on notes RAG failure, try phraser before giving up
- New router.KnowledgePrompt() pure function with test

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:11:54 +04:00
kami cf066bde97 maven: calendar event querying (task 3)
Store: CalendarEvents(ctx, from, to) — filters caldav facts by key date prefix.
IPC: full wiring through interface, server dispatch, and client.
Router: ParseCalendarDate (сегодня/завтра), CalendarEventFormatter (RU reply).
Voice: calendar detection before notes RAG in IntentQuery handler.
Tests: store integration test, date parser tests, formatter tests.

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:09:47 +04:00
kami 3b8fb691cb maven: seed safe tool allowlist (task 2)
- Add 12 homelab tools to deploy/mavend.json (6 read-only, 6 destructive)
- Add matching RU seed phrases to models/seeds/act.txt
- Guardrail verified: no destructive tool marked destructive=false

Scope: homelab. Read-only: status, ps, uptime, disk, memory, logs.
Destructive: restart, stop, start, docker-restart, docker-stop, reboot.

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:04:17 +04:00
kami b778f0bbec maven: embedder config validation + docs (task 1)
- Tests: all-three-set → ok; one-missing → error; nil → ok
- Log message on HashEmbedder fallback in voice.go
- Document floor mode in START.md

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:02:32 +04:00
kami 311c5cb1cd mavweb: ui design system refresh — shared nav, cards, component classes
- Rewrote ui.css with design tokens, card/btn/badge/dot components
- All pages wrapped in <main class=page> with max-width container
- Replaced inline <style> blocks with ui.css classes
- passkey page now uses shared nav.site template
- PWA voice page unified under shared nav.site (no more separate tab nav)
- Inline lang toggle moved from nav to voice page body
2026-07-06 00:41:16 +04:00
kami 2506327b62 progress: document voice-assistant gaps; theme the destructive checkbox
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASstMtsZWLSRcD1Tq8T68Q
2026-07-05 18:47:46 +04:00
kami d7c0cf89d3 mavweb: unify UI — shared ui.css + nav partial across all pages
One theme (the PWA's dark palette) for dash/history/trace/notifications/
tools/passkey via static/ui.css; shared nav template with active-page
highlight; tables wrapped in .scroll so they pan on phones; PWA nav no
longer clips the RU/EN toggle; dash 'updated' timestamp fixed (selector
matched nothing). AGENTS.md documents the local preview/screenshot recipe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASstMtsZWLSRcD1Tq8T68Q
2026-07-05 18:36:31 +04:00
kami a158ffd1d2 progress: refresh for overnight-jul5 (encryption, docker, digest, trace, web UI)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASstMtsZWLSRcD1Tq8T68Q
2026-07-05 17:59:10 +04:00
kami 8d823000d1 fix: code-review findings on overnight-jul5
- digest queue no longer dropped on failed dispatch (retry next tick)
- collapsed reminders marked fired/rescheduled only after digest delivers
- /tools step-up gate skipped when WebAuthn is not configured (was 403 forever)
- passkey credential store rolls back memory on persist failure
- auth_test fake updated for TickTrace (branch build break)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASstMtsZWLSRcD1Tq8T68Q
2026-07-05 17:50:21 +04:00
kami 5c34fb14f9 mavweb: add notifications history page at /notifications
RecentNudges IPC method, store adapter, dispatch, and client proxy.
Web UI at /notifications showing recent nudge history with color-coded
outcomes, nav links from /dash and /history.
2026-07-05 13:20:24 +04:00
kami 85013f7b8b mavweb: add rule trace page at /trace
New template showing the most recent tick's rule evaluation results
with color-coded table, expandable gate detail panels, nav links
to dash and history. Uses the TickTrace IPC method from #15.
2026-07-05 13:17:47 +04:00
kami 2689db1248 loop: add rule trace/explanation engine
ExplainTick and ExplainGate produce a full TickTrace for every tick,
recording per-rule: predicate result, gate result, first gate blocker,
gate detail (snooze/cooldown/presence etc.), and win/loss info.

IPC layer: new MethodTickTrace, DTOs (TickTrace, RuleTrace, GateDetail),
dispatch, client proxy, and CoreAPI interface method.

Daemon: tickLoop caches the latest trace; daemonAPI wraps storeAPI
with a TickTrace override that returns the cached trace.

Tests: 15 new tests for ExplainGate (all blockers, bypass conditions,
ordering) and ExplainTick (nothing fires, one fires, tiebreak,
winner/lost_to recording, gate-blocked recording).
2026-07-05 13:14:58 +04:00
kami 354990fed0 nudge: add digest/batching mode for care nudges
When enabled, eligible nudges (sev ≤ ceiling) are queued in memory
instead of sent immediately. Every 'window' duration (or when
'max_items' reached), the queue is flushed as a single digest
notification with concatenated bodies.

Config:
  digest:
    enabled: true           # default false
    window: 30m             # flush window (default 30m)
    max_items: 5            # flush at this count (default 5)
    severity_ceiling: 2     # max sev batched (default 2; sev3+ bypass)

Changes:
- config.go: add DigestConfig struct with defaults
- tick.go: QueuedNudge type, digest queue/flush in TickLoop,
  shouldQueue/maybeFlush/flushDigest helpers
- main.go: pass cfg.Digest to newTickLoop
- tick_test.go: 6 new tests covering queue, flush, bypass, dedup
2026-07-05 13:07:28 +04:00
kami 5afff001c3 mavweb: add in-process auth gate for POST /tools
Add a local PasskeySession that handleTools checks before processing
any POST action (enable/disable). If the session hasn't been asserted
within the 5-minute TTL, return 403 Forbidden.

Changes:
- webauthn/session.go: add IsStepUp() convenience method (nil-safe)
- webauthn.go: PasskeyHandle holds a *PasskeySession; AssertFinish
  calls session.Assert() after IPC step-up
- main.go: create stepUpSession, pass to handleTools and
  newPasskeyHandle; handleTools returns 403 if !session.IsStepUp()
- handlers_test.go: update TestEnableTool_NoInProcessAuthGate to
  expect 403; add TestEnableTool_WithAuthGate_RequiresStepUp for
  the happy path with asserted session; update all 10 call sites
2026-07-05 11:55:44 +04:00
kami c225ba37b2 pwa: add bilingual lang toggle to cheatsheet
Split cheatsheet into separate RU/EN <dd> elements with CSS class
toggle. Toggle buttons in nav bar (RU/EN). Language also controlled
by ?lang=ru|en query parameter (default: ru).

The rest of the PWA stays English; voice responses remain Russian.
2026-07-05 11:50:57 +04:00
kami 00a3bba9cb pwa: add app icon SVG and update manifest
Create a minimal SVG icon (512x512, dark rounded square with blue 'M')
and update manifest.json to reference it with purpose 'any'.

This fixes the blank tile on mobile add-to-home-screen and PWA install
prompts.
2026-07-05 11:49:44 +04:00
kami 3f09cdb5ae scripts: add maven-backup.sh for encrypted DB backup/restore
Simple shell script for backing up, restoring, and verifying the
encrypted SQLite database (AES-256-GCM with MVNC1\0 magic header).

Commands:
  backup   — cp + magic-verify to MAVEN_BACKUP_DIR
  restore  — cp back with confirmation prompt
  verify   — check magic header via od (no xxd/jq dependency)
  list     — show all backups with size and validity

Portable: uses only POSIX sh, od, grep, stat. Config path resolved
from mavend.json via grep or env var overrides.
2026-07-05 11:48:46 +04:00
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