Files
mesh-catalog/modules/messenger/cmd/messenger/answers_test.go
T

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)
}
}