Compare commits

...

18 Commits

Author SHA1 Message Date
claude b338d9bb40 Merge pull request 'Bug: the Praxis attention capability is unreachable from a question' (#133) from task/475-bug-the-praxis-attention-capability-is-u into master 2026-08-04 18:23:24 +02:00
claude 6878e12d37 Merge pull request 'Bug: a transient complaint is stored as a durable fact at confidence 1.00' (#132) from task/481-bug-a-transient-complaint-is-stored-as-a into master 2026-08-04 18:23:20 +02:00
claude 8d46ee39e0 Merge pull request 'Bug: the router transliterates Latin entity names into Cyrillic before Nexus sees them' (#131) from task/476-bug-the-router-transliterates-latin-enti into master 2026-08-04 18:23:16 +02:00
claude 6eba79b332 Merge pull request 'Decide whether a parked clarify question should survive a restart' (#130) from task/385-decide-whether-a-parked-clarify-question into master 2026-08-04 18:23:12 +02:00
claude 3f60ec3994 Merge pull request 'Backfill routines accepted before the fire-forever fix' (#129) from task/377-backfill-routines into master 2026-08-04 18:23:08 +02:00
claude 14ea06712e Merge pull request 'Weather: the 6-city match table has no geocoder behind it' (#128) from task/421-weather-geocoder into master 2026-08-04 18:23:03 +02:00
claude 42d3feadd2 Merge pull request 'No read path for delivery_attempts — the outbox is durable but invisible' (#127) from task/390-no-read-path-for-delivery-attempts into master 2026-08-04 18:22:58 +02:00
claude 2a2f706b74 Merge pull request 'Recall fixture: filler note ids can collide with case ids and split the two backends' (#126) from task/386-recall-fixture-filler-note-ids into master 2026-08-04 18:22:52 +02:00
claude e082e06868 Merge pull request 'Bug: morning.Item has no required/optional flag, so behaviour 1 of task 280 cannot hold' (#125) from task/473-bug-morning-item-has-no-required-flag into master 2026-08-04 18:22:45 +02:00
claude 88dc4e1383 Merge pull request 'Bug: make simulate routes with an empty seed set, so a green run proves less than it looks' (#124) from task/465-bug-make-simulate-routes-with-an-empty into master 2026-08-04 18:22:37 +02:00
claude 60759a991e Merge pull request 'Bug: spoken task capture is dead — the router calls the marker an act, and capture only rides the note intent' (#123) from task/467-bug-spoken-task-capture-is-dead into master 2026-08-04 18:22:28 +02:00
claude 9537346441 Merge pull request 'Bug: a pending clarify is global, so one unanswerable question swallows the next three utterances from anybody' (#122) from task/466-bug-a-pending-clarify-is-global-so-one-u into master 2026-08-04 18:21:49 +02:00
claude c6be818f13 Merge pull request 'Bug: pattern.Detect has no minimum-interval floor, so four fast taps mint a permanent false routine' (#121) from task/468-bug-pattern-detect-has-no-minimum-interv into master 2026-08-04 18:21:38 +02:00
claude b5575a9402 Merge pull request 'Bug: CheckFeminine flags second-person masculine verbs as self-reference' (#120) from task/462-bug-checkfeminine-flags-second-person-ma into master 2026-08-04 18:21:27 +02:00
claude fcda5e3d2c Merge pull request 'safeKey drops Cyrillic, so Russian calendar events on one day collide' (#119) from task/443-safekey-drops-cyrillic-so-russian-calend into master 2026-08-04 18:21:15 +02:00
claude bdafc82e35 Merge pull request 'llama-server core-dumps on every SIGTERM, so each mavgpud yield writes a core file' (#116) from task/491-llama-server-core-dumps-on-every-sigterm into master 2026-08-04 14:09:25 +02:00
claude c69023c310 Merge PR #117 into task/491 (V-383) 2026-08-04 14:08:59 +02:00
claude e481ad4930 mavend: a question about attention reaches Praxis (V-475)
The capability was built, wired and degrading correctly, and no utterance
could reach it. Its aliases sit on the act dispatch, "что требует внимания"
routes to a query, and every query source passed — so the turn fell to the
web search and came back with an article about the concept of attention.
That reads as an answer, which is worse than silence.

queryAttention sits next to "tasks", above the recall sources and well above
the personal boundary: it is operational state about his things, and a notes
pass would otherwise answer from whatever he once wrote about a server. It
calls the same handler the act path calls, so the outage string comes free.

An absent or unconfigured Praxis falls through instead of claiming the turn,
like queryHome and queryNetwork. A configured Praxis that is down claims it
and names the gap. "что нового" is left to the feeds source.
2026-08-04 03:44:48 +04:00
3 changed files with 150 additions and 0 deletions
+5
View File
@@ -81,6 +81,11 @@ var querySources = []querySource{
// matcher requires a task noun or an explicit "что … сделать", so a
// date-bearing question still reaches the calendar.
{name: "tasks", answer: (*reactiveHandler).queryTasks},
// Next to "tasks" and for the same reason: "что требует внимания?" is a
// question about the operational state Praxis holds, and it used to fall
// through every source to the web search (Vikunja #475). Its matcher needs
// an attention marker, and it falls through when Praxis is not configured.
{name: "attention", answer: (*reactiveHandler).queryAttention},
// Before the recall sources too: "сколько я потратил?" is a question about
// the money facts the poller wrote, and the notes pass would otherwise
// answer it from whatever he once said about spending. Its matcher needs a
+68
View File
@@ -0,0 +1,68 @@
package main
import (
"context"
"strings"
"github.com/kami/maven/internal/router"
)
// attentionMarkers — the ways he asks what Praxis is holding. Substrings on a
// stem, because "внимание", "внимания" and "вниманию" are one word to him.
//
// "что нового" is deliberately absent: the feeds source claims it, and it
// still should — a question about news is a question about the feeds she
// reads. This list is about the operational state of his things.
var attentionMarkers = []string{
"внимани", "что требует", "что не так", "что важн", "что срочн",
"needs attention", "what needs looking",
}
// isAttentionQuery reports whether the utterance asks what needs looking at.
func isAttentionQuery(u string) bool {
s := strings.ToLower(strings.TrimSpace(u))
if s == "" {
return false
}
for _, m := range attentionMarkers {
if strings.Contains(s, m) {
return true
}
}
return false
}
// queryAttention answers "что требует внимания?" from Praxis.
//
// The capability was already built and already degraded correctly, and no
// utterance could reach it (Vikunja #475). Its aliases live on the act
// dispatch, and the question routes to IntentQuery, so it fell through every
// source to the web search and came back with an encyclopedia article about
// the concept of attention — worse than silence, because it reads as an
// answer.
//
// Placed above the recall sources and well above the personal boundary: this
// is operational state about his things, and a notes pass would otherwise
// answer it from whatever he once wrote about a server. An unconfigured or
// absent Praxis falls through rather than claiming the turn, the same
// convention queryHome and queryNetwork follow. A Praxis that is configured
// and down does claim it, and says it cannot reach the service — that is the
// degradation the ecosystem contract asks for, and it comes from the same
// handler the act path uses.
func (h *reactiveHandler) queryAttention(ctx context.Context, t *queryTurn) (string, bool) {
if !isAttentionQuery(t.dec.Utterance) {
return "", false
}
if h.ecosystem == nil || h.ecosystem.praxis == nil {
return "", false
}
reply := h.handlePraxisAct(ctx, router.Decision{
Utterance: t.dec.Utterance,
Intent: router.IntentAct,
Slots: router.Slots{Fn: "list_attention", HasFn: true},
})
if reply == "" {
return "", false
}
return reply, true
}
+77
View File
@@ -0,0 +1,77 @@
package main
import (
"context"
"strings"
"testing"
"time"
"github.com/kami/maven/internal/router"
)
func TestIsAttentionQuery(t *testing.T) {
for _, tc := range []struct {
text string
want bool
}{
{"что требует внимания", true},
{"на что обратить внимание?", true},
{"что не так?", true},
{"что важного?", true},
// The feeds source owns this one, and should keep owning it.
{"что нового?", false},
{"какая погода?", false},
{"", false},
} {
if got := isAttentionQuery(tc.text); got != tc.want {
t.Errorf("isAttentionQuery(%q) = %v, want %v", tc.text, got, tc.want)
}
}
}
// TestAttentionQuestionReachesPraxis — the defect (Vikunja #475). The question
// routes to IntentQuery, and every source used to pass, so a web search about
// the concept of attention answered it.
func TestAttentionQuestionReachesPraxis(t *testing.T) {
ctx := context.Background()
praxis := newFakePraxis(t, fixturePraxisAttentionItems(map[string]any{
"id": "item_1", "title": "disk almost full", "importance": 3.0,
}))
h := ecoHandler(t, nil, praxis, nil)
reply, ok := h.queryAttention(ctx, &queryTurn{dec: router.Decision{
Intent: router.IntentQuery, Utterance: "что требует внимания",
}})
if !ok {
t.Fatal("the attention question must be claimed before the world sources")
}
if !strings.Contains(reply, "disk almost full") {
t.Fatalf("reply = %q, want the praxis item", reply)
}
}
// A configured Praxis that is down claims the turn and says so. Falling
// through here would answer an outage with an encyclopedia article.
func TestAttentionQuestionSaysWhenPraxisIsDown(t *testing.T) {
ctx := context.Background()
praxis := newFakePraxis(t, fixturePraxisAttentionItems())
h := ecoHandler(t, nil, praxis, nil)
praxis.SetFault(503)
reply, ok := h.queryAttention(ctx, &queryTurn{dec: router.Decision{
Intent: router.IntentQuery, Utterance: "что требует внимания",
}})
if !ok || !strings.Contains(reply, "не могу") {
t.Fatalf("an outage must name the gap, got ok=%v reply=%q", ok, reply)
}
}
// No Praxis configured means no claim: the rest of the chain still runs.
func TestAttentionQuestionFallsThroughWithoutPraxis(t *testing.T) {
h, _ := newFactGateHandler(t, time.Now())
if _, ok := h.queryAttention(context.Background(), &queryTurn{dec: router.Decision{
Intent: router.IntentQuery, Utterance: "что требует внимания",
}}); ok {
t.Fatal("an unconfigured praxis must not claim the turn")
}
}