Answer the review of hq issues 348 and 349: gaps, parts, the newest merge in any state
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 pass: its merge-check.sh passed
mesh/delivery delivered

- A reopened fault keeps its gaps: it was there at a send unless the send
  fell in one, so a send that breaks a machine recovered before it still
  fails its gate (A2).
- An undecided part holds only the conditions that name it (A4).
- D2 holds a silent resolver for the next run again, refused or not: a
  burst of refusals is also a restart (A3).
- A late merge is planned at the newest planned merge of its branch in any
  state, not only an open one (A1); merge times to the nanosecond (A5).
This commit is contained in:
2026-10-09 15:00:56 +02:00
committed by jochen
parent c3c56a69e2
commit 58cb586c37
11 changed files with 347 additions and 179 deletions
+6 -22
View File
@@ -11,7 +11,6 @@ import (
"sort"
"strings"
"sync"
"syscall"
"time"
"github.com/nats-io/nats.go"
@@ -217,8 +216,6 @@ func askEveryResolver(ctx context.Context, resolvers map[string]string, places [
type verdict struct {
wrong, unanswered, said []string
asked int
// refused says every try of every unanswered question was refused (nothing listens there).
refused bool
}
byHolder := map[string]*verdict{}
for _, q := range questions {
@@ -235,7 +232,6 @@ func askEveryResolver(ctx context.Context, resolvers map[string]string, places [
v.wrong = append(v.wrong, words)
v.said = append(v.said, said)
default:
v.refused = (len(v.unanswered) == 0 || v.refused) && q.asked.allRefused()
v.unanswered = append(v.unanswered, words)
v.said = append(v.said, said)
}
@@ -255,11 +251,12 @@ func askEveryResolver(ctx context.Context, resolvers map[string]string, places [
o.Summary = fmt.Sprintf("the mesh's resolver on %s does not answer machine names as it must: %s%s", node,
v.wrong[0], andMore(len(v.wrong)-1))
} else {
// Nothing but silence: held for the next run, which raises it if the resolver is still silent —
// unless every try of every question was refused. A refusal is an answer from the machine:
// nothing listens there. It is not a loaded resolver slow to answer (issue 277), and holding it
// for the next run cost the control node's resolver five minutes on 2026-10-09 (issue 348).
o.Confirm = !v.refused
// Nothing but silence: held for the next run, which raises it if the resolver is still silent.
// Refused on every try too: a few hundred milliseconds of refusals is a resolver restarting as
// well as one that stopped, and the two looks a finding needs are this run and the next
// (novox/hq issue 348). The machine's own node-engine is the fast detector: its names check
// raised the control node's resolver within a minute on 2026-10-09.
o.Confirm = true
o.Summary = fmt.Sprintf("the mesh's resolver on %s does not answer: %d of the %d question(s) about the "+
"machines' names went unanswered, each asked %d times — the first, %s", node, len(v.unanswered), v.asked,
resolverTries, v.unanswered[0])
@@ -348,19 +345,6 @@ type resolverAsked struct {
errs []error
}
// allRefused says every try was refused: the resolver's machine said nothing listens on its port.
func (a resolverAsked) allRefused() bool {
if a.answered || len(a.errs) == 0 {
return false
}
for _, err := range a.errs {
if !errors.Is(err, syscall.ECONNREFUSED) {
return false
}
}
return true
}
// askResolverPatiently asks one question until it is answered as right says, at most resolverTries
// times. A wrong answer is asked again too — a resolver restarting may say NXDOMAIN for a moment — and
// stands if no try answers rightly.