Say a secret given in plain words, and log words the keeper refuses
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed

The secret-given condition's explanation carried a clock time, which the
plain rule refuses, so the operator read the scope's fallback ("needs a
look") instead of what changed. Its words now carry no time and say the
secret's name as words; a test holds them to the rule through a keeper.
With no test hook set, the keeper logs a refused or missing wording once
per kind and reason, so a fallback is never silent again (hq issue 359).
This commit is contained in:
jochen
2026-10-09 23:41:22 +02:00
parent 19eefb66c6
commit 988e501ccc
4 changed files with 103 additions and 13 deletions
+19 -5
View File
@@ -221,20 +221,34 @@ func giveAtDesk(ctx context.Context, node, module, name, desk string) error {
const kindSecretGiven = "secret-given"
// secretGivenObservation is that condition: which secret, of which module on which machine, how and when.
// The summary, for whoever looks closer, names the secret and the time. The words the operator reads are
// held to the plain rule (conditions.PlainWords): no clock time — the channel says when, in the operator's
// time — and the secret's name said as words. Words that broke the rule were replaced by the keeper with
// "needs a look … a problem it calls secret given" (hq issue 359), which told the operator nothing.
func secretGivenObservation(node, module, name, how string, at time.Time) conditions.Observation {
key := node + "." + module + "." + name
where := module + " on " + node
headline := "New secret given for " + where
if len(headline) > conditions.HeadlineMax {
headline = "New secret given for " + module
}
return conditions.Observation{Scope: conditions.ScopeMachine, ID: key, Token: kindSecretGiven, Kind: kindSecretGiven,
Machine: node, Severity: conditions.Urgent, Source: kindSecretGiven,
Summary: fmt.Sprintf("%s of %s on %s was given %s at %s", name, module, node, how,
at.Local().Format("2006-01-02 15:04")),
Headline: "Secret of " + module + " changed",
Explanation: fmt.Sprintf("The secret %s of %s on %s was given %s at %s. If you did not do this, "+
"somebody else holds what %s acts with.", name, module, node, how, at.Local().Format("15:04"), module),
Needs: "silence this if you gave it; if you did not, give the secret again yourself and unlink what it serves.",
Resolved: "You saw that " + name + " of " + module + " was changed",
Headline: headline,
Explanation: fmt.Sprintf("The secret %s of %s was given %s. If you gave it, nothing else is needed. If you "+
"did not, somebody else now holds what %s acts with.", secretNameWords(name), where, how, module),
Needs: "silence this if you just gave it; if you did not, give it again yourself so that only you hold it.",
Resolved: "You saw that " + module + " was given a new secret",
Actions: []conditions.Action{conditions.SilenceAction(conditions.Key(conditions.ScopeMachine, key, kindSecretGiven))}}
}
// secretNameWords is a secret's name as the operator reads it: "telegram-token" is "telegram token".
func secretNameWords(name string) string {
return strings.Join(strings.FieldsFunc(name, func(r rune) bool { return r == '-' || r == '_' || r == '.' }), " ")
}
// announceSecretGiven raises it on this controller's keeper.
func announceSecretGiven(ctx context.Context, node, module, name, how string) error {
return withKeeper(ctx, func(k *conditions.Keeper) error {
+37 -1
View File
@@ -178,7 +178,7 @@ func TestAValueGivenAtTheDeskIsAnnounced(t *testing.T) {
t.Fatalf("announced %v", said)
}
o := secretGivenObservation("anchor", "telegram", "telegram-token", "at the desk on laptop", time.Date(2026, 10, 9, 12, 3, 0, 0, time.UTC))
if o.Severity != conditions.Urgent || !strings.Contains(o.Explanation, "telegram-token") ||
if o.Severity != conditions.Urgent || !strings.Contains(o.Explanation, "telegram token") ||
len(o.Actions) == 0 || o.Key() == "" {
t.Errorf("the announcement %+v", o)
}
@@ -187,6 +187,42 @@ func TestAValueGivenAtTheDeskIsAnnounced(t *testing.T) {
}
}
// **The secret-given condition says itself in its own plain words** (hq issue 359): the words it carries pass
// the plain rule, from the controller's terminal and from a desk, so the keeper keeps them — they once held a
// clock time, and the operator read "Novox needs a look … a problem it calls secret given" instead. Its
// severity and its answer stay: it is heard on every channel, and silenced by the operator.
func TestTheSecretGivenConditionSaysItselfInPlainWords(t *testing.T) {
at := time.Date(2026, 10, 9, 23, 32, 0, 0, time.Local)
for _, how := range []string{"at the controller's terminal", "at the desk on laptop"} {
o := secretGivenObservation("anchor", "telegram", "telegram-token", how, at)
w := conditions.Words{Headline: o.Headline, Explanation: o.Explanation, Resolved: o.Resolved, Needs: o.Needs,
Actions: o.Actions}
if why, ok := conditions.PlainWords(w, o.Machine); !ok {
t.Fatalf("given %s, the words are not plain: %s", how, why)
}
k, _ := withConditionsInMemory(t)
c, err := k.Observe(t.Context(), o)
if err != nil {
t.Fatal(err)
}
if c.Headline != "New secret given for telegram on anchor" || c.Severity != conditions.Urgent ||
!strings.HasPrefix(c.Explanation, conditions.NeedsYou+" silence this") ||
!strings.Contains(c.Explanation, "telegram token of telegram on anchor was given "+how) ||
len(c.Actions) != 1 || c.Actions[0].Label != "Silence for a week" {
t.Errorf("given %s, the keeper said %q / %q (%s, %v)", how, c.Headline, c.Explanation, c.Severity, c.Actions)
}
if strings.Contains(c.Headline+c.Explanation+c.Resolved+c.Needs, typed) {
t.Error("the words carry the value")
}
}
// A long module name keeps the headline within its bound.
o := secretGivenObservation("anchor", "a-module-with-a-rather-long-name-indeed", "api-key", "at the controller's terminal", at)
if why, ok := conditions.PlainWords(conditions.Words{Headline: o.Headline, Explanation: o.Explanation,
Resolved: o.Resolved, Needs: o.Needs, Actions: o.Actions}, o.Machine); !ok {
t.Errorf("a long module name: %s", why)
}
}
// The bus lets the controller alone ask the desk's prompt (the review of 2026-10-09, M4): the runtime, which
// carries every agent's calls, and a person granted every tool are denied it, however wide their grant.
func TestOnlyTheControllerMayAskTheDesksPrompt(t *testing.T) {
+21 -7
View File
@@ -27,6 +27,7 @@ package conditions
import (
"fmt"
"log"
"regexp"
"strings"
"sync"
@@ -135,9 +136,26 @@ func Worded(kind string) bool {
// Unworded is told of every observation said in borrowed words: its kind has none registered, or the
// words it was given break the plain rule. The keeper says it plainly anyway; a test suite sets this to
// fail the producer.
// fail the producer. Unset, as in the serving controller, it is a line in the log (unworded).
var Unworded func(o Observation, why string)
// loggedUnworded holds each kind and why already logged: once per process, since a condition observed
// every minute would otherwise say the same line every minute.
var loggedUnworded sync.Map
// unworded tells Unworded, or else logs once, that an observation is said in borrowed words — so words
// that fell back are never silent (hq issue 359: a secret given reached the operator as "needs a look",
// and nothing said why).
func unworded(o Observation, why string) {
if Unworded != nil {
Unworded(o, why)
return
}
if _, seen := loggedUnworded.LoadOrStore(o.Kind+"\x00"+why, true); !seen {
log.Printf("the condition kind %q is said in the scope's words, not its own: %s", o.Kind, why)
}
}
// The plain rule's shapes.
var (
hexID = regexp.MustCompile(`\b[0-9a-f]{7,40}\b`)
@@ -263,14 +281,10 @@ func plainly(o Observation) Observation {
// An explanation too long is cut, never refused: what it says first is what matters.
w.Explanation = cut(w.Explanation, ExplanationMax-len(Verdict(w.Needs, ""))-1)
if from == "" {
if Unworded != nil {
Unworded(o, "the kind "+o.Kind+" has no plain words")
}
unworded(o, "the kind "+o.Kind+" has no plain words")
w = scopeWords(o)
} else if why, ok := PlainWords(w, machines...); !ok {
if Unworded != nil {
Unworded(o, from+" is not plain: "+why)
}
unworded(o, from+" is not plain: "+why)
// The scope's words, but never a weaker verdict: what needed the operator still does, and its
// answers are kept where they are themselves sound.
needed, actions := w.Needs != "", soundActions(w.Actions)
+26
View File
@@ -1,7 +1,10 @@
package conditions
import (
"bytes"
"encoding/json"
"log"
"os"
"strings"
"testing"
)
@@ -245,3 +248,26 @@ func TestAChangeOfWordsIsSaid(t *testing.T) {
t.Fatalf("%+v", said)
}
}
// **Words that fall back are never silent** (hq issue 359): with no test listening, the keeper logs which
// kind is said in borrowed words and why — once, however often the condition is observed.
func TestBorrowedWordsAreLogged(t *testing.T) {
k, _, _, _ := keeper(t)
before := Unworded
Unworded = nil
t.Cleanup(func() { Unworded = before })
var out bytes.Buffer
log.SetOutput(&out)
t.Cleanup(func() { log.SetOutput(os.Stderr) })
for i := 0; i < 3; i++ {
if _, err := k.Observe(t.Context(), Observation{Scope: ScopeMachine, ID: "ace", Kind: "test-clock", Machine: "ace",
Severity: Warning, Source: "test", Summary: "s", Headline: "ace was given a secret",
Explanation: "It was given at 23:32.", Resolved: "Seen"}); err != nil {
t.Fatal(err)
}
}
if got := out.String(); strings.Count(got, "\n") != 1 || !strings.Contains(got, `"test-clock"`) ||
!strings.Contains(got, "a clock time or date") {
t.Errorf("the log said %q", got)
}
}