Files
mesh-catalog/modules/mesh-knowledge/cmd/mesh-knowledge/glance_test.go
T
jochen eadf301f9f An ask reads at a glance on the phone: a headline, a summary and its points, the rest in a note (issues 383 and 639)
The operator would not read the Telegram ask for ADR 0343: thirty plain lines, every clause on one, no bold,
no summary. A settings proposal was the same wall of text, its values masked so the approval was blind.

- messenger: an ask's message is the asker's headline and explanation alone; who asked and when, what each
  answer does, the expiry, where else it is answered and the asker's Details travel as the message's note
  (asks.Message.Note). Answered, the message is one line, "You chose Approve at 16:06 on Telegram ·
  <headline>". An explanation that does not read at a glance (asks.Glance) is shown and logged. On a kind
  that proves who answers, an ask's values are shown whole (asks.Ask.Whole), held to the secret rule alone
  (from the earlier work on issue 383, #178).
- telegram: every message in HTML, never reworded: the title bold, a key line monospace, the note apart in
  a collapsed blockquote; markup escaped. A tap is compared by its words, whitespace aside, and a message
  shown before is still compared by the digest it was kept under.
- desk-channel: the note follows the body.
- mesh-knowledge: a record that needs the operator carries a brief (headline, summary, at most three
  points), the 30th per-record rule; its ask shows the brief and never the clauses. An ask made before is
  composed again as it was asked (form), so its warrant still holds.
- claude-code: an instructions proposal's explanation is one summary line (the proposer's, or the
  module's), its text in the Details; a question's context is at most 300 characters and its recommendation
  one point. Older proposals and questions keep their form.

The SDK is pinned at mesh-sdk 9588871 (fix/639-an-ask-reads-at-a-glance), which adds Message.Note and
asks.Glance.
2026-10-13 17:16:50 +02:00

112 lines
5.9 KiB
Go

package main
import (
"fmt"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// novox/hq issue 639: the operator would not read the ask for ADR 0343 — thirty plain lines, every clause one per
// line. A record that needs the operator is asked with its brief: a headline that says what it changes for them, a
// summary, at most three points; who proposed it, the fingerprint and how to read the clauses are the Details.
// adr0343 is ADR 0343 as it was asked on 2026-10-13: thirteen clauses, and the brief its author would now give.
func adr0343() *Record {
r := &Record{Kind: KindDecision, Number: 343, Status: StatusProposed, Date: "2026-10-13", NeedsOperator: true,
Title: "A session's memory follows the operator: the agent module carries each project's memory folder to every node, and a file changed on two keeps both",
Author: "fix-367@g14",
Brief: &Brief{Headline: "Your agents' memory follows you to every machine",
Summary: "What an agent learns in a project's memory is copied to every machine that runs the agent, so a session " +
"anywhere starts with it. A file changed on two machines keeps both versions and warns you; secrets in memory " +
"files are screened, though not perfectly.",
Points: []string{"Only memory folders travel: links and files over 128 KiB stay where they are",
"A conflict reaches you as a warning that needs you",
"A deleted file is marked, and a newer change elsewhere outlives the deletion"}}}
leads := []string{"Only the memory folders are carried", "Some memory files are not carried, and each is listed with why",
"The memory folders become paths the module's own process writes", "Memory moves only between homes of the same kind",
"The module's process on each node notices a change and puts it in the module's state",
"Every other node writes the new version where its own file holds no change it did not carry, and keeps the version replaced",
"When a file changed on two nodes, both versions are kept, the newer first",
"A conflict is sent to the operator as a warning that needs them",
"A deletion is carried as a removal marker, and a change outlives it",
"On a node's first carrying, every version learned before is kept", "One read-only tool shows what is carried",
"A value put straight into the state is not yet told apart from one the module put there", "How it is checked"}
for i, l := range leads {
r.Clauses = append(r.Clauses, Clause{N: i + 1, Text: "**" + l + ".** The statement.", Status: ClauseProposed, Source: SourceWritten})
}
return r
}
func TestADecisionAskReadsAtAGlance(t *testing.T) {
r := adr0343()
at := time.Date(2026, 10, 13, 14, 5, 0, 0, time.UTC)
a := composeAsk(r, &OperatorAsk{ID: "kb-decision-343-0d1e2f3a", At: at, Version: 12, Form: AskGlance})
if err := a.Check(at); err != nil {
t.Fatal(err)
}
if a.Headline != "Your agents' memory follows you to every machine" {
t.Errorf("the headline is not what it changes for the operator: %q", a.Headline)
}
if err := asks.Glance(a.Explanation); err != nil {
t.Errorf("%v:\n%s", err, a.Explanation)
}
if n := strings.Count(a.Explanation, "\n• "); n != 3 || !strings.HasPrefix(a.Explanation, "What an agent learns") {
t.Errorf("the summary and its %d points:\n%s", n, a.Explanation)
}
for _, c := range r.Clauses {
if strings.Contains(a.Explanation+a.Details, c.Lead()) {
t.Errorf("the ask lists the clause %d: %q", c.N, c.Lead())
}
}
details := strings.Split(a.Details, "\n")
if len(details) != 2 || !strings.Contains(details[0], "ADR 0343, proposed by fix-367@g14; the knowledge base's own review approved it.") ||
!strings.Contains(details[0], "Fingerprint "+fingerprint(r.Digest())+".") ||
details[1] != "Read its 13 clauses with the knowledge base's get, number 343." {
t.Errorf("the details:\n%s", a.Details)
}
// The ask made before the brief is composed again as it was, so its warrant still holds after the change.
old := composeAsk(r, &OperatorAsk{ID: "kb-decision-343-0d1e2f3a", At: at, Version: 12})
if !strings.Contains(old.Explanation, "Its 13 clause(s):") || old.Headline != "Accept ADR 0343?" || old.Details != "" || old.Digest() == a.Digest() {
t.Errorf("an ask made before the brief is not composed as it was asked:\n%s", old.Explanation)
}
// Without a brief (a record proposed before the rule), the glance is the title, and no clause.
r.Brief = nil
a = composeAsk(r, &OperatorAsk{ID: "kb-decision-343-0d1e2f3a", At: at, Version: 12, Form: AskGlance})
if a.Headline != "Approve ADR 0343?" || asks.Glance(a.Explanation) != nil || strings.Contains(a.Explanation, "Only the memory folders") {
t.Errorf("without a brief: %q\n%s", a.Headline, a.Explanation)
}
}
// A brief is held to what the phone reads at a glance, each way it fails named.
func TestABriefIsHeldToAGlance(t *testing.T) {
good := adr0343().Brief
if got := briefProblems(good); len(got) != 0 {
t.Fatalf("the brief of ADR 0343 is refused: %v", got)
}
for name, c := range map[string]struct {
change func(b *Brief)
says string
}{
"no headline": {func(b *Brief) { b.Headline = "" }, "headline is empty"},
"long headline": {func(b *Brief) { b.Headline = strings.Repeat("memory ", 10) }, "longer than 60"},
"no summary": {func(b *Brief) { b.Summary = " " }, "summary is empty"},
"long summary": {func(b *Brief) { b.Summary = strings.Repeat("A short sentence. ", 20) }, "summary is"},
"four points": {func(b *Brief) { b.Points = append(b.Points, "one more") }, "4 points"},
"a path": {func(b *Brief) { b.Points[0] = "Carried from the agent's home under .claude/projects" }, "names a path"},
"two lines": {func(b *Brief) { b.Summary = "One.\nTwo." }, "more than one line"},
} {
b := *good
b.Points = append([]string{}, good.Points...)
c.change(&b)
if got := fmt.Sprint(briefProblems(&b)); !strings.Contains(got, c.says) {
t.Errorf("%s: %s", name, got)
}
}
if got := briefProblems(nil); len(got) != 1 || !strings.Contains(got[0], "carries a brief") {
t.Errorf("no brief: %v", got)
}
}