praxis: an empty attention list is not always an all-clear (V-540)

ECOSYSTEM-SPEC §2.6 requires list_attention to distinguish "nothing needs
attention" from "I cannot currently tell", and to say so when a source is
failed or stale. Maven said the first one unconditionally: ListAttention
decoded into []map[string]any, the word degraded appeared nowhere, and an empty
list answered "ничего не требует внимания". A Praxis with every source dead
read as calm.

Two halves, because the spec's mechanism does not exist server-side yet. The
deployed Praxis answers /api/v1/tools/attention with a bare array and no
envelope, so praxisAttention now decodes either shape and believes a degraded
array when one arrives. Until one does, an empty list triggers one read of
/api/v1/sources, and anything that is not reporting health "ok" is named
instead of the all-clear. Zero sources is the same answer: a Praxis that polls
nothing knows nothing, which is the state of this box today.

A sources read that fails is deliberately not a hedge. The attention call
succeeded, and not being able to ask about health is not evidence of a fault.

Both hedges also cover the entity-scoped digest, where a per-entity all-clear
is the more convincing of the two. New keys attention_degraded and
attention_no_sources, in acts_ru_v1.json and the floor. The fake Praxis serves
one healthy source by default, so the existing attention tests still assert an
all-clear on purpose rather than by omission.
This commit is contained in:
2026-08-05 12:07:07 +04:00
parent c586346a60
commit 1524991adc
6 changed files with 283 additions and 14 deletions
+10
View File
@@ -330,7 +330,17 @@ func newFakeNexus(t *testing.T, resolveBody string) *fakeServer {
// Maven's praxisClient calls. Every route returns its fixed body until a
// fault is injected via SetFault.
func newFakePraxis(t *testing.T, attentionBody string) *fakeServer {
// One healthy source by default: an empty attention list only means
// all-clear when something is actually polling (Vikunja #540), and the
// other tests here are about attention rather than about source health.
return newFakePraxisWithSources(t, attentionBody, `[{"source_id":"src_ntfy","health":"ok"}]`)
}
// newFakePraxisWithSources is newFakePraxis with the /api/v1/sources body
// under the test's control, for the degraded and no-sources hedges.
func newFakePraxisWithSources(t *testing.T, attentionBody, sourcesBody string) *fakeServer {
return newFakeServer(t, map[string]http.HandlerFunc{
"GET /api/v1/sources": jsonHandler(http.StatusOK, sourcesBody),
"GET /api/v1/tools/attention": jsonHandler(http.StatusOK, attentionBody),
"GET /api/v1/tools/changes": jsonHandler(http.StatusOK, `[]`),
"POST /api/v1/tools/surface": jsonHandler(http.StatusOK, `{}`),