diff --git a/internal/modes/modes.go b/internal/modes/modes.go index 2954246..d3b4d14 100644 --- a/internal/modes/modes.go +++ b/internal/modes/modes.go @@ -41,7 +41,7 @@ type Mode struct { RejectPolicy string `json:"reject_policy,omitempty"` PrototypeCount int `json:"prototype_count"` MinSeedExamples int `json:"min_seed_examples"` - DeadArmNote string `json:"dead_arm_note,omitempty"` + Note string `json:"note,omitempty"` Examples []string `json:"examples"` } diff --git a/internal/modes/modes_test.go b/internal/modes/modes_test.go index 756d4a7..870e0a0 100644 --- a/internal/modes/modes_test.go +++ b/internal/modes/modes_test.go @@ -54,6 +54,17 @@ func TestInventoryShape(t *testing.T) { t.Errorf("%s: prototype_count %d", m.ID, m.PrototypeCount) } } + // No id is a prefix of another. act.tool.hoststats was, and it turned out to + // run the same handler as act.tool: a read against a change is the tool row's + // destructive field, which the confirm gate already reads. Handler is prose, + // so a duplicated behaviour hides there. A nested id is the tell that shows. + for _, a := range inv.Modes { + for _, b := range inv.Modes { + if a.ID != b.ID && strings.HasPrefix(b.ID, a.ID+".") { + t.Errorf("%s is nested under %s, so one of them is not a mode", b.ID, a.ID) + } + } + } // Nearest names a real mode, or the review obligation points at nothing. for _, m := range inv.Modes { if m.Nearest != "" && !seen[m.Nearest] { diff --git a/internal/modes/modes_v1.json b/internal/modes/modes_v1.json index aa09d3d..ea93ace 100644 --- a/internal/modes/modes_v1.json +++ b/internal/modes/modes_v1.json @@ -128,7 +128,7 @@ "intent": "query", "handler": "queryNetwork", "means": "what is on the LAN", - "nearest": "act.tool.hoststats", + "nearest": "act.tool", "separated_by": "the subject is the network, not this box", "open": false, "prototype_count": 2, @@ -212,12 +212,13 @@ "id": "act.tool", "intent": "act", "handler": "tools.Exec against the enabled allowlist", - "means": "switch, open, start or stop something", + "means": "switch, start, stop or read something the tool allowlist names", "nearest": "query.home", - "separated_by": "it changes state; the home query only reads it", + "separated_by": "it names a tool the allowlist carries; destructive is the tool row’s field, not a mode of its own", "open": false, "prototype_count": 6, "min_seed_examples": 8, + "note": "act.tool.hoststats was a mode here until 06-08-2026 and is not one: it ran the same tools.Exec, and read against change is the tool row’s destructive field, which the confirm gate already reads. Its nine examples went with it, because they are question-shaped query seeds that no configured alias matches, so no tool answers them today. replySystem's память/загрузк/аптайм arm answers “системная статистика пока не подключена.” and always did.", "examples": ["включи свет на кухне", "выключи кондиционер", "открой шторы", "закрой окно", "перезагрузи роутер", "запусти пылесос", "заблокируй дверь", "maven, restart nginx", "перезапусти nginx", "останови контейнер", "maven, сделай бэкап", "запусти обновление системы"] }, { @@ -352,19 +353,6 @@ "min_seed_examples": 8, "examples": ["кто сейчас дома", "сколько человек дома", "есть ли кто дома", "все ли дома", "кто дома сейчас"] }, - { - "id": "act.tool.hoststats", - "intent": "act", - "handler": "tool.Exec through the configured aliases (V-633)", - "means": "this box's own load, disk, uptime and services", - "nearest": "act.tool", - "separated_by": "a read of this box, never a change to it", - "open": false, - "prototype_count": 4, - "min_seed_examples": 8, - "dead_arm_note": "replySystem's память/загрузк/аптайм arm answers “системная статистика пока не подключена.” and always did, so system was never the mode. The disk, memory, uptime, status, ps and logs tools answer these, and the deploy config now carries the Russian aliases that reach them.", - "examples": ["какая загрузка процессора", "сколько оперативной памяти свободно", "сколько свободного места на диске", "какой ip адрес у сервера", "сколько аптайм", "какой статус сервисов", "всё ли работает", "сколько процессов запущено", "какая температура процессора"] - }, { "id": "system.quiet", "intent": "system",