Compare commits

..
1 Commits
Author SHA1 Message Date
clawbot 8ea6191566 watcher: send the inconsistency alert once per disagreement (closes #158)
check / check (push) Successful in 1m19s
detectInconsistencies alerted for neighbouring pairs of nameservers whose
records differed, on every DNS check, for as long as they differed. It now
also takes the previous hostname state, compares every pair of nameservers,
and alerts for a pair only when both were in that state with equal records.
The state loaded at startup is the previous state for the first check, so a
disagreement saved before a restart is not reported again. The choice of
pairs is tested on record data, and the alert itself through the hostname
change detection with the notifier stand-in and no resolver. The README
describes the new behaviour.

Model: opus-5-5
2026-09-28 23:42:56 +00:00
4 changed files with 15 additions and 26 deletions
+6 -9
View File
@@ -74,15 +74,12 @@ rejected.
This is distinct from "responded with no records."
- **NS recovery**: A previously-unreachable nameserver starts
responding again.
- **Inconsistency detected**: Two nameservers return different record
sets for the same hostname and did not already differ on the previous
check. Every pair of nameservers is compared. The alert is sent once
for each such pair, on the check where they start to disagree, and not
again while they keep disagreeing, including after a restart. A
nameserver that was not in the previous check (newly added, or back
after dropping out) and answers differently is reported on the check
where it appears. If a pair agrees again and later disagrees, the
alert is sent again.
- **Inconsistency detected**: Two nameservers that agreed on the
previous check now return different record sets for the same
hostname. Every pair of nameservers is compared. The alert is sent
once for each such pair, on the check where they start to disagree,
and not again while they keep disagreeing, including after a restart.
If they agree again and later disagree, it is sent again.
### TCP Port Monitoring
+3 -4
View File
@@ -24,10 +24,9 @@ Rationale, Design, TODO, License, Author) if any are still missing.
# Completed Steps
- 2026-09-28: the inconsistency alert is sent once, on the check where two
nameservers start to disagree or where a nameserver that disagrees first
appears, instead of on every check while they disagree, and not again after
a restart. Every pair of nameservers is compared, not only neighbours in
sorted order of name (closes #158).
nameservers that agreed start to disagree, instead of on every check while
they disagree, and not again after a restart. Every pair of nameservers is
compared, not only neighbours in sorted order of name (closes #158).
- 2026-09-28: DNS names in record values (CNAME, MX, SRV and NS targets) are
lower-cased, so nameservers that answer in different letter case no longer
count as inconsistent or as a record change (closes #157).
+3 -9
View File
@@ -80,10 +80,10 @@ func TestNewlyDisagreeingPairs(t *testing.T) {
want: [][][2]string{nil, nil},
},
{
name: "nameserver new on the first check and disagreeing alerts once",
name: "nameserver new on this check does not alert",
loaded: onlyA,
checks: []map[string]map[string][]string{disagree, disagree},
want: [][][2]string{alert, nil},
checks: []map[string]map[string][]string{disagree},
want: [][][2]string{nil},
},
{
name: "disagreement after agreeing again alerts again",
@@ -123,7 +123,6 @@ func TestNewlyDisagreeingPairs(t *testing.T) {
func TestInconsistencyAlert(t *testing.T) {
t.Parallel()
onlyA := map[string]map[string][]string{nsA: {"A": {ip1}}}
agree := map[string]map[string][]string{nsA: {"A": {ip1}}, nsB: {"A": {ip1}}}
disagree := map[string]map[string][]string{nsA: {"A": {ip1}}, nsB: {"A": {ip2}}}
@@ -144,11 +143,6 @@ func TestInconsistencyAlert(t *testing.T) {
loaded: disagree,
want: 0,
},
{
name: "nameserver new on the first check and disagreeing alerts once",
loaded: onlyA,
want: 1,
},
}
for _, tt := range tests {
+3 -4
View File
@@ -471,9 +471,8 @@ func (w *Watcher) detectInconsistencies(
}
// newlyDisagreeingPairs returns every pair of nameservers whose records
// differ in current, in sorted order of name, except pairs where both
// nameservers were in prev and already differed there. A nameserver
// missing from prev is paired with every nameserver it differs from.
// differ in current but were equal in prev, in sorted order of name.
// A pair with a nameserver missing from prev is not returned.
func newlyDisagreeingPairs(
prev *state.HostnameState,
current map[string]map[string][]string,
@@ -496,7 +495,7 @@ func newlyDisagreeingPairs(
prev1, ok1 := prev.RecordsByNameserver[ns1]
prev2, ok2 := prev.RecordsByNameserver[ns2]
if ok1 && ok2 && !recordsEqual(prev1.Records, prev2.Records) {
if !ok1 || !ok2 || !recordsEqual(prev1.Records, prev2.Records) {
continue
}