Commit Graph

2 Commits

Author SHA1 Message Date
kami 7f42cc73be Address PR review comments on 50, 52, 53, 54, 59, 61
Seven fixes, each answering a line comment on the stack.

**Weather no longer invents Moscow** (PR 50). extractWeatherLocation returned
the string "Moscow" when he named no city and voice.weather.default_location
was unset — a made-up answer presented as fact, which is the one thing maven
must never do. It returns "" now and the query path says it does not know.

**Digest statuses are a defined type** (PR 50). DigestStatus string plus the
three constants, so a rule name cannot reach the status column.

**Quiet-mode negation is not adjacency** (PR 53). The OFF list carried
{"не","тих"}, an adjacency pattern, so "не надо тихий режим" missed OFF, hit
the ON pattern {"тих","режим"}, and asking for quiet mode to stop turned it
on. Negators are scanned over the whole utterance now, with the two ON phrases
that are themselves built on "не" excluded. "тихий режим выключи" works too,
which it did not before.

**Pattern stability uses a median band** (PR 54). max/min over the extremes
asked whether every gap resembles every other gap, so 7,7,7,7,20 — four clean
weeks and one holiday — was thrown away at a ratio of 2.9. Each interval is
now tested against the median and 70% must be in band, and the reported
interval is the median of the in-band ones, so a holiday no longer drags a
weekly habit to "every 9.6 days". The reviewer's 5,8,10,3 is still rejected.

**The weekday profile stops reciting everyday habits** (PR 59). "What do I do
on Saturdays?" answered "you drink water" — true, and useless, because it is
equally true of every other day. Activities that are habits on six or more
weekdays move to Profile.Everyday and are read back as daily habits instead of
as an answer about that day.

**Russian phrase tables move out of Go** (PR 59, PR 61). The behaviour glosses
and weekday names, and the task capture/urgency/list vocabulary, are now
behavior_ru.json and task_phrases.json, embedded with go:embed. Single-binary
deploy is unchanged; wording edits are no longer source diffs.

**nginx template stops taking nginx down** (PR 52). Two host-side failure
modes, both plausible causes of today's crash. The $connection_upgrade map is
fatal when duplicated, so it moved to its own nginx-upgrade-map.conf with a
grep-first note. And `listen 10.42.0.1:80` fails with EADDRNOTAVAIL when wg0
is not up yet, so nginx exits on a reboot that beats WireGuard — the header
now documents net.ipv4.ip_nonlocal_bind.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TrVSBKe3RFDF4fGYKWYQnX
2026-08-01 12:50:47 +04:00
kami b0f5a16ec9 Add digest as a real outcome: suppressed care nudges get resurfaced, not lost
Vikunja #281. The interruption policy promised four outcomes — deliver_now,
queue, digest, drop — but only three existed: a care candidate the restraint
gate suppressed for quiet hours / away / calendar-busy simply vanished in
loop.Tick's `continue`, with only the trace remembering why.

internal/morning turned out not to be the natural drain: it's a fixed
Item/FactKey checklist engine, not a generic message bundler, so gate-
suppressed nudge text has nowhere to plug into its evidence model. Built a
parallel (but small, reusing the outbox's shape) durable digest instead:

- internal/store: digest_entries table + EnqueueDigestEntry (dedupes by
  rule+body, mirroring the delivery outbox's bodyHash), PendingDigestEntries,
  ExpireStaleDigestEntries, DrainDigestEntries (mark, never delete — an
  audit trail of what she actually said).
- internal/loop: DigestEligible(severity, blockedBy) is the pure boundary —
  only genuine restraint blocks (quiet_hours/calendar_busy/presence) even
  qualify (cooldown/snooze are not "suppression"); within care, Sev2 (break)
  digests, Sev1 (water/meal — stale by the time anyone could resurface them)
  drops. High severity never digests; alarms bypass the gate and deliver
  unchanged, on purpose.
- cmd/mavend/tick.go: each tick scans ExplainTick's trace for eligible
  blocked candidates, enqueues them, sweeps stale entries (24h expiry — the
  care rules are daily-cadence, so anything older is describing a day
  that's over), and drains the bundle only once the suppression reason has
  actually cleared, capped at 3 spoken items plus a trailing count so a
  digest can't turn into the exact nagging it was built to avoid.

Tests: store-level round-trip/restart-survival/dedupe/expiry/drain, loop-
level severity-boundary unit tests, and tick-level integration tests for
the drain-only-when-clear and never-digest-high-severity behavior.
2026-07-31 23:09:22 +04:00