// Package contract defines the DiscoveryEntry types published by // checker-dangling and consumed by companion checkers (notably // domain-expiry, which subscribes to ExternalTargetType to perform RDAP // on the target's registrable domain). // // This package is deliberately tiny and dependency-light so that any // consumer can import it without dragging the whole checker in. package contract import ( "encoding/json" "fmt" sdk "git.happydns.org/checker-sdk-go/checker" ) // ExternalTargetType is the DiscoveryEntry.Type for an out-of-zone // pointer target (CNAME/MX/SRV/NS host) whose registrable domain is // distinct from the zone apex. Consumers subscribed to this type are // expected to look up the registrable domain (RDAP/WHOIS) and publish a // "whois" observation per entry Ref. const ExternalTargetType = "dangling.external-target.v1" // InZoneTargetType is the DiscoveryEntry.Type for a pointer target that // resolves within the same zone or the same registrable domain. It is // declared so future probing checkers (ping/http/alias) can subscribe to // it for in-zone reachability checks. v1 of checker-dangling does not // itself rely on observations attached to in-zone entries. const InZoneTargetType = "dangling.in-zone-target.v1" // ExternalTarget is the payload of an ExternalTargetType entry. // // Owner is the FQDN whose pointer is at risk (e.g. "old-promo.example.com."). // Pointer captures the type+target verbatim so a consumer can refer to // the precise record when reporting findings. Registrable is the eTLD+1 // (or PSL-derived equivalent) that an RDAP probe should query. type ExternalTarget struct { Owner string `json:"owner"` Rrtype string `json:"rrtype"` // "CNAME", "MX", "SRV", "NS" Target string `json:"target"` // FQDN, no trailing dot Registrable string `json:"registrable"` } // InZoneTarget mirrors ExternalTarget for in-zone or same-registrable // pointers. Registrable is set when known so a subscriber can decide to // skip records that point at the user's own domain. type InZoneTarget struct { Owner string `json:"owner"` Rrtype string `json:"rrtype"` Target string `json:"target"` Registrable string `json:"registrable,omitempty"` } // Ref builds the canonical, stable Ref for a (owner, rrtype, target) // triple. Callers must use this on both the producer and consumer side // so RelatedObservation.Ref correlates with the right entry. func Ref(owner, rrtype, target string) string { return fmt.Sprintf("%s|%s|%s", owner, rrtype, target) } // NewExternalEntry builds a DiscoveryEntry of type ExternalTargetType // with the canonical Ref. func NewExternalEntry(t ExternalTarget) (sdk.DiscoveryEntry, error) { payload, err := json.Marshal(t) if err != nil { return sdk.DiscoveryEntry{}, fmt.Errorf("marshal external target: %w", err) } return sdk.DiscoveryEntry{ Type: ExternalTargetType, Ref: Ref(t.Owner, t.Rrtype, t.Target), Payload: payload, }, nil } // NewInZoneEntry builds a DiscoveryEntry of type InZoneTargetType. func NewInZoneEntry(t InZoneTarget) (sdk.DiscoveryEntry, error) { payload, err := json.Marshal(t) if err != nil { return sdk.DiscoveryEntry{}, fmt.Errorf("marshal in-zone target: %w", err) } return sdk.DiscoveryEntry{ Type: InZoneTargetType, Ref: Ref(t.Owner, t.Rrtype, t.Target), Payload: payload, }, nil }