Commit Graph

14 Commits

Author SHA1 Message Date
kami 44807b612c webauthn: persist credentials to JSON file instead of in-memory map
- New credentialStore type in credentials.go loads/saves
  map[id]localCred to a JSON file. Thread-safe with sync.RWMutex,
  writes to disk on every mutation.
- PasskeyHandle replaces sync.RWMutex+map with *credentialStore.
  Inline save/lookip/update closures delegate to store methods.
- newPasskeyHandle now takes a storePath parameter and returns an
  error; callers updated.
- New -passkey-file flag (default ./passkeys.json) configures the
  credential store path in main.go.
- Tests use os.CreateTemp in t.TempDir() so each test gets an
  isolated, auto-cleaned store file.
2026-07-05 02:09:56 +04:00
kami 7683a9b32c ipc: promote startup socket-wait to a shared DialWait; use in all modules
The cold-start crash-loop wasn't mavweb-specific — mavpoll and mavcaldav also
ipc.Dial + exit on failure, so they crash-looped until core booted too. Moved
the retry into ipc.DialWait (capped backoff, bounded) and switched mavweb,
mavpoll, mavcaldav to it. mavweb's local dialCoreWithRetry is gone.

Test: server appears after DialWait starts → it waits and connects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:17:10 +04:00
kami a38e733514 fix: local timezone for replies + quiet-hours; guard presence "ago" overflow
Timezone: the container ran in UTC, so mavend answered clock/date queries
(voice.go replySystem) and evaluated quiet-hours (gather.go) in UTC. Fixed at
the root — process TZ — rather than per-call: TZ=Europe/Samara in compose +
tzdata in the image (debian-slim strips it, without which Go ignores TZ and
stays UTC). One knob fixes replies and quiet-hours for every daemon; change the
zone in compose.

