core/collector.go
Rama main · réplica de lectura del repositorio original.
package core
import (
"context"
"time"
)
// Collector is the ONLY thing a new product must implement. Everything generic
// a product needs — scheduling, concurrency, storage, dedup, diffing,
// notification, display — is provided by the core. Adding product #2 means
// writing a new Collector, not a new application.
type Collector interface {
// Describe returns static metadata. Called once at registration.
Describe() ModuleInfo
// ValidateTarget parses and normalises raw user input into a canonical
// Target, or returns a user-facing error explaining what's wrong. Called
// when a user adds a target — never during a scan.
ValidateTarget(raw string) (Target, error)
// Collect performs one check of one target and returns findings. This is
// where ALL the product's intelligence lives. It MUST:
// - respect ctx cancellation/timeout,
// - be side-effect free with respect to the target beyond reading it,
// - fill only collector-owned Finding fields,
// - return a deterministic Fingerprint per finding.
// An empty slice with nil error means "checked, all clear". A non-nil
// error means the scan itself failed (host unreachable, timeout) — NOT
// that a problem was found. A problem found is a Finding.
Collect(ctx context.Context, t Target) ([]Finding, error)
// Diff is OPTIONAL. Return nil to defer to the core's fingerprint-based
// diff. Implement it only when "what changed since last scan" is a
// first-class product output (e.g. Attack Surface Monitor).
Diff(previous, current []Finding) []Change
}
// ModuleInfo is a collector's static metadata, read once at registration.
type ModuleInfo struct {
ID string // "certwatch" — becomes Finding.Module
Name string // "CertLight"
Version string // semver
TargetKind string // human hint: "host:port", "domain", "device"
DefaultInterval time.Duration // smart default; user may override (paid tier)
ResolveAfter int // consecutive-absent scans before auto-resolve (>=1)
}
// ResolveAfterOrDefault returns the effective auto-resolve threshold, defaulting
// to 1 when a module leaves it unset. One is right for deterministic checks;
// flappy modules raise it to avoid resolve/reopen churn (decision #2).
func (m ModuleInfo) ResolveAfterOrDefault() int {
if m.ResolveAfter < 1 {
return 1
}
return m.ResolveAfter
}
// Target is a validated, canonicalised scan target.
type Target struct {
Raw string // exactly what the user typed
Canonical string // normalised, e.g. "example.com:443"
Meta map[string]string // collector-specific parsed bits
}
// Change is an entry produced by an optional Collector.Diff implementation.
type Change struct {
Kind string // "added" | "removed" | "changed"
Finding Finding
Detail string
}