Files
mesh-catalog/modules/messenger/cmd/messenger/answers_test.go
T
jschoubben b7d9590fdc
mesh/merge-gate head b7d9590fdc95 merged into trunk e1c8965ddbf1 — pass: builds messenger, telegram → novox; no bus step; every machine composes with the c…
mesh/repo-check head b7d9590fdc95 merged into trunk e1c8965ddbf1 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
An answered ask keeps its question whole, its answer below it (issue 714)
Issue 372's fix with issue 639's one line edited an answered ask down to
'You chose X at HH:MM · headline', so the operator could not read back what
they approved. The headline stays the title, the summary, points, link and
Details stay the body, and how it ended and who acts on it become the note.
Telegram cuts the body from its end past 4096 characters, never the title or
the note.
2026-10-14 20:06:24 +02:00

1062 lines
41 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// The router of novox/hq ADR 0259: it holds every ask, judges each answer and issues a warrant; it
// performs nothing. These tests hold it to every rule of the record's §4, §5 and §7.
// memAsks is the router's state in memory, with the compare-and-set the bus gives.
type memAsks struct {
mu sync.Mutex
asked map[string][]time.Time
asks map[string]AskRecord
revs map[string]uint64
ids map[string]Identity
links map[string]PendingLink
unlinked map[string]Unlinked
rev uint64
}
func newMemAsks() *memAsks {
return &memAsks{asks: map[string]AskRecord{}, revs: map[string]uint64{}, ids: map[string]Identity{},
links: map[string]PendingLink{}, unlinked: map[string]Unlinked{}, asked: map[string][]time.Time{}}
}
func clone(r AskRecord) AskRecord {
raw, _ := json.Marshal(r)
var out AskRecord
_ = json.Unmarshal(raw, &out)
return out
}
func (m *memAsks) GetAsk(k string) (*AskRecord, uint64, error) {
m.mu.Lock()
defer m.mu.Unlock()
r, ok := m.asks[k]
if !ok {
return nil, 0, nil
}
c := clone(r)
return &c, m.revs[k], nil
}
func (m *memAsks) CreateAsk(k string, r AskRecord) (uint64, error) {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.asks[k]; ok {
return 0, ErrChanged
}
m.rev++
m.asks[k], m.revs[k] = clone(r), m.rev
return m.rev, nil
}
func (m *memAsks) UpdateAsk(k string, r AskRecord, rev uint64) (uint64, error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.revs[k] != rev {
return 0, ErrChanged
}
m.rev++
m.asks[k], m.revs[k] = clone(r), m.rev
return m.rev, nil
}
func (m *memAsks) AskKeys() ([]string, error) {
m.mu.Lock()
defer m.mu.Unlock()
var out []string
for k := range m.asks {
out = append(out, k)
}
return out, nil
}
func (m *memAsks) GetIdentity(k string) (*Identity, error) {
m.mu.Lock()
defer m.mu.Unlock()
id, ok := m.ids[k]
if !ok {
return nil, nil
}
return &id, nil
}
func (m *memAsks) PutIdentity(k string, id Identity) error {
m.mu.Lock()
defer m.mu.Unlock()
m.ids[k] = id
return nil
}
func (m *memAsks) DeleteIdentity(k string) error {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.ids, k)
return nil
}
func (m *memAsks) IdentityKeys() ([]string, error) {
m.mu.Lock()
defer m.mu.Unlock()
var out []string
for k := range m.ids {
out = append(out, k)
}
return out, nil
}
func (m *memAsks) GetLink(kind string) (*PendingLink, error) {
m.mu.Lock()
defer m.mu.Unlock()
l, ok := m.links[kind]
if !ok {
return nil, nil
}
return &l, nil
}
func (m *memAsks) PutLink(kind string, l PendingLink) error {
m.mu.Lock()
defer m.mu.Unlock()
m.links[kind] = l
return nil
}
func (m *memAsks) DeleteLink(kind string) error {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.links, kind)
return nil
}
func (m *memAsks) GetUnlinked(kind string) (*Unlinked, error) {
m.mu.Lock()
defer m.mu.Unlock()
u, ok := m.unlinked[kind]
if !ok {
return nil, nil
}
return &u, nil
}
func (m *memAsks) PutUnlinked(kind string, u Unlinked) error {
m.mu.Lock()
defer m.mu.Unlock()
m.unlinked[kind] = u
return nil
}
func (m *memAsks) GetLinksAsked(key string) ([]time.Time, error) {
m.mu.Lock()
defer m.mu.Unlock()
return append([]time.Time(nil), m.asked[key]...), nil
}
func (m *memAsks) PutLinksAsked(key string, times []time.Time) error {
m.mu.Lock()
defer m.mu.Unlock()
m.asked[key] = append([]time.Time(nil), times...)
return nil
}
// rootFreeOf is each test router's machines said not root-free, by why.
var rootFreeOf = map[*Holder]map[string]string{}
// told is what the router published to askers, by subject.
type told struct{ by map[string][]asks.Warrant }
func (w *told) of(asker string) []asks.Warrant { return w.by[asks.DecidedSubject(asker)] }
// newRouter is a router with two kinds: telegram, which reaches the operator away and verifies its sender,
// and the desk, which does neither — as the controller would issue them from the claims.
func newRouter(t *testing.T) (*Holder, *fakeChannel, *fakeChannel, *clock, *memAsks, *told) {
t.Helper()
h, tg, dt, c, _ := newHolder(t)
store := newMemAsks()
w := &told{by: map[string][]asks.Warrant{}}
bench := NewBench(c.now)
bench.SetKinds([]KindInfo{
{Kind: "telegram", Module: "telegram", Node: "anchor", Capabilities: []string{"deliver", "reaches-away", "choice", "edit",
"verified-sender", "exact-render"}},
{Kind: "desktop", Module: "desk-channel", Node: "anchor", Capabilities: []string{"deliver", "choice", "edit", "exact-render", "private"}},
})
bench.To = func(kind string) string {
if ids := h.identitiesOf(kind); len(ids) > 0 {
return ids[0].Identity
}
return ""
}
bench.Stand("telegram", Standing{Ready: true, At: c.now()})
bench.Stand("desktop", Standing{Ready: true, At: c.now()})
h.Bench, h.Asks = bench, store
h.Node = "anchor"
// The controller's word on root, as its verb root-free answers it: free unless a test says otherwise.
notFree := map[string]string{}
rootFreeOf[h] = notFree
h.RootFree = func(machines []string) (map[string]RootVerdict, error) {
out := map[string]RootVerdict{}
for _, m := range machines {
why, no := notFree[m]
out[m] = RootVerdict{Machine: m, Free: !no, Why: why}
}
return out, nil
}
h.Zone = func() *time.Location { return time.UTC }
h.Publish = func(subject string, body any, id string) error {
if wt, ok := body.(asks.Warrant); ok {
w.by[subject] = append(w.by[subject], wt)
}
return nil
}
return h, tg, dt, c, store, w
}
// stand keeps the channels' word fresh as the clock moves.
func stand(h *Holder, c *clock) {
// The controller's word on the kinds, read again as the router does every half minute.
h.Bench.SetKinds(h.Bench.Kinds())
h.Bench.Stand("telegram", Standing{Ready: true, At: c.now()})
h.Bench.Stand("desktop", Standing{Ready: true, At: c.now()})
}
// bound is what an option of the held delivery's ask binds: the asker's act, digested.
func bound(verb string) string {
d, err := asks.ActDigest(asks.Act{"verb": "mesh-delivery." + verb, "id": "novox/hq@0555"})
if err != nil {
panic(err)
}
return d
}
func heldAsk(c *clock, id string) asks.Ask {
return asks.Ask{ID: id, Headline: "Delivery of hq held for 36 hours",
Explanation: "Needs you: release it, or stop it. A delivery of hq has been held for 36 hours.",
Who: asks.Operator, Expires: c.now().Add(2 * time.Hour), OnExpiry: "the delivery stays held",
Options: []asks.Option{
{ID: "release", Label: "Release", Does: "the delivery goes on", Level: asks.Approve, Binds: bound("release")},
{ID: "stop", Label: "Stop", Does: "the delivery ends", Level: asks.Approve, Binds: bound("stop")},
{ID: "silence", Label: "Silence for a week", Does: "nothing more is said of it for a week", Level: asks.Acknowledge},
}}
}
func submit(t *testing.T, h *Holder, asker string, a asks.Ask) {
t.Helper()
raw, _ := json.Marshal(a)
if err := h.TakeAsk("ask", asker, raw); err != nil {
t.Fatal(err)
}
}
func link(h *Holder, store *memAsks, c *clock, identity string, at time.Time) {
store.ids[identityKey("telegram", identity)] = Identity{Kind: "telegram", Identity: identity, Display: "Jo",
Linked: at, ApproveFrom: at.Add(LinkedApproveAfter)}
}
func choiceOf(ticket, identity string, at time.Time) asks.Choice {
return asks.Choice{ID: "c-" + ticket + "-" + identity, Ticket: ticket, At: at,
Sender: asks.Sender{Identity: identity, Display: "Jo", Authenticated: true}}
}
func ticketFor(m Message, label string) string {
for _, a := range m.Actions {
if a.Label == label {
return a.Token
}
}
return ""
}
func labelsOf(m Message) string {
var out []string
for _, a := range m.Actions {
out = append(out, a.Label)
}
return strings.Join(out, ", ")
}
// ackAsk is an ask of acknowledgements alone: silencing the held delivery.
func ackAsk(c *clock, id string) asks.Ask {
a := heldAsk(c, id)
a.Options = a.Options[2:]
a.Explanation = "Needs you: silence it, or leave it. A delivery of hq has been held for 36 hours."
return a
}
// An ask that offers an approval is answered only where it could be approved, whichever option is chosen (the
// review of 2026-10-09): the desk, which proves nothing of who clicked, is offered none of it. An ask of
// acknowledgements alone is offered on every kind that carries a choice.
func TestAnAskIsOfferedOnEachKindAsFarAsItCanCarryIt(t *testing.T) {
h, tg, dt, c, store, _ := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
a := heldAsk(c, "d-1")
a.Urgent = true
submit(t, h, "mesh-delivery", a)
settle(h, c)
if len(tg.sends) != 1 || len(dt.sends) != 0 {
t.Fatalf("telegram %d, desk %d", len(tg.sends), len(dt.sends))
}
if got := labelsOf(tg.sends[0]); got != "Release, Stop, Silence for a week" {
t.Errorf("telegram offers %q", got)
}
if rec := store.asks["mesh-delivery.d-1"]; rec.State != StateOpen || len(rec.Tickets) != 3 {
t.Errorf("recorded %+v", rec)
}
b := ackAsk(c, "d-2")
b.Urgent = true
b.About = "other"
submit(t, h, "mesh-delivery", b)
settle(h, c)
if len(dt.sends) != 1 || labelsOf(dt.sends[0]) != "Silence for a week" {
t.Fatalf("the desk was not offered an acknowledgement: %+v", dt.sends)
}
}
func TestWithoutALinkedAccountOnlyTheMeshMCPServerApproves(t *testing.T) {
h, tg, dt, c, _, w := newRouter(t)
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
settle(h, c)
// Telegram says only to the account linked as the operator: none linked, it is not used at all.
if len(tg.sends) != 0 {
t.Errorf("an unlinked Telegram was sent %d messages", len(tg.sends))
}
if err := h.Bench.Ready("telegram"); err == nil || !strings.Contains(err.Error(), "send /start to the bot") {
t.Errorf("an unlinked Telegram is ready: %v", err)
}
// The ask offers an approval no channel can carry: refused to its asker, in words.
if got := w.of("mesh-delivery"); len(got) != 1 || got[0].Outcome != asks.OutcomeRefused ||
!strings.Contains(got[0].Words, "no channel can carry") {
t.Fatalf("not refused: %+v", got)
}
if len(dt.sends) != 0 {
t.Errorf("the desk was offered an ask with an approval: %+v", dt.sends)
}
}
func TestAnApproveFromTheLinkedAccountIsAWarrantForTheAskerAlone(t *testing.T) {
h, tg, _, c, store, w := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
asked := heldAsk(c, "d-1")
submit(t, h, "mesh-delivery", asked)
settle(h, c)
release := ticketFor(tg.sends[0], "Release")
out, err := h.Chosen("telegram", choiceOf(release, "42", c.now()))
if err != nil || out != "chosen" {
t.Fatalf("%s %v", out, err)
}
got := w.of("mesh-delivery")
if len(got) != 1 || got[0].Outcome != asks.OutcomeChosen || got[0].Option != "release" || got[0].Channel != "telegram" ||
got[0].Says() != "the operator, via telegram (user id verified), chose Release" || strings.Join(got[0].Proofs, ",") != "P1" {
t.Fatalf("the warrant: %+v", got)
}
if _, err := got[0].For("mesh-delivery", asked); err != nil {
t.Errorf("the asker would not act on it: %v", err)
}
// It answers the ask as it was taken, and no other ask under that id (ADR 0259 §6).
if got[0].AskDigest != asked.Digest() {
t.Errorf("the warrant names the digest %s, not the ask's %s", got[0].AskDigest, asked.Digest())
}
if _, err := got[0].For("mesh-delivery", heldAsk(c, "d-1")); err == nil {
t.Error("a warrant was taken for an ask under the same id with another expiry")
}
if rec := store.asks["mesh-delivery.d-1"]; rec.State != string(asks.OutcomeChosen) || !rec.Told || !rec.Tickets[release].Used {
t.Errorf("the record: %+v", rec)
}
// Every copy says how it ended, its buttons gone.
for _, ch := range []*fakeChannel{tg} {
var edited Message
for _, m := range ch.edits {
edited = m
}
// The question stays whole (issue 714); below it, how it ended and what the router knows of the asker
// (issue 372).
if len(edited.Actions) != 0 || edited.Title != "Delivery of hq held for 36 hours" ||
edited.Body != heldAsk(c, "d-1").Explanation ||
edited.Note != "You chose Release at 12:00 on Telegram.\nTold to mesh-delivery, which acts on it itself." {
t.Errorf("%s still offers %q, or does not keep the question and say how it ended (issue 714): %q / %q / %q", ch.name, labelsOf(edited), edited.Title, edited.Body, edited.Note)
}
}
// The tap is confirmed where it was made, in reply to it, once the warrant stood — and before the question
// was edited, so the confirmation is what the operator sees first.
var confirmed []Message
for _, m := range tg.sends {
if m.Reply == choiceOf(release, "42", c.now()).ID {
confirmed = append(confirmed, m)
}
}
if len(confirmed) != 1 || confirmed[0].Title != "You chose Release: recorded, and mesh-delivery was told." ||
confirmed[0].Body != "" || !confirmed[0].Quiet {
t.Errorf("the tap was confirmed with %+v", confirmed)
}
// One warrant: the same answer again, and another answer after it, are refused and said.
if out, _ := h.Chosen("telegram", choiceOf(release, "42", c.now())); out != "chosen" {
t.Errorf("a redelivery of the same answer was acted on again: %s", out)
}
// The same answer from the same account, as a new tap, is what was recorded: said so, nothing more done
// (issue 372). From another account it is refused.
again := choiceOf(release, "42", c.now())
again.ID = "another"
if out, err := h.Chosen("telegram", again); err != nil || out != "chosen" ||
!strings.HasPrefix(tg.sends[len(tg.sends)-1].Title, "You already chose Release at ") {
t.Errorf("the same answer again was not said as recorded: %s %v %q", out, err, tg.sends[len(tg.sends)-1].Title)
}
stranger := choiceOf(release, "666", c.now())
if out, err := h.Chosen("telegram", stranger); err == nil || out != "refused" {
t.Errorf("a used ticket was taken from another account: %s", out)
}
stop := ticketFor(tg.sends[0], "Stop")
if out, err := h.Chosen("telegram", choiceOf(stop, "42", c.now())); err == nil || !strings.Contains(err.Error(), "already answered") {
t.Errorf("a second answer stood: %s %v", out, err)
}
if len(w.of("mesh-delivery")) != 1 {
t.Errorf("more than one warrant: %+v", w.of("mesh-delivery"))
}
if len(w.of("mesh-controller")) != 0 {
t.Error("another asker heard the warrant")
}
}
func TestEveryAnswerThatIsNotTheOperatorsIsRefusedAndSaid(t *testing.T) {
for name, tc := range map[string]struct {
kind, identity string
authenticated bool
linkedAgo time.Duration
label string
pass time.Duration
want string
}{
"an account not linked": {"telegram", "666", true, 2 * time.Hour, "Release", 0, "not linked as the operator"},
"a sender the service did not say": {"telegram", "42", false, 2 * time.Hour, "Release", 0, "did not authenticate"},
"a link younger than an hour": {"telegram", "42", true, 59 * time.Minute, "Release", 0, "can answer this question from"},
"an ask that expired": {"telegram", "42", true, 2 * time.Hour, "Release", 3 * time.Hour, "expired"},
"a ticket shown on another kind": {"desktop", "g14", true, 2 * time.Hour, "Release", 0, "not offered here"},
} {
t.Run(name, func(t *testing.T) {
h, tg, _, c, store, w := newRouter(t)
link(h, store, c, "42", c.now().Add(-tc.linkedAgo))
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
settle(h, c)
ticket := ticketFor(tg.sends[0], tc.label)
if ticket == "" {
t.Fatalf("no %s offered: %q", tc.label, labelsOf(tg.sends[0]))
}
c.pass(tc.pass)
ch := choiceOf(ticket, tc.identity, c.now())
ch.Sender.Authenticated = tc.authenticated
before := len(tg.sends)
out, err := h.Chosen(tc.kind, ch)
if err == nil || out != "refused" || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("%s %v, want refused for %q", out, err, tc.want)
}
if len(w.of("mesh-delivery")) != 0 {
t.Error("a warrant was issued")
}
title := "That answer was not taken"
if tc.identity == "666" {
// Another account's answer is said to the operator, naming that account (the lab's proof found
// the plain refusal reached the operator and read as their own answer refused).
title = "Another account tried to answer for you"
}
if tc.kind == "telegram" && (len(tg.sends) != before+1 || tg.sends[len(tg.sends)-1].Title != title) {
t.Errorf("the refusal was not said on the channel: %+v", tg.sends[before:])
}
if tc.identity == "666" && !strings.Contains(tg.sends[len(tg.sends)-1].Body, "number 666") {
t.Errorf("the notice does not name the other account: %q", tg.sends[len(tg.sends)-1].Body)
}
})
}
}
func TestALinkApprovesAfterAnHourAndAcknowledgesAtOnce(t *testing.T) {
h, tg, _, c, store, w := newRouter(t)
link(h, store, c, "42", c.now())
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
settle(h, c)
release, silence := ticketFor(tg.sends[0], "Release"), ticketFor(tg.sends[0], "Silence for a week")
c.pass(59*time.Minute - BurstWindow)
if out, err := h.Chosen("telegram", choiceOf(release, "42", c.now())); err == nil || out != "refused" {
t.Fatalf("approved at 59 minutes: %s", out)
}
c.pass(time.Minute)
stand(h, c)
late := choiceOf(release, "42", c.now().Add(time.Second))
late.ID = "at-sixty"
if out, err := h.Chosen("telegram", late); err != nil || out != "chosen" {
t.Fatalf("refused at 60 minutes: %s %v", out, err)
}
// An acknowledgement is taken at once from a fresh link, on an ask of acknowledgements alone.
h2, tg2, _, c2, store2, w2 := newRouter(t)
link(h2, store2, c2, "42", c2.now())
submit(t, h2, "mesh-delivery", ackAsk(c2, "d-2"))
settle(h2, c2)
silence = ticketFor(tg2.sends[0], "Silence for a week")
if out, err := h2.Chosen("telegram", choiceOf(silence, "42", c2.now())); err != nil || out != "chosen" {
t.Fatalf("an acknowledgement from a fresh link was refused: %s %v", out, err)
}
if got := w2.of("mesh-delivery"); len(got) != 1 || got[0].Level != asks.Acknowledge {
t.Errorf("%+v", got)
}
_ = w
}
// An acknowledgement does not end an ask that waits for an approval from where an approval is not proven
// (the review of 2026-10-09): a fresh link's Silence on the held delivery's ask is refused, and the ask stays
// open for Release or Stop once the hour has passed.
func TestAnAcknowledgementDoesNotEndAnAskThatWaitsForAnApproval(t *testing.T) {
h, tg, _, c, store, w := newRouter(t)
link(h, store, c, "42", c.now())
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
settle(h, c)
silence := ticketFor(tg.sends[0], "Silence for a week")
if out, err := h.Chosen("telegram", choiceOf(silence, "42", c.now())); err == nil || out != "refused" {
t.Fatalf("a fresh link's acknowledgement ended an ask that offers an approval: %s %v", out, err)
}
if len(w.of("mesh-delivery")) != 0 || store.asks["mesh-delivery.d-1"].State != StateOpen {
t.Fatal("the ask ended")
}
// Nor while the controller does not say root is free there.
c.pass(time.Hour)
stand(h, c)
rootFreeOf[h]["anchor"] = "not measured"
again := choiceOf(silence, "42", c.now())
again.ID = "again"
if out, _ := h.Chosen("telegram", again); out != "refused" || store.asks["mesh-delivery.d-1"].State != StateOpen {
t.Fatalf("an acknowledgement ended the ask while root is not free: %s", out)
}
delete(rootFreeOf[h], "anchor")
release := choiceOf(ticketFor(tg.sends[0], "Release"), "42", c.now())
if out, err := h.Chosen("telegram", release); err != nil || out != "chosen" {
t.Fatalf("the approval after the hour: %s %v", out, err)
}
}
func TestTheDeskAcknowledgesAndNeverApproves(t *testing.T) {
h, _, dt, c, _, w := newRouter(t)
submit(t, h, "mesh-delivery", ackAsk(c, "d-1"))
settle(h, c)
silence := ticketFor(dt.sends[0], "Silence for a week")
ch := choiceOf(silence, "g14", c.now())
ch.Sender.Authenticated = false
if out, err := h.Chosen("desktop", ch); err != nil || out != "chosen" {
t.Fatalf("%s %v", out, err)
}
got := w.of("mesh-delivery")
if len(got) != 1 || got[0].By.Verified != "a desk click: whoever was at the operator's session on g14" || len(got[0].Proofs) != 0 {
t.Errorf("%+v", got)
}
// An approve ticket forged onto the desk is refused: the kind cannot carry it.
h2, tg2, _, c2, store2, _ := newRouter(t)
link(h2, store2, c2, "42", c2.now().Add(-2*time.Hour))
submit(t, h2, "mesh-delivery", heldAsk(c2, "d-1"))
settle(h2, c2)
rec := store2.asks["mesh-delivery.d-1"]
release := ticketFor(tg2.sends[0], "Release")
tk := rec.Tickets[release]
tk.Kind = "desktop"
rec.Tickets[release] = tk
store2.asks["mesh-delivery.d-1"] = rec
h2.keepAsk("mesh-delivery.d-1", rec, store2.revs["mesh-delivery.d-1"])
if out, err := h2.Chosen("desktop", choiceOf(release, "g14", c2.now())); err == nil || out != "refused" ||
!strings.Contains(err.Error(), "cannot answer a question that asks for an approval") {
t.Errorf("the desk approved: %s %v", out, err)
}
// Nor does an acknowledgement forged onto the desk end that ask.
silence = ticketFor(tg2.sends[0], "Silence for a week")
tk = rec.Tickets[silence]
tk.Kind = "desktop"
rec.Tickets[silence] = tk
store2.asks["mesh-delivery.d-1"] = rec
h2.keepAsk("mesh-delivery.d-1", rec, store2.revs["mesh-delivery.d-1"])
if out, _ := h2.Chosen("desktop", choiceOf(silence, "g14", c2.now())); out != "refused" ||
store2.asks["mesh-delivery.d-1"].State != StateOpen {
t.Errorf("a desk click ended an ask that waits for an approval: %s", out)
}
}
func TestAnAskIsRefusedInWordsToItsAsker(t *testing.T) {
h, _, _, c, store, w := newRouter(t)
bad := heldAsk(c, "d-1")
bad.Options[0].Level = asks.Destroy
submit(t, h, "mesh-delivery", bad)
carrying := heldAsk(c, "d-2")
carrying.Explanation = "Needs you: see /etc/passwd on the machine."
submit(t, h, "mesh-delivery", carrying)
for i := 3; i <= 6; i++ {
a := ackAsk(c, "d-"+string(rune('0'+i)))
a.About = "about-" + string(rune('0'+i))
submit(t, h, "mesh-delivery", a)
}
got := w.of("mesh-delivery")
var refused []string
for _, g := range got {
if g.Outcome == asks.OutcomeRefused {
refused = append(refused, g.Ask+": "+g.Words)
}
}
all := strings.Join(refused, "\n")
for _, want := range []string{"d-1: the ask is refused: an ask that destroys lasts at most", "d-2: its words carry",
"d-6: its asker already has 3 open asks"} {
if !strings.Contains(all, want) {
t.Errorf("not refused: %q in\n%s", want, all)
}
}
if _, kept := store.asks["mesh-delivery.d-1"]; kept {
t.Error("a refused ask was recorded")
}
// No channel can carry anything: refused, never queued in silence.
h2, _, _, c2, _, w2 := newRouter(t)
h2.Bench.Stand("telegram", Standing{Ready: false, Why: "no bot token", At: c2.now()})
h2.Bench.Stand("desktop", Standing{Ready: false, Why: "no session", At: c2.now()})
submit(t, h2, "mesh-delivery", heldAsk(c2, "d-1"))
if got := w2.of("mesh-delivery"); len(got) != 1 || !strings.Contains(got[0].Words, "no channel can carry any of its answers now") {
t.Errorf("%+v", got)
}
}
func TestAnAskExpiresAndNeverDefaults(t *testing.T) {
h, tg, _, c, store, w := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
settle(h, c)
c.pass(2 * time.Hour)
stand(h, c)
h.Tick()
got := w.of("mesh-delivery")
if len(got) != 1 || got[0].Outcome != asks.OutcomeExpired || got[0].Option != "" || !strings.Contains(got[0].Words, "the delivery stays held") {
t.Fatalf("%+v", got)
}
var edited Message
for _, m := range tg.edits {
edited = m
}
if len(edited.Actions) != 0 || !strings.HasPrefix(edited.Title, "Delivery of hq") ||
edited.Body == "" || edited.Note != "No longer answerable: nobody answered in time.\nThe delivery stays held." {
t.Errorf("the copy still offers answers, or does not say how it ended: %+v", edited)
}
}
func TestANewerAskAboutTheSameThingReplacesTheOlderAndACancelEndsIt(t *testing.T) {
h, _, _, c, store, w := newRouter(t)
a := ackAsk(c, "d-1")
a.About = "delivery.hq"
submit(t, h, "mesh-delivery", a)
b := ackAsk(c, "d-2")
b.About = "delivery.hq"
submit(t, h, "mesh-delivery", b)
if store.asks["mesh-delivery.d-1"].State != string(asks.OutcomeReplaced) {
t.Errorf("the older ask was not replaced: %s", store.asks["mesh-delivery.d-1"].State)
}
if err := h.TakeAsk("cancel", "mesh-delivery", json.RawMessage(`{"id":"d-2"}`)); err != nil {
t.Fatal(err)
}
got := w.of("mesh-delivery")
if len(got) != 2 || got[0].Outcome != asks.OutcomeReplaced || got[1].Outcome != asks.OutcomeCancelled {
t.Errorf("%+v", got)
}
// Another asker cannot cancel it: its cancel names its own ask, under its own name.
submit(t, h, "mesh-delivery", ackAsk(c, "d-3"))
_ = h.TakeAsk("cancel", "mesh-controller", json.RawMessage(`{"id":"d-3"}`))
if store.asks["mesh-delivery.d-3"].State != StateOpen {
t.Error("another asker's cancel ended it")
}
}
func TestAnAskAboutAConditionIsAnsweredOnItsMessage(t *testing.T) {
h, tg, dt, c, store, w := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
k := cond("delivery.hq.held", Warning, "the delivery of hq is held")
k.Raised = c.now()
h.Condition(EventRaised, k)
settle(h, c)
a := ackAsk(c, "d-1")
a.About = "delivery.hq.held"
submit(t, h, "mesh-controller", a)
settle(h, c)
// Its first message was on the desk alone (a warning, the desk ready); the ask re-shows it with answers.
var shown Message
for _, m := range dt.edits {
shown = m
}
if got := labelsOf(shown); got != "Silence for a week, Details" {
t.Errorf("the desk's copy offers %q: %+v", got, shown)
}
if store.asks["mesh-controller.d-1"].Record != "delivery.hq.held" {
t.Errorf("the ask is shown on %q", store.asks["mesh-controller.d-1"].Record)
}
silence := ticketFor(shown, "Silence for a week")
if out, err := h.Chosen("desktop", choiceOf(silence, "g14", c.now())); err != nil || out != "chosen" {
t.Fatalf("%s %v", out, err)
}
if got := w.of("mesh-controller"); len(got) != 1 || got[0].About != "delivery.hq.held" {
t.Errorf("%+v", got)
}
_ = tg
}
func TestAnAskerNotToldIsToldAgain(t *testing.T) {
h, tg, _, c, store, w := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
settle(h, c)
pub := h.Publish
h.Publish = func(string, any, string) error { return errorf("the bus is away") }
if out, _ := h.Chosen("telegram", choiceOf(ticketFor(tg.sends[0], "Stop"), "42", c.now())); out != "chosen" {
t.Fatal(out)
}
if store.asks["mesh-delivery.d-1"].Told {
t.Fatal("told while the bus was away")
}
h.Publish = pub
c.pass(time.Minute)
stand(h, c)
h.Tick()
if got := w.of("mesh-delivery"); len(got) != 1 || got[0].Option != "stop" || !store.asks["mesh-delivery.d-1"].Told {
t.Errorf("not told again: %+v", got)
}
}
func TestAnAnswerReplayedLongAfterIsNotActedOn(t *testing.T) {
h, tg, _, c, store, w := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
settle(h, c)
old := choiceOf(ticketFor(tg.sends[0], "Release"), "42", c.now().Add(-time.Hour))
if out, _ := h.Chosen("telegram", old); out != "too old" || len(w.of("mesh-delivery")) != 0 {
t.Errorf("%s", out)
}
}
func TestLinkingShowsACodeOnTheDeskAndLinksOnlyWhoAsked(t *testing.T) {
h, tg, dt, c, store, _ := newRouter(t)
asked := asks.LinkAsked{ID: "l1", At: c.now(), Sender: asks.Sender{Identity: "42", Display: "Jo", Authenticated: true}}
h.Link("telegram", asked)
if len(dt.sends) != 1 || !strings.HasPrefix(dt.sends[0].Title, "Link a Telegram account as you") {
t.Fatalf("no code on the desk: %+v", dt.sends)
}
code := strings.ReplaceAll(strings.TrimSuffix(strings.SplitAfter(dt.sends[0].Body, "before ")[1][7:14], "."), " ", "")
if last := tg.sends[len(tg.sends)-1]; last.Title != "Send the code shown on your desk" {
t.Errorf("telegram was told %q", last.Title)
}
if p := store.links["telegram"]; p.CodeHash == "" || strings.Contains(p.CodeHash, code) {
t.Errorf("the code is kept in clear: %+v", p)
}
// Another account sending the code is refused; a wrong code is refused; the right one links.
if a := h.Prove("telegram", asks.Code{Sender: asks.Sender{Identity: "666", Authenticated: true}, Code: code}); a.Accepted {
t.Error("another account linked with the code")
}
if a := h.Prove("telegram", asks.Code{Sender: asks.Sender{Identity: "42", Authenticated: true}, Code: "000000x"}); a.Accepted {
t.Error("a wrong code linked")
}
a := h.Prove("telegram", asks.Code{Sender: asks.Sender{Identity: "42", Authenticated: true}, Code: code, Purpose: "link"})
if !a.Accepted || !strings.Contains(a.Words, "approve from 13:00") {
t.Fatalf("the right code: %+v (code %q)", a, code)
}
id := store.ids[identityKey("telegram", "42")]
if id.ApproveFrom.Sub(id.Linked) != time.Hour {
t.Errorf("approves from %s, linked %s", id.ApproveFrom, id.Linked)
}
// Said on every channel.
for _, ch := range []*fakeChannel{tg, dt} {
if last := ch.sends[len(ch.sends)-1]; !strings.Contains(last.Title, "is now linked as you") {
t.Errorf("%s was not told: %q", ch.name, last.Title)
}
}
// A second account cannot link while one is.
h.Link("telegram", asks.LinkAsked{ID: "l2", At: c.now(), Sender: asks.Sender{Identity: "666", Authenticated: true}})
if _, pending := store.links["telegram"]; pending {
t.Error("a second link was begun while one is linked")
}
}
func TestALinkCodeExpiresAndFiveWrongTriesEndIt(t *testing.T) {
h, _, _, c, store, _ := newRouter(t)
jo := asks.Sender{Identity: "42", Authenticated: true}
h.Link("telegram", asks.LinkAsked{ID: "l1", At: c.now(), Sender: jo})
for i := 0; i < LinkTries; i++ {
h.Prove("telegram", asks.Code{Sender: jo, Code: "nope"})
}
if _, pending := store.links["telegram"]; pending {
t.Error("five wrong codes did not end the link")
}
h.Link("telegram", asks.LinkAsked{ID: "l2", At: c.now(), Sender: jo})
c.pass(LinkCodeFor)
if a := h.Prove("telegram", asks.Code{Sender: jo, Code: "123456"}); a.Accepted || !strings.Contains(a.Words, "expired") {
t.Errorf("%+v", a)
}
// The desk cannot be linked: it proves nothing about who clicked.
h.Link("desktop", asks.LinkAsked{ID: "l3", At: c.now(), Sender: asks.Sender{Identity: "g14"}})
if _, pending := store.links["desktop"]; pending {
t.Error("the desk began a link")
}
}
// novox/hq ADR 0259 §7 (revised): unlinking is an approve ask put to the account that is linked; its notice
// reaches that account first, by name; nobody else's answer unlinks it.
func TestUnlinkingIsAskedOfTheLinkedAccountAndToldToItFirst(t *testing.T) {
h, tg, dt, c, store, _ := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
got, err := h.Unlink("telegram")
if err != nil || got["asked"] != true {
t.Fatalf("%v %v", got, err)
}
if len(store.ids) != 1 {
t.Fatal("unlinked before the account answered")
}
ask := tg.sends[len(tg.sends)-1]
if ask.Title != "Urgent: Unlink this account?" || tg.to[len(tg.to)-1] != "42" || labelsOf(ask) != "Unlink" {
t.Fatalf("the question: %q to %q offers %q", ask.Title, tg.to[len(tg.to)-1], labelsOf(ask))
}
if last := dt.sends[len(dt.sends)-1]; !strings.Contains(last.Title, "An unlink of your Telegram account was asked") {
t.Errorf("the desk was not told: %q", last.Title)
}
unlink := ticketFor(ask, "Unlink")
// Another account cannot answer it.
if out, _ := h.Chosen("telegram", choiceOf(unlink, "666", c.now())); out != "refused" || len(store.ids) != 1 {
t.Fatalf("another account unlinked it: %s", out)
}
before := len(tg.sends)
if out, err := h.Chosen("telegram", choiceOf(unlink, "42", c.now())); err != nil || out != "chosen" {
t.Fatalf("%s %v", out, err)
}
if len(store.ids) != 0 {
t.Fatal("the account's own approval did not unlink it")
}
// The tap is confirmed where it was made (issue 372), then the account is told, by name, before it goes.
if confirmed := tg.sends[before]; confirmed.Title != "You chose Unlink: recorded." || confirmed.Reply == "" {
t.Errorf("the tap was not confirmed in reply to it: %+v", confirmed)
}
notice := tg.sends[before+1]
if notice.Title != "This account is no longer linked as you" || tg.to[before+1] != "42" {
t.Errorf("the account was not told first, by name: %q to %q", notice.Title, tg.to[before+1])
}
if last := dt.sends[len(dt.sends)-1]; !strings.Contains(last.Title, "No Telegram account is linked as you") {
t.Errorf("the desk was not told: %q", last.Title)
}
}
// A link within a day of an unlink waits for the replaced account's approval.
func TestARelinkWithinADayWaitsForTheReplacedAccount(t *testing.T) {
h, tg, dt, c, store, _ := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
_, _ = h.Unlink("telegram")
_, _ = h.Chosen("telegram", choiceOf(ticketFor(tg.sends[len(tg.sends)-1], "Unlink"), "42", c.now()))
h.Link("telegram", asks.LinkAsked{ID: "l9", At: c.now(), Sender: asks.Sender{Identity: "666", Display: "Eve", Authenticated: true}})
code := codeOf(t, dt.sends[len(dt.sends)-1])
a := h.Prove("telegram", asks.Code{Sender: asks.Sender{Identity: "666", Authenticated: true}, Code: code})
if !a.Accepted || !strings.Contains(a.Words, "waits for that account's approval") || len(store.ids) != 0 {
t.Fatalf("linked at once within a day of an unlink: %+v %v", a, store.ids)
}
asked := tg.sends[len(tg.sends)-1]
if asked.Title != "Urgent: Allow another account in your place?" || tg.to[len(tg.to)-1] != "42" {
t.Fatalf("the replaced account was not asked: %q to %q", asked.Title, tg.to[len(tg.to)-1])
}
if out, _ := h.Chosen("telegram", choiceOf(ticketFor(asked, "Allow"), "666", c.now())); out != "refused" {
t.Error("the new account allowed itself")
}
if out, err := h.Chosen("telegram", choiceOf(ticketFor(asked, "Allow"), "42", c.now())); err != nil || out != "chosen" {
t.Fatalf("%s %v", out, err)
}
if id, ok := store.ids[identityKey("telegram", "666")]; !ok || id.ApproveFrom.Sub(id.Linked) != time.Hour {
t.Errorf("not linked on the replaced account's approval: %+v", store.ids)
}
}
// Root at the router's machine unlinks, or allows a waiting link, by a file only root makes. It acts from the
// router's minute (Tick), which holds the work: the review of 2026-10-09 found the minute took the work again
// there and hung for ever the first time root used it. It is said on every channel, and the router's own open
// questions end.
func TestBreakGlassIsAFileRootMadeAndTheMinuteGoesOn(t *testing.T) {
h, tg, dt, c, store, _ := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
h.StateDir = t.TempDir()
tick := func() {
t.Helper()
done := make(chan struct{})
go func() { h.Tick(); close(done) }()
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatal("the router's minute hung on a break-glass file")
}
}
// Who owns the file is what the kernel says, faked here both ways: the build seat runs this suite as root, where
// every file the test makes is root's, and a person runs it as themselves (the forge's check of 2026-10-09 failed
// on the first and passed on the second).
was := fileOwner
t.Cleanup(func() { fileOwner = was })
fileOwner = func(os.FileInfo) uint32 { return 1000 }
if err := os.WriteFile(filepath.Join(h.StateDir, "unlink.telegram"), nil, 0o600); err != nil {
t.Fatal(err)
}
tick()
if len(store.ids) != 1 || !strings.Contains(dt.sends[len(dt.sends)-1].Title, "was refused") {
t.Fatalf("a file not made by root unlinked: %v", store.ids)
}
// An open question of the router's own: root's act ends it.
if _, err := h.Unlink("telegram"); err != nil {
t.Fatal(err)
}
fileOwner = func(os.FileInfo) uint32 { return 0 }
_ = os.WriteFile(filepath.Join(h.StateDir, "unlink.telegram"), nil, 0o600)
tick()
if len(store.ids) != 0 {
t.Fatal("root's file did not unlink")
}
if _, err := os.Stat(filepath.Join(h.StateDir, "unlink.telegram")); err == nil {
t.Error("the file was left to act again")
}
for _, ch := range []*fakeChannel{tg, dt} {
said := false
for _, m := range ch.sends {
said = said || (m.Title == "Urgent: Root changed who is you" || strings.Contains(m.Title, "Root changed who is you")) &&
strings.Contains(m.Body, "was unlinked") && strings.Contains(m.Body, "1 open question")
}
if !said && ch == dt {
t.Errorf("%s was not told what root did: %+v", ch.name, ch.sends[len(ch.sends)-1])
}
}
for k, rec := range store.asks {
if rec.Asker == SelfAsker && rec.State == StateOpen {
t.Errorf("the router's own question %s is still open after root decided", k)
}
}
}
// While an agent can become root where the router or the channel runs, nothing proven there approves.
func TestNoApprovalWhileAnAgentCanBecomeRootThere(t *testing.T) {
h, tg, _, c, store, w := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
asked := heldAsk(c, "d-1")
submit(t, h, "mesh-delivery", asked)
settle(h, c)
release := ticketFor(tg.sends[0], "Release")
rootFreeOf[h]["anchor"] = "the agent account is not judged"
if out, err := h.Chosen("telegram", choiceOf(release, "42", c.now())); err == nil || out != "refused" ||
!strings.Contains(err.Error(), "may become root on anchor") {
t.Fatalf("approved while an agent can become root there: %s %v", out, err)
}
if len(w.of("mesh-delivery")) != 0 {
t.Error("a warrant was issued")
}
if got := h.carriers(asks.Approve); len(got) != 0 {
t.Errorf("an approval is still offered on %v", got)
}
}
// Another asker naming a condition's key does not take that condition's message over.
func TestOnlyTheConditionsOwnerAsksOnItsMessage(t *testing.T) {
h, _, _, c, store, _ := newRouter(t)
k := cond("delivery.hq.held", Warning, "the delivery of hq is held")
k.Raised = c.now()
h.Condition(EventRaised, k)
a := heldAsk(c, "d-1")
a.About = "delivery.hq.held"
submit(t, h, "mesh-delivery", a)
if got := store.asks["mesh-delivery.d-1"].Record; got == "delivery.hq.held" {
t.Error("another asker took the condition's message")
}
}
// A channel that does not render exactly as the router does carries no answer.
func TestAnAnswerNeedsAChannelThatRendersExactly(t *testing.T) {
h, _, _, c, _, _ := newRouter(t)
h.Bench.SetKinds([]KindInfo{{Kind: "desktop", Module: "desk-channel", Capabilities: []string{"deliver", "choice", "private"}}})
h.Bench.Stand("desktop", Standing{Ready: true, At: c.now()})
if got := h.carriers(asks.Acknowledge); len(got) != 0 {
t.Errorf("a channel without exact-render carries %v", got)
}
}
// At most three links an hour per kind, and the code is shown only where the words stay private.
func TestLinksAreBoundedAndTheCodeShownOnlyInPrivate(t *testing.T) {
h, tg, dt, c, store, _ := newRouter(t)
jo := asks.Sender{Identity: "42", Display: "Jo", Authenticated: true}
for i := 0; i < LinksAnHour; i++ {
h.Link("telegram", asks.LinkAsked{ID: fmt.Sprint("l", i), At: c.now(), Sender: jo})
}
shown := len(dt.sends)
h.Link("telegram", asks.LinkAsked{ID: "l9", At: c.now(), Sender: jo})
if len(dt.sends) != shown || !strings.Contains(tg.sends[len(tg.sends)-1].Body, "Try again in an hour") {
t.Error("a fourth link this hour showed a code")
}
if !strings.Contains(dt.sends[0].Body, "(number 42)") {
t.Errorf("the notice does not name the account by its number: %q", dt.sends[0].Body)
}
h2, tg2, _, c2, store2, _ := newRouter(t)
h2.Bench.SetKinds([]KindInfo{
{Kind: "telegram", Module: "telegram", Capabilities: []string{"choice", "verified-sender", "exact-render", "reaches-away"}},
{Kind: "desktop", Module: "desk-channel", Capabilities: []string{"choice", "exact-render"}}})
h2.Link("telegram", asks.LinkAsked{ID: "l1", At: c2.now(), Sender: jo})
if _, pending := store2.links["telegram"]; pending || !strings.Contains(tg2.sends[len(tg2.sends)-1].Body, "No private place") {
t.Error("a code was shown where the words are not private")
}
_ = store
}
// A warning the desk could not show goes to the channel that reaches the operator away.
func TestAWarningTheDeskCouldNotShowGoesAway(t *testing.T) {
h, tg, dt, c, store, _ := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
k := cond("module.x.unhealthy", Warning, "x is not healthy")
k.Raised = c.now()
h.Condition(EventRaised, k)
settle(h, c)
if len(dt.sends) != 1 {
t.Fatalf("the desk was sent %d", len(dt.sends))
}
h.Failed("desktop", asks.Failed{Handle: "g14=1", Why: "no session answers", At: c.now()})
c.pass(BurstWindow)
h.Flush()
if len(tg.sends) == 0 {
t.Error("a warning the desk could not show did not go to Telegram")
}
}
// codeOf reads the six digits a link's notice shows.
func codeOf(t *testing.T, m Message) string {
t.Helper()
i := strings.Index(m.Body, ": ")
for i >= 0 {
rest := m.Body[i+2:]
if len(rest) >= 7 && rest[3] == ' ' {
return rest[:3] + rest[4:7]
}
j := strings.Index(rest, ": ")
if j < 0 {
break
}
i += 2 + j
}
t.Fatalf("no code in %q", m.Body)
return ""
}
// An ask's message goes alone even when it offers one answer: folded into a burst's digest, its one button was
// lost (found by the lab's proof, mesh-lab asks/, with a condition's message in the same burst).
func TestAnAsksMessageWithOneAnswerIsNotFoldedIntoADigest(t *testing.T) {
h, tg, _, c, store, _ := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
settle(h, c)
// Something was just said on the channel, so what follows waits for the burst.
submit(t, h, "mesh-delivery", heldAsk(c, "d-0"))
settle(h, c)
k := cond("machine.anchor.disk", Urgent, "the disk on anchor is full")
k.Raised = c.now()
h.Condition(EventRaised, k)
before := len(tg.sends)
one := heldAsk(c, "d-1")
one.Options = one.Options[2:] // Silence alone: one answer
submit(t, h, "mesh-delivery", one)
settle(h, c)
var asked *Message
for i := range tg.sends[before:] {
m := tg.sends[before+i]
if ticketFor(m, "Silence for a week") != "" {
asked = &m
}
}
if asked == nil {
t.Fatalf("the ask's one answer was not offered on Telegram: %+v", tg.sends[before:])
}
if strings.Contains(asked.Body, "disk on anchor") {
t.Errorf("the ask's message was folded with another: %q", asked.Body)
}
}