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 fail: its merge-check.sh failed: --- FAIL: TestTheResolverAndWhatAsksItComposeOnOneMachine (0.03s)
mesh/delivery superseded: a newer head of the same pull request
A refusal of a send the mesh never recorded went only to stderr; it is now kept with its sender said to be unknown, and raised. A counter behind the machine (a store put back) is raised and the condition names push, since the receive loop must not push. The node's digest and generation are one transaction, and a send record that fails is said loudly without failing the send. The trigger ignores an update that changes nothing, and the put-back mark is dropped: a put-back is composed afresh with the current generation.
149 lines
6.1 KiB
Go
149 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// The assignment generation a declaration was composed from (novox/hq issue 234).
|
|
|
|
func bodyKeys(t *testing.T, s sendable) map[string]any {
|
|
t.Helper()
|
|
raw, err := s.Body()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var keys map[string]any
|
|
if err := json.Unmarshal(raw, &keys); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// **The generation goes on the wire only to a machine whose node-engine said it reads one**: an older
|
|
// node-engine decodes strictly and refuses an unknown key, whole. No put-back mark is ever sent: a gate
|
|
// composes its put-back afresh, with the generation as it stands.
|
|
func TestTheGenerationIsSentOnlyToAMachineThatReadsOne(t *testing.T) {
|
|
resources := []map[string]any{{"id": "a", "type": "file", "path": "/etc/a", "content": "x\n"}}
|
|
reads := order{sequence: 12, epoch: 57, generation: 40, readsGeneration: true, acting: 57}
|
|
var told sendable
|
|
told.Resources = resources
|
|
reads.stamp(&told)
|
|
keys := bodyKeys(t, told)
|
|
if keys["generation"] != float64(40) || keys["sequence"] != float64(12) {
|
|
t.Fatalf("a machine that reads a generation was sent %v", keys)
|
|
}
|
|
if _, there := keys["put_back"]; there {
|
|
t.Fatalf("a put-back mark was sent: %v", keys)
|
|
}
|
|
if told.composedFrom != 40 || told.actingEpoch != 57 {
|
|
t.Errorf("the send does not keep what it was composed from for its record: %+v", told)
|
|
}
|
|
|
|
older := order{sequence: 12, epoch: 57, generation: 40, readsGeneration: false, acting: 57}
|
|
var untold sendable
|
|
untold.Resources = resources
|
|
older.stamp(&untold)
|
|
if _, there := bodyKeys(t, untold)["generation"]; there {
|
|
t.Fatal("a machine whose node-engine never said it reads a generation was sent one")
|
|
}
|
|
if untold.composedFrom != 40 {
|
|
t.Errorf("the generation it was composed from is recorded whether or not it was sent: %+v", untold)
|
|
}
|
|
}
|
|
|
|
// **The sender is named**: the caller of the seat call when there is one, else the shell's account, and the
|
|
// process and build that composed it.
|
|
func TestASendNamesItsSender(t *testing.T) {
|
|
said := senderOf("g14.node-tools, through the mesh-controller seat")
|
|
if !strings.Contains(said, "g14.node-tools") || !strings.Contains(said, "pid ") || !strings.Contains(said, "build ") {
|
|
t.Fatalf("the sender reads %q", said)
|
|
}
|
|
}
|
|
|
|
// refusedSend is a send a machine refused for its generation at a moment.
|
|
func refusedSend(at time.Time) inventory.Send {
|
|
return inventory.Send{NodeName: "anchor", Sequence: 12, Epoch: 57, Generation: 38, RefusedApplied: 40,
|
|
Sender: "a one-shot push by jochen at a shell on anchor (pid 4242 on anchor, build 2026.10.11)",
|
|
Modules: []string{"docker"}, SentAt: at.Add(-time.Second), RefusedAt: &at, Recorded: true}
|
|
}
|
|
|
|
// **A refused send is raised naming its sender** (novox/hq issue 234): who sent it, from which generation,
|
|
// against which the machine applied, and its sequence — the facts that took a morning to look for.
|
|
func TestARefusedSendIsRaisedNamingItsSender(t *testing.T) {
|
|
now := time.Date(2026, 10, 11, 12, 0, 0, 0, time.UTC)
|
|
f := calm(now)
|
|
f.refusedSends = []inventory.Send{refusedSend(now.Add(-time.Minute))}
|
|
got := watchGenerationRefusals(f)
|
|
if len(got) != 1 {
|
|
t.Fatalf("%+v", got)
|
|
}
|
|
o := got[0]
|
|
if o.Key() != "machine.anchor.older-generation" || o.Machine != "anchor" {
|
|
t.Errorf("raised as %s about %q", o.Key(), o.Machine)
|
|
}
|
|
for _, want := range []string{"a one-shot push by jochen", "generation 38", "generation 40", "sequence 12"} {
|
|
if !strings.Contains(o.Summary, want) {
|
|
t.Errorf("the summary does not say %q: %s", want, o.Summary)
|
|
}
|
|
}
|
|
if o.Headline == "" || o.Explanation == "" || o.Resolved == "" {
|
|
t.Errorf("the condition is not worded for the operator: %+v", o)
|
|
}
|
|
}
|
|
|
|
// **A refusal of a send the mesh has no record of is raised too, its sender said to be unknown** — never
|
|
// only a line on stderr.
|
|
func TestARefusalOfAnUnrecordedSendIsRaisedSayingTheSenderIsUnknown(t *testing.T) {
|
|
now := time.Date(2026, 10, 11, 12, 0, 0, 0, time.UTC)
|
|
f := calm(now)
|
|
s := refusedSend(now.Add(-time.Minute))
|
|
s.Recorded, s.Sender, s.Modules = false, "a sender the mesh has no record of (no send of this sequence was recorded)", nil
|
|
f.refusedSends = []inventory.Send{s}
|
|
got := watchGenerationRefusals(f)
|
|
if len(got) != 1 || !strings.Contains(got[0].Summary, "no record of") {
|
|
t.Fatalf("an unattributed refusal was not raised as one: %+v", got)
|
|
}
|
|
}
|
|
|
|
// **When the refusal showed the mesh's counter behind the machine, the condition asks for a push** — it was
|
|
// raised, and nothing has sent the machine its declaration since — and does not say there is nothing to do.
|
|
func TestACounterBehindTheMachineAsksForAPush(t *testing.T) {
|
|
now := time.Date(2026, 10, 11, 12, 0, 0, 0, time.UTC)
|
|
f := calm(now)
|
|
s := refusedSend(now.Add(-time.Minute))
|
|
s.Generation, s.RefusedApplied, s.CounterRaisedTo = 12, 40, 41
|
|
f.refusedSends = []inventory.Send{s}
|
|
got := watchGenerationRefusals(f)
|
|
if len(got) != 1 {
|
|
t.Fatalf("%+v", got)
|
|
}
|
|
if !strings.Contains(got[0].Summary, "`push anchor`") || !strings.Contains(got[0].Explanation, "push anchor") ||
|
|
strings.Contains(got[0].Explanation, "Nothing for you to do") {
|
|
t.Errorf("a counter behind the machine does not ask for a push: %s / %s", got[0].Summary, got[0].Explanation)
|
|
}
|
|
}
|
|
|
|
// **Only a refusal of the counter as it stands is the counter behind**: a stale sender's generation is below
|
|
// it, and the counter is left alone for that.
|
|
func TestOnlyARefusalOfTheCounterAsItStandsRaisesIt(t *testing.T) {
|
|
for _, c := range []struct {
|
|
refused, applied, now int64
|
|
behind bool
|
|
}{
|
|
{refused: 12, applied: 40, now: 12, behind: true}, // the store was put back: the mesh says 12, the machine had 40
|
|
{refused: 38, applied: 40, now: 41, behind: false}, // a stale sender: the counter is already past
|
|
{refused: 38, applied: 40, now: 40, behind: false}, // a stale sender: the counter is where the machine is
|
|
} {
|
|
r := link.GenerationRefusal{Generation: c.refused, Applied: c.applied}
|
|
if got := counterBehind(r, c.now); got != c.behind {
|
|
t.Errorf("refused %d, applied %d, counter %d: behind %v, want %v", c.refused, c.applied, c.now, got, c.behind)
|
|
}
|
|
}
|
|
}
|