ab3e818bb9
querySources splits in two once you look at which sources over-claimed during the week of 2026-08-07. The clean ones perform a lookup and can come back empty: fact-by-key, tasks, list, money, calendar, notes. The dirty ones decide by cosine against frozen seeds and then answer whatever they claimed, because they have no lookup that could miss. Weather has no local table at all, which is why "что такое TCP?" became "для какого города?". So each source now carries its destination and whether it guesses, and queryWalk takes the guessers that were not named OUT of the chain. It removes and never reorders, which is the whole safety argument: the table's order is load-bearing, every comment on it argues a reason between two sources, and above all it carries "his data first, then the world". Naming SourceWorld does not send the turn outside. It stops weather claiming a protocol on the way past. His notes, his facts and the boundary in front of them still run first, so a wrong destination costs nothing but the guess it prevented. The skipped sources are recorded as never-asked with the reason, so /trace shows a narrowed walk rather than a chain that silently shrank. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
116 lines
4.1 KiB
Go
116 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/kami/maven/internal/router"
|
|
)
|
|
|
|
// The floor, and it is the reason a destination is safe to add at all: a box
|
|
// whose model is down names nothing, and naming nothing has to walk the chain
|
|
// the way it walked before the field existed.
|
|
func TestNoDestinationWalksTheWholeChain(t *testing.T) {
|
|
walk, skipped := queryWalk(router.SourceUnknown)
|
|
if len(skipped) != 0 {
|
|
t.Errorf("skipped %d sources with no destination named, want none", len(skipped))
|
|
}
|
|
if len(walk) != len(querySources) {
|
|
t.Fatalf("walk has %d sources, want the whole table of %d", len(walk), len(querySources))
|
|
}
|
|
for i := range walk {
|
|
if walk[i].name != querySources[i].name {
|
|
t.Fatalf("position %d is %q, want %q", i, walk[i].name, querySources[i].name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The 2026-08-07 defects, one per line. Each is a source that decides by seed
|
|
// similarity claiming a turn that was never its own, and then answering it
|
|
// because it has no lookup that could come back empty.
|
|
func TestANamedDestinationSilencesTheOtherGuessers(t *testing.T) {
|
|
cases := []struct {
|
|
dest router.Source
|
|
utterance string
|
|
silenced string
|
|
}{
|
|
{router.SourceWorld, "что такое TCP?", "weather"},
|
|
{router.SourceWorld, "сколько будет 17 на 23?", "weather"},
|
|
{router.SourceWorld, "кто такой Линус Торвальдс?", "personal"},
|
|
{router.SourceRecall, "какой у меня любимый язык?", "feeds"},
|
|
{router.SourceCalendar, "что в календаре на завтра?", "weather"},
|
|
}
|
|
for _, c := range cases {
|
|
walk, skipped := queryWalk(c.dest)
|
|
if inWalk(walk, c.silenced) {
|
|
t.Errorf("%q named %q: %q is still asked", c.utterance, c.dest, c.silenced)
|
|
}
|
|
if !inWalk(skipped, c.silenced) {
|
|
t.Errorf("%q named %q: %q is missing from the record of who was skipped",
|
|
c.utterance, c.dest, c.silenced)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Naming the world must not send the turn outside. His notes, his facts and the
|
|
// boundary in front of them are the invariant CLAUDE.md states as "the owner's
|
|
// data first, then the world", and a destination a model wrote must not be able
|
|
// to reverse it.
|
|
func TestNamingTheWorldStillReadsHisDataFirst(t *testing.T) {
|
|
walk, _ := queryWalk(router.SourceWorld)
|
|
for _, look := range []string{"fact-by-key", "embed", "memory", "notes"} {
|
|
if !inWalk(walk, look) {
|
|
t.Errorf("%q was dropped; only the sources that guess may be dropped", look)
|
|
}
|
|
}
|
|
if posOf(walk, "notes") > posOf(walk, "search") {
|
|
t.Error("search is asked before his notes are")
|
|
}
|
|
if posOf(walk, "search") < 0 {
|
|
t.Fatal("search is not in the walk at all")
|
|
}
|
|
}
|
|
|
|
// The boundary belongs to his data, so naming recall keeps it. That is what
|
|
// makes "какой у меня любимый язык?" answer "не нашла у тебя такой записи"
|
|
// rather than reaching SearXNG once nothing local had it.
|
|
func TestNamingRecallKeepsTheBoundary(t *testing.T) {
|
|
walk, _ := queryWalk(router.SourceRecall)
|
|
if !inWalk(walk, "personal") {
|
|
t.Fatal("the personal boundary was skipped on a turn named for his own data")
|
|
}
|
|
if posOf(walk, "personal") > posOf(walk, "search") {
|
|
t.Error("the boundary no longer sits in front of the world")
|
|
}
|
|
}
|
|
|
|
// Whatever the destination, the walk is a subsequence of the table. Every
|
|
// comment on that table argues an order between two sources, and none of those
|
|
// reasons is about this field.
|
|
func TestTheWalkNeverReordersTheTable(t *testing.T) {
|
|
for _, dest := range append([]router.Source{router.SourceUnknown}, router.Sources...) {
|
|
walk, skipped := queryWalk(dest)
|
|
if len(walk)+len(skipped) != len(querySources) {
|
|
t.Errorf("%q: %d walked + %d skipped, want %d", dest, len(walk), len(skipped), len(querySources))
|
|
}
|
|
last := -1
|
|
for _, s := range walk {
|
|
at := posOf(querySources, s.name)
|
|
if at <= last {
|
|
t.Errorf("%q: %q is out of table order", dest, s.name)
|
|
}
|
|
last = at
|
|
}
|
|
}
|
|
}
|
|
|
|
func inWalk(list []querySource, name string) bool { return posOf(list, name) >= 0 }
|
|
|
|
func posOf(list []querySource, name string) int {
|
|
for i, s := range list {
|
|
if s.name == name {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|