Overflow: the dash "ago" helper ran time.Since on a zero timestamp (no presence
yet / fresh db), saturating to ~292y and rendering "2562047h47m…". Guard zero →
"never".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:13:13 +04:00
kami 1a3ef572d1 mavweb: wait for core socket at startup instead of crash-looping
mavweb log.Fatal'd if mavend's socket wasn't up yet, so under compose it
crash-looped (relying on restart:unless-stopped) until core finished booting
its models. depends_on only orders container start, not socket readiness.
dialCoreWithRetry polls with capped backoff up to 60s; still fatal past the
deadline. Mid-life core restarts remain covered by ipc.Client's redial-on-drop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:08:56 +04:00
kami 88fb4912c4 mavweb: add a command cheatsheet to the voice page
Temporary reference while testing utterances: a collapsible <details> panel
(native, no JS) listing the six router intents (act/reminder/fact/note/query/
system) with real example phrasings pulled from models/seeds, RU-first since
the voice lang is ru.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:05:16 +04:00
kami 2740c2f685 mavsttd: silence gate — don't feed non-speech to whisper
Whisper hallucinates subtitle-credit boilerplate ("Редактор субтитров …") on
silence/room-noise, which then got stored as tap:voice facts. Gate before the
model: drop clips shorter than -min-ms (default 300) or below -silence-rms
(default 0.01 normalized RMS). Both are flags — the mic floor is hardware
specific. Returns empty transcript (same as whisper's no-segments path), so
nothing downstream changes.

gateReason is pure and unit-tested (silence/short/quiet → dropped, loud+long →
passes). ponytail: energy gate, not a real VAD; upgrade to WebRTC VAD or
whisper no_speech_prob if too blunt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:02:44 +04:00
kami de2058c851 mavweb: handler tests + gate DisableTool at step-up
- handlers_test.go: first tests for cmd/mavweb (feature-ranking #2). Covers
  the /tools enable/disable surface (arg parsing, error mapping, html escaping)
  and the webauthn handler contracts (method guards, malformed input). 14 cases.
  Verified the enable path is genuinely gated: an un-asserted call fails at the
  mavend IPC boundary (Requirement(EnableTool)=AuthStepUp), so mavweb stays a
  trust-nothing pass-through and core mediates.

- policy.go: DisableTool now also requires AuthStepUp. It mutates the same tool
  allowlist as EnableTool and is a lever to silence a security-relevant tool;
  gating allowlist mutation uniformly beats a split rule. ProposeTool stays
  maven-callable (no passkey). Corrects the stale api.go comment that claimed
  all three gated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:22:10 +04:00
kami 047a813278 store: at-rest encryption + schema-migration runner
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>
2026-07-03 21:22:09 +04:00
kami 6239eca243 items 5-7: passkey step-up, tools enable/disable, note RAG — end to end
Completes the three in-flight open items and fixes the away-fallthrough bug.

Item 7 — passkey step-up (WebAuthn):
- internal/webauthn: ES256/P-256 register + assert with real ecdsa signature
  verification, minimal CBOR/COSE decode, PasskeySession (L2→L3 on assert,
  decays after TTL). Drop the RS256 offer we can't verify (register-ok/
  assert-fail trap). Verify rpIdHash + UP/UV flags in FinishAssertion — UV is
  the step-up gesture. Round-trip test with negative cases (tampered sig,
  missing UV, wrong origin).
- cmd/mavweb: /auth/passkey enroll+assert page (the only surface that can do
  a WebAuthn gesture) + the four begin/finish endpoints. Without this the
  daemon's PasskeySession swap leaves /tools enable permanently blocked.
- daemon wires PasskeySession as the auth Session + srv.StepUp; policy gates
  MethodAssertStepUp at AuthRead.

Item 5 — tools page: DisableTool through store/ipc/client/wire; /tools grows a
disable action and a link to the passkey page. Lifecycle test.

Item 6 — note RAG: PhraseQuery on the phraser (LLM-composed answer over top-k
notes, raw-notes fallback); IntentQuery routes through it. Stub returns a
deterministic summary.

Item 2 — away-fallthrough: on ErrVoiceNoSession the dispatcher now reroutes
through the AWAY table (sev3→ntfy, sev4→telegram-repeat-til-ack, sev≤2→drop)
instead of silently dropping / mis-routing to the present-list remainder.
Covers DispatchNudge + DispatchReminder. 4 tests.

Also: re-add ProposeTool to CoreAPI (dropped in a comment rewrite), fix
missing imports + a duplicate block left mid-edit, drop dead AssertStepUpFunc,
gitignore /mavcaldav.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:41:13 +04:00
kami 36233058dd quiet-hours: configurable time-window schedule
Adds QuietHours config section (start/end as HH:MM local time). The loop's
gatherer checks the window each tick: if now falls within [start, end), the
State.QuietHours flag is set to true regardless of the config fact (which
the voice toggle writes independently). Both sources activate quiet —
schedule AND toggle.

Handles midnight-crossing windows (23:00-08:00). The gate already reads
State.QuietHours for care nudge suppression — no gate change needed.
2026-07-03 13:21:08 +02:00
kami f94758966f cmd/mavcaldav: new CalDAV poller module
Polls Radicale for today's events, writes calendar_busy and calendar_event
facts through CoreAPI. Only writes on value change (same append-only
discipline as mavpoll).

Usage: mavcaldav -socket <core> -url <radicale> -user <u> -pass <p>

Flags: -interval (default 5m), -timeout (default 10s).
Fires immediately on start, then on interval.

iCal parser supports UTC and local DTSTART/DTEND, skips all-day events.
2026-07-03 13:19:47 +02:00
kami 359ae81d1f piper: pipe cleanup on early return, write error; misc error hygiene
piper_handler: close stdin/stdout pipes on Start() failure and on
WriteString error instead of leaking fds. Propagate WriteString error.

worker/client: log SetDeadline errors instead of discarding them.

voice/session: pushAudio marshals params inline and returns the marshal
error instead of swallowing it via mustParams (removed).

tool/matcher: log ListTools errors instead of silently returning an
empty allowlist that refuses every act.

config: applyDefaults now sets RouterThreshold and ToolTimeout defaults
so consumers self-contained defaults are belt-and-suspenders.
2026-07-03 12:11:24 +02:00
kami b77f209686 voice: pre-route quiet-hours toggle, whisper ctx cancellation, stale reply fix
- resolveQuietToggle runs in HandlePushToTalk before the router so
  'тихий режим' works regardless of classifier confidence.
- whisper_full() runs in a goroutine with ctx.Done() select so the
  handler returns promptly on timeout/shutdown.
- StubReplier.IntentQuery no longer claims query is unimplemented.
2026-07-03 10:56:44 +02:00
kami e00cb07658 fix zombie leak, add quiet-hours toggle, improve query reply, configurable router threshold, JS dashboard 2026-07-03 00:42:35 +02:00