fix(ingestor): skip default_scope update when ScopeName is empty (#1534) (#1569)

Red commit: e5668585da

Fixes #1534

## Problem
`cmd/ingestor/main.go:720` called `UpdateNodeDefaultScope` whenever a
packet was transport-scoped (`IsTransportScoped == true`), without
checking whether `matchScope()` actually returned a region match.
Transport-scoped adverts from non-matching regions carry `ScopeName=""`,
which then overwrote previously-correct `nodes.default_scope` values
with the empty string — surfacing as "unknown scope" / "--" in the node
sidebar.

## Fix
Extracted the guard into `shouldUpdateDefaultScope(pktData)` and added
the non-empty `ScopeName` check:

```go
return pktData.IsTransportScoped && pktData.ScopeName != ""
```

## TDD
- Red commit (`e5668585`): adds
`TestBuildPacketDataScopeMatchingNoMatch` + helper that mirrors the
buggy guard. CI must fail on assertion.
- Green commit (`aab7f5d7`): adds the `ScopeName != ""` check. Test
passes.

## Out of scope (deferred)
- The optional one-time backfill / migration marker removal described in
the issue — new matching adverts will self-correct existing rows.
- Refactor of `IsTransportScoped` + `ScopeName` into a typed wrapper.

## Files
- `cmd/ingestor/main.go` — guard + new helper
- `cmd/ingestor/main_test.go` — regression test

## Preflight
`bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master`
— clean.

---------

Co-authored-by: corescope-bot <bot@corescope.local>
This commit is contained in:
Kpa-clawbot
2026-06-04 10:06:13 -07:00
committed by GitHub
parent 2b45f7872c
commit 05af6c6ee5
2 changed files with 45 additions and 2 deletions
+10 -2
View File
@@ -715,8 +715,8 @@ func handleMessage(store *Store, tag string, source MQTTSource, m mqtt.Message,
log.Printf("MQTT [%s] node telemetry update error: %v", tag, err)
}
}
// Update default_scope when advert carries a matched transport scope (#899)
if pktData.IsTransportScoped {
// Update default_scope when advert carries a matched transport scope (#899, #1534)
if shouldUpdateDefaultScope(pktData) {
if err := store.UpdateNodeDefaultScope(decoded.Payload.PubKey, pktData.ScopeName); err != nil {
log.Printf("MQTT [%s] node default_scope update error: %v", tag, err)
}
@@ -1356,3 +1356,11 @@ func init() {
os.Exit(0)
}
}
// shouldUpdateDefaultScope returns true when the packet carries a transport
// scope whose region key matched (#1534). Without the ScopeName non-empty
// guard, transport-scoped adverts from non-matching regions would overwrite
// previously-correct default_scope values with the empty string.
func shouldUpdateDefaultScope(pktData *PacketData) bool {
return pktData.IsTransportScoped && pktData.ScopeName != ""
}
+35
View File
@@ -1053,3 +1053,38 @@ func TestHandleMessageObserverIATAWhitelist(t *testing.T) {
t.Errorf("observer from whitelisted IATA ARN should be accepted, got count=%d", count)
}
}
// TestBuildPacketDataScopeMatchingNoMatch covers the #1534 regression: a
// transport-scoped advert from a non-matching region carries
// IsTransportScoped=true and ScopeName="". The default_scope update guard
// must skip these packets so previously-correct scopes aren't overwritten
// with the empty string.
func TestBuildPacketDataScopeMatchingNoMatch(t *testing.T) {
// Code1=2AB5 is the precomputed code for region "#test" (payload="hello",
// payloadType=5). Build a region-key map for a DIFFERENT region so
// matchScope() finds no match and returns "".
const rawHex = "142AB500000068656C6C6F"
otherKey, _ := hex.DecodeString("aabbccddeeff00112233445566778899")
regionKeys := map[string][]byte{"#other": otherKey}
decoded, err := DecodePacket(rawHex, nil, false)
if err != nil {
t.Fatalf("DecodePacket: %v", err)
}
msg := &MQTTPacketMessage{Raw: rawHex}
pktData := BuildPacketData(msg, decoded, "obs1", "region1", regionKeys)
if !pktData.IsTransportScoped {
t.Fatalf("precondition: IsTransportScoped should be true (Code1 != 0000)")
}
if pktData.ScopeName != "" {
t.Fatalf("precondition: ScopeName should be empty (no region match), got %q", pktData.ScopeName)
}
// Regression assertion: when ScopeName is empty, the guard must skip the
// UpdateNodeDefaultScope call so an empty value never overwrites a
// previously-correct default_scope (#1534).
if shouldUpdateDefaultScope(pktData) {
t.Errorf("shouldUpdateDefaultScope = true for empty ScopeName; want false (would overwrite default_scope with \"\")")
}
}