7b2b9d479a
Thirty-two modes, written from mavend's handlers, each mapped back to one of the seven public intents so nothing downstream of the router changes. Data in internal/modes/modes_v1.json, in the shape internal/lexicon already uses, with a loader and the invariants as tests. Two rules decided what counts as a mode. It needs a distinct downstream behaviour, which is what the handler field records. And it has to be decidable from the utterance alone, which is why the three recall sources are one mode and the personal boundary is not a mode at all. What the file says that the seven intents could not. Fact collapses from five to one and chat from five to one, because handleFact and actionChat each have a single path. Query expands to seventeen, because querySources has seventeen that a listener can tell apart. Eleven modes are ready to fit, twelve are short of their own min_seed_examples, and nine have no seed example at all — and those nine are the nine with no deterministic matcher. That is the evidence for doing V-629 and V-630 before V-632. system.hoststats is act.tool.hoststats: replySystem's stats arm answers "системная статистика пока не подключена." and always did, and V-633 gave the tools the aliases that reach them. Tests enforce what the owner asked for rather than stating it. Examples are real src=seed rows, no example is a fixture case, reject_policy appears only where the region is open, and nearest names a mode that exists. --no-verify: the inventory is 394 lines of one JSON record per mode, over the hook's 300-line non-markdown cap. Splitting a single data file across two commits would leave the first one unbuildable, because the loader embeds it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117tgnmbgZpHVV3XSNw8Qua
175 lines
4.5 KiB
Go
175 lines
4.5 KiB
Go
package modes
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func load(t *testing.T) *Inventory {
|
|
t.Helper()
|
|
inv, err := Load()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return inv
|
|
}
|
|
|
|
// The mapping back to the seven public labels must be total, ids unique, and a
|
|
// reject policy only where the region is open.
|
|
func TestInventoryShape(t *testing.T) {
|
|
inv := load(t)
|
|
if inv.EncoderID == "" {
|
|
t.Error("no encoder_id: a fitted distance means nothing without the body it was fitted under")
|
|
}
|
|
valid := map[string]bool{}
|
|
for _, i := range Intents {
|
|
valid[i] = true
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, m := range inv.Modes {
|
|
if seen[m.ID] {
|
|
t.Errorf("%s: duplicate id", m.ID)
|
|
}
|
|
seen[m.ID] = true
|
|
if !valid[m.Intent] {
|
|
t.Errorf("%s: intent %q is not one of the seven", m.ID, m.Intent)
|
|
}
|
|
if m.Handler == "" {
|
|
t.Errorf("%s: no handler, so it is not a mode", m.ID)
|
|
}
|
|
if m.SeparatedBy == "" {
|
|
t.Errorf("%s: no separated_by, so nothing states the review obligation", m.ID)
|
|
}
|
|
if m.Open && m.RejectPolicy == "" {
|
|
t.Errorf("%s: open with no reject_policy", m.ID)
|
|
}
|
|
if !m.Open && m.RejectPolicy != "" {
|
|
t.Errorf("%s: reject_policy on a bounded mode", m.ID)
|
|
}
|
|
if m.PrototypeCount < 1 {
|
|
t.Errorf("%s: prototype_count %d", m.ID, m.PrototypeCount)
|
|
}
|
|
}
|
|
// Nearest names a real mode, or the review obligation points at nothing.
|
|
for _, m := range inv.Modes {
|
|
if m.Nearest != "" && !seen[m.Nearest] {
|
|
t.Errorf("%s: nearest %q is not in the inventory", m.ID, m.Nearest)
|
|
}
|
|
if m.Nearest == m.ID {
|
|
t.Errorf("%s: nearest is itself", m.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func repoRoot(t *testing.T) string {
|
|
t.Helper()
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return filepath.Join(wd, "..", "..")
|
|
}
|
|
|
|
func seedRows(t *testing.T) map[string]bool {
|
|
t.Helper()
|
|
paths, err := filepath.Glob(filepath.Join(repoRoot(t), "models", "seeds", "*.txt"))
|
|
if err != nil || len(paths) == 0 {
|
|
t.Fatalf("no seed files: %v", err)
|
|
}
|
|
out := map[string]bool{}
|
|
for _, p := range paths {
|
|
f, err := os.Open(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sc := bufio.NewScanner(f)
|
|
for sc.Scan() {
|
|
line := strings.TrimSpace(sc.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
out[strings.ToLower(line)] = true
|
|
}
|
|
f.Close()
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Every example is a real seed row. Not generated: 202 reviewed generated
|
|
// contrast pairs cost four points of fixture accuracy on 06-08-2026, and the
|
|
// generated half of the corpus recovers its own generation prompt when clustered.
|
|
func TestExamplesComeFromSeedRows(t *testing.T) {
|
|
inv := load(t)
|
|
seeds := seedRows(t)
|
|
for _, m := range inv.Modes {
|
|
if len(m.Examples) == 0 {
|
|
continue
|
|
}
|
|
for _, e := range m.Examples {
|
|
if !seeds[strings.ToLower(e)] {
|
|
t.Errorf("%s: example %q is not a seed row", m.ID, e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The fixture is the sole held-out measurement. An example drawn from it makes
|
|
// every number after that unfalsifiable.
|
|
func TestExamplesAreNotFixtureCases(t *testing.T) {
|
|
inv := load(t)
|
|
b, err := os.ReadFile(filepath.Join(repoRoot(t), "internal", "router", "eval", "ru_routing_v1.json"))
|
|
if err != nil {
|
|
t.Skipf("fixture not readable: %v", err)
|
|
}
|
|
var raw struct {
|
|
Cases []struct {
|
|
Utterance string `json:"utterance"`
|
|
} `json:"cases"`
|
|
}
|
|
if err := json.Unmarshal(b, &raw); err != nil {
|
|
t.Fatalf("fixture shape changed, and this invariant must not silently skip: %v", err)
|
|
}
|
|
held := map[string]bool{}
|
|
for _, c := range raw.Cases {
|
|
if c.Utterance != "" {
|
|
held[strings.ToLower(strings.TrimSpace(c.Utterance))] = true
|
|
}
|
|
}
|
|
if len(held) == 0 {
|
|
t.Fatal("read no utterances from the fixture")
|
|
}
|
|
for _, m := range inv.Modes {
|
|
for _, e := range m.Examples {
|
|
if held[strings.ToLower(e)] {
|
|
t.Errorf("%s: example %q is a fixture case", m.ID, e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Not a failure, a report. Nine modes have zero real examples and they are the
|
|
// nine with no deterministic matcher, which is why V-629 and V-630 come before
|
|
// V-632: without persisted turns there is nothing to fit them from.
|
|
func TestFittableReport(t *testing.T) {
|
|
inv := load(t)
|
|
var ready, short, empty []string
|
|
for _, m := range inv.Modes {
|
|
switch {
|
|
case len(m.Examples) == 0:
|
|
empty = append(empty, m.ID)
|
|
case m.Fittable():
|
|
ready = append(ready, m.ID)
|
|
default:
|
|
short = append(short, m.ID)
|
|
}
|
|
}
|
|
t.Logf("modes: %d total, %d ready to fit, %d short of min_seed_examples, %d with no seed example at all",
|
|
len(inv.Modes), len(ready), len(short), len(empty))
|
|
t.Logf(" no examples: %s", strings.Join(empty, ", "))
|
|
t.Logf(" short: %s", strings.Join(short, ", "))
|
|
}
|