Skip to content

feat(config): warn on non-canonical target connect host (#396) - #400

Merged
fheikens merged 1 commit into
mainfrom
396-noncanonical-host-warning
Aug 29, 2026
Merged

feat(config): warn on non-canonical target connect host (#396)#400
fheikens merged 1 commit into
mainfrom
396-noncanonical-host-warning

Conversation

@fheikens

Copy link
Copy Markdown
Contributor

Root cause (reproduced end-to-end — two produced ZIPs; full write-up on #396)

The Analyzer keys database identity on the composite (cluster_key, database_name) where cluster_key = lower(trim(host)):port, with the host echoed verbatim from the export's target_identity. Signals faithfully emits the configured host (a produced ZIP shows {"host":"postgres","dbname":"postgres"}), so the same database collected under two different host strings — its DNS endpoint from one Signals instance vs a loopback/tunnel address or a resolved IP from another — yields two distinct cluster_keys. The Analyzer recognizes the first and holds the second as an unknown identity, silently leaving those snapshots unprocessed.

The filename-prefix "hash" in the issue is a random instance_id (changes only on a Signals DB/volume reset), not the cluster identity — so the two prefixes were two Signals instances, and the split was driven by a non-canonical connect host in one instance's config, not any Signals identity-derivation defect. Signals core already does exactly what the issue asks ("always the configured connect host, never a resolved IP").

Fix

Signals cannot know which host string a database was registered under elsewhere, so it cannot unify them — but it can stop the failure being silent. This adds a startup WARNING when an enabled target's connect host is a loopback (localhost / 127.0.0.0/8 / ::1) or a bare IP literal — the non-canonical identities that split against a DNS endpoint. It logs the target name + reason class only, never the host value or credentials (R078 no-leak discipline). This would have flagged the live incident at boot.

The operational rule (documented on #396): one Signals feed per target, always the canonical endpoint host — never a parallel local/tunnel Signals run emitting a different host for the same DB.

Verification

  • NonCanonicalHostReason unit-tested across loopback names, IPv4/IPv6 loopback, private + public IP literals, and DNS hostnames.
  • gofmt/go vet/go test green; golangci-lint 0 issues; docs-drift + de-arq guards pass.

closes #396

Root cause of #396 (reproduced end-to-end with two produced ZIPs): the
Analyzer keys database identity on the composite (cluster_key,
database_name) where cluster_key = lower(trim(host)):port, with the host
echoed verbatim from the export's target_identity. Signals faithfully
emits the CONFIGURED host, so collecting the same database under two
different host strings (its DNS endpoint from one Signals instance and a
loopback/tunnel address or a resolved IP from another) yields two
distinct cluster_keys: the Analyzer holds the second as an unknown
identity and its snapshots are silently held unprocessed.

Signals cannot know the canonical host a database was registered under
elsewhere, so it cannot unify the two — but it can stop the failure
being SILENT. Add a startup WARNING when an enabled target's connect
host is a loopback (localhost / 127.0.0.0/8 / ::1) or a bare IP literal:
a non-canonical identity that will split against the DNS endpoint. It
logs the target name + reason class only, never the host value or
credentials (R078 no-leak discipline).

NonCanonicalHostReason is unit-tested across loopback names, IPv4/IPv6
loopback, private + public IP literals, and DNS hostnames.

closes #396
@fheikens
fheikens merged commit 56311aa into main Aug 29, 2026
11 checks passed
@fheikens
fheikens deleted the 396-noncanonical-host-warning branch August 29, 2026 19:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Same RDS target exported under TWO different cluster-key hashes → Analyzer holds the second as an unrecognized database

1 participant