bb51c28a19
The router names a position ("2", "last") or a demonstrative ("this"),
because only the daemon has the list. surfacedItems records the item ids
she read out, in the order she said them, and only for items she could
actually say: one Praxis returned without a title has no position in what
he heard.
resolveSurfacedPosition maps the reference to an id before dispatch, and
its second return says whether the turn is still Praxis's. A position that
names nothing keeps the turn and clears the slot, so the capability asks
which пункт -- he said "второй пункт" and deserves to hear there is no
second one. A demonstrative that resolves to nothing gives the turn BACK,
because "я это сделал" was probably never about a пункт. "это" also needs
the list to hold exactly one item: pointing at one of five is a guess, and
a wrong guess here transitions the wrong item.
No TTL, unlike the pending confirmation. A stale position resolves to an
item Praxis will report as already acknowledged, which is a harmless
answer, where a stale confirmation would execute something.
Measured, make eval-reach, classifier + ONNX: 16/30 -> 27/30 overall,
praxis 0/12 -> 11/12, lifecycle 0/5 -> 5/5, attention 0/7 -> 6/7, hexis
and none unchanged, p50 20.6ms -> 16.5ms. make eval-router: 60/84, 0 false
clarifies, and no failure in that list comes from a stage-0 decision.
Details and the two judgement calls in docs/evals/2026-08-05-praxis-reach.md.
161 lines
5.6 KiB
Go
161 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// "отметь второй пункт" names a position, and only the daemon knows which item
|
|
// that is. The router fills the value slot with "2"; this is where it becomes an
|
|
// item id (Vikunja #516).
|
|
func TestPositionResolvesAgainstTheLastSpokenList(t *testing.T) {
|
|
praxis := newFakePraxis(t, `[
|
|
{"id":"item_a","title":"диск заканчивается"},
|
|
{"id":"item_b","title":"бэкап не прошёл"},
|
|
{"id":"item_c","title":"сертификат истекает"}
|
|
]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
|
|
if reply := h.handlePraxisAct(context.Background(), praxisActDec("list_attention")); reply == "" {
|
|
t.Fatal("attention returned nothing")
|
|
}
|
|
|
|
cases := []struct{ ref, wantItem string }{
|
|
{"2", "item_b"},
|
|
{"1", "item_a"},
|
|
{"last", "item_c"},
|
|
}
|
|
for _, c := range cases {
|
|
praxis.ResetRequests()
|
|
reply := h.handlePraxisAct(context.Background(), praxisItemDec("acknowledge_item", c.ref))
|
|
if !strings.Contains(reply, "принято") {
|
|
t.Errorf("ref %q: reply %q", c.ref, reply)
|
|
}
|
|
if !requestedPathContaining(praxis, c.wantItem) {
|
|
t.Errorf("ref %q did not acknowledge %s; paths %v", c.ref, c.wantItem, paths(praxis))
|
|
}
|
|
}
|
|
}
|
|
|
|
// A position past the end must not acknowledge the wrong item. It asks.
|
|
func TestPositionPastTheEndAsksInsteadOfGuessing(t *testing.T) {
|
|
praxis := newFakePraxis(t, `[{"id":"item_a","title":"диск заканчивается"}]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
h.handlePraxisAct(context.Background(), praxisActDec("list_attention"))
|
|
|
|
praxis.ResetRequests()
|
|
reply := h.handlePraxisAct(context.Background(), praxisItemDec("resolve_item", "4"))
|
|
if !strings.Contains(reply, "какой пункт") {
|
|
t.Errorf("a position with no item should ask, got %q", reply)
|
|
}
|
|
if requestedPathContaining(praxis, "item_a") {
|
|
t.Error("the only surfaced item was resolved for a position that did not name it")
|
|
}
|
|
}
|
|
|
|
// No digest yet means no positions. Nothing is mutated.
|
|
func TestPositionWithNoSpokenListAsks(t *testing.T) {
|
|
praxis := newFakePraxis(t, `[]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
|
|
reply := h.handlePraxisAct(context.Background(), praxisItemDec("acknowledge_item", "1"))
|
|
if !strings.Contains(reply, "какой пункт") {
|
|
t.Errorf("want the ask, got %q", reply)
|
|
}
|
|
}
|
|
|
|
// An explicit id is not a position and passes through untouched.
|
|
func TestExplicitItemIDIsNotRewritten(t *testing.T) {
|
|
praxis := newFakePraxis(t, `[{"id":"item_a","title":"диск"}]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
h.handlePraxisAct(context.Background(), praxisActDec("list_attention"))
|
|
|
|
praxis.ResetRequests()
|
|
h.handlePraxisAct(context.Background(), praxisItemDec("pin_item", "item_zz"))
|
|
if !requestedPathContaining(praxis, "item_zz") {
|
|
t.Errorf("the id he gave was not the one called; paths %v", paths(praxis))
|
|
}
|
|
}
|
|
|
|
// An item Praxis sent without a title is never spoken, so it holds no position.
|
|
func TestUnspokenItemsHoldNoPosition(t *testing.T) {
|
|
praxis := newFakePraxis(t, `[
|
|
{"id":"item_silent"},
|
|
{"id":"item_said","title":"бэкап не прошёл"}
|
|
]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
h.handlePraxisAct(context.Background(), praxisActDec("list_attention"))
|
|
|
|
praxis.ResetRequests()
|
|
h.handlePraxisAct(context.Background(), praxisItemDec("acknowledge_item", "1"))
|
|
if !requestedPathContaining(praxis, "item_said") {
|
|
t.Errorf("position 1 is the first item she SAID; paths %v", paths(praxis))
|
|
}
|
|
}
|
|
|
|
// The item id travels in the POST body, so that is what these read.
|
|
func paths(f *fakeServer) []string {
|
|
var out []string
|
|
for _, r := range f.Requests() {
|
|
out = append(out, r.Path+" "+string(r.Body))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func requestedPathContaining(f *fakeServer, want string) bool {
|
|
for _, r := range f.Requests() {
|
|
if strings.Contains(string(r.Body), want) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// "отметь это как сделанное" after a one-item digest points at that item.
|
|
func TestDemonstrativeResolvesWhenOneItemWasSpoken(t *testing.T) {
|
|
praxis := newFakePraxis(t, `[{"id":"item_only","title":"бэкап не прошёл"}]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
h.handlePraxisAct(context.Background(), praxisActDec("list_attention"))
|
|
|
|
praxis.ResetRequests()
|
|
reply := h.handlePraxisAct(context.Background(), praxisItemDec("acknowledge_item", "this"))
|
|
if !strings.Contains(reply, "принято") {
|
|
t.Errorf("reply %q", reply)
|
|
}
|
|
if !requestedPathContaining(praxis, "item_only") {
|
|
t.Errorf("the one surfaced item was not acknowledged; paths %v", paths(praxis))
|
|
}
|
|
}
|
|
|
|
// Pointing at one of several is a guess, and a wrong guess transitions the wrong
|
|
// item. The turn goes back to the cascade instead.
|
|
func TestDemonstrativeWithSeveralItemsGivesTheTurnBack(t *testing.T) {
|
|
praxis := newFakePraxis(t, `[
|
|
{"id":"item_a","title":"диск"},
|
|
{"id":"item_b","title":"бэкап"}
|
|
]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
h.handlePraxisAct(context.Background(), praxisActDec("list_attention"))
|
|
|
|
praxis.ResetRequests()
|
|
if reply := h.handlePraxisAct(context.Background(), praxisItemDec("resolve_item", "this")); reply != "" {
|
|
t.Errorf("want a fall-through, got %q", reply)
|
|
}
|
|
for _, p := range paths(praxis) {
|
|
if strings.Contains(p, "resolve") {
|
|
t.Error("an ambiguous demonstrative resolved an item anyway")
|
|
}
|
|
}
|
|
}
|
|
|
|
// "я это сделал" with no digest behind it is a sentence about his day.
|
|
func TestDemonstrativeWithNoDigestGivesTheTurnBack(t *testing.T) {
|
|
praxis := newFakePraxis(t, `[]`)
|
|
h := newPraxisTestHandler(t, praxis)
|
|
|
|
if reply := h.handlePraxisAct(context.Background(), praxisItemDec("resolve_item", "this")); reply != "" {
|
|
t.Errorf("want a fall-through, got %q", reply)
|
|
}
|
|
}
|