Say which input woke each placement candidate's answer, so a probe's safety check tells a loop from moving inputs (issue 1126)
mesh/delivery merged: source.main moved to novox/mesh-host@690aa662a837
mesh/delivery-group group fix/1126-vault-own merged: every member is merged
mesh/check pass 8bd0b5db15a3 on b4fd2cc210bf | unit 1639 run, 4201 kept, 46 skipped | integration 1674 run, 4215 kept, 47 skipped | over bound: unit, …

Each answer's line now reads 'answering placement candidate G (revision N; woken by X)', X being its first answer,
a retry after a read the bus did not answer, the candidate, or the inputs that changed (placement, records, modules,
values, haves, wants). The single input counter becomes one per input, which also decides whether a group is answered
again, as before.

Quick fix weighed: a counter word in the line. Thorough fix chosen: one count per input, so the line names exactly what
moved since the group's last answer.
This commit is contained in:
2026-10-28 14:44:27 +01:00
parent b4fd2cc210
commit 8bd0b5db15
2 changed files with 69 additions and 13 deletions
+22 -2
View File
@@ -836,6 +836,20 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
if got := answering(); got != settled+1 {
t.Errorf("a new revision of one group answered %d group(s), want 1", got-settled)
}
// Issue 1126, 2026-10-28 (seed-9's probe): each answer says which input woke it, so a loop is told from inputs moving.
lastSaid := func(prefix string) string {
readOnly.mu.Lock()
defer readOnly.mu.Unlock()
for i := len(readOnly.said) - 1; i >= 0; i-- {
if strings.HasPrefix(readOnly.said[i], prefix) {
return readOnly.said[i]
}
}
return ""
}
if got := lastSaid("answering placement candidate same "); got != "answering placement candidate same (revision 8; woken by the candidate)" {
t.Errorf("a new revision's answer said %q", got)
}
settled = answering()
hk, _ := delivery.HaveKey("n1", "lemurs")
moved, _ := delivery.EncodeHave(delivery.Have{FP: fp, Composed: "sha256:" + strings.Repeat("e", 64), Phase: delivery.PhaseRunning})
@@ -844,6 +858,9 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
if got := answering(); got != settled+len(answers) {
t.Errorf("what runs moved, and %d answer(s) were composed again for %d group(s)", got-settled, len(answers))
}
if got := lastSaid("answering placement candidate drops "); got != "answering placement candidate drops (revision 5; woken by haves)" {
t.Errorf("an answer woken by what runs moving said %q", got)
}
// A read the bus did not answer is no answer to keep: the group is answered again after a bound, though nothing moved.
setFor(t, &laterAgain, []time.Duration{20 * time.Millisecond})
ownFails.Store(true)
@@ -856,6 +873,9 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
return a["adds"].Revision == 9 && a["adds"].Verdict == delivery.CandidateDiffers
})
drain()
if got := lastSaid("answering placement candidate adds "); got != "answering placement candidate adds (revision 9; woken by a retry after a read the bus did not answer)" {
t.Errorf("an answer composed again after the bus did not answer said %q", got)
}
k, _ := delivery.PlacementCandidateKey("same", "n1")
sb.del(delivery.PlacementCandidatesBucket, k)
waitFor("the deleted group's answer stays", func(a map[string]delivery.CandidateAnswer) bool { _, ok := a["same"]; return !ok && len(a) == 4 })
@@ -973,8 +993,8 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
// Issue 1126, T2: an answer says when it starts and when it ends (and, every minute between, what it waits on).
nmu.Lock()
said := strings.Join(nsaid, "\n")
for _, line := range []string{"the answer to same took", "answering placement candidate same (revision 3)",
"answered placement candidate same in", "answering placement candidate older (revision 2)"} {
for _, line := range []string{"the answer to same took", "answering placement candidate same (revision 3; woken by its first answer since the shadow began)",
"answered placement candidate same in", "answering placement candidate older (revision 2;"} {
if !strings.Contains(said, line) {
t.Errorf("%q was not said: %q", line, nsaid)
}