Skip to content

feat(agent): change detection + webhook/syslog alerts (P2-02 / 26.13)#16

Merged
CryptoJones merged 1 commit into
mainfrom
sprint/26.13-alerts
May 27, 2026
Merged

feat(agent): change detection + webhook/syslog alerts (P2-02 / 26.13)#16
CryptoJones merged 1 commit into
mainfrom
sprint/26.13-alerts

Conversation

@CryptoJones
Copy link
Copy Markdown
Owner

Summary

The agent now diffs the host inventory before and after every scan cycle and fires `host.discovered` / `host.vanished` events to configurable sinks. Operators no longer have to tail logs to notice a new device.

```json
{
"alerts": {
"webhook": { "url": "https://hooks.example/x\", "auth_header": "Bearer abc" },
"syslog": { "addr": "udp://syslog.example:514", "tag": "inventory" }
}
}
```

Both sinks ship the same JSON payload (`{type, ip, hostname, mac_address, vendor, device_type, time, agent}`). The syslog sink wraps it in RFC 5424; `rsyslog mmjsonparse` / `syslog-ng` / Splunk all parse out the inner fields for free.

Why two sinks

  • Webhook fits cloud-era stacks: PagerDuty events API, Slack incoming webhooks, custom internal hooks.
  • Syslog fits on-prem stacks: existing SIEM/log-aggregator infrastructure already swallows it.

Operators pick one or both. Empty config disables alerting (no change for existing deployments).

Reliability model

  • Multiplexer fans out in goroutines; one slow/broken sink can't block the scan cycle or the other sinks.
  • WebhookSink retries once on 5xx or network error; 4xx is final (your receiver rejected the payload, retrying won't help).
  • SyslogSink keeps one persistent connection per sink; re-dials on write failure.
  • Cycle-failed scans intentionally skip the diff so a transient DB blip doesn't spam `host.vanished` events for every row.

Test plan

  • `go test -race -timeout 120s ./...` clean across all packages including the new agent + alerts paths
  • `golangci-lint run ./...` → 0 issues
  • RFC 5424 PRI math validated against a live UDP listener test (`facility 16 + severity 6 = 134`)

🤖 Generated with Claude Code

The agent now diffs the host inventory pre- and post-cycle and emits
host.discovered / host.vanished events to configurable sinks.
Operators no longer have to tail logs to notice a new device.

New package internal/alerts:
- Event + EventType (host.discovered, host.vanished). JSON-tagged
  for wire reuse.
- Emitter interface + Multiplexer that fans events out to N sinks
  in parallel goroutines. Sink failures log but don't suppress
  sibling deliveries.
- WebhookSink: HTTP POST JSON, one retry on 5xx/network, no retry
  on 4xx, optional Authorization header.
- SyslogSink: RFC 5424 over UDP/TCP, hand-rolled because
  log/syslog is Unix-only and the agent runs on Windows. Message
  body is the same JSON as the webhook payload, so structured
  syslog parsers get fields for free.
- NoopEmitter for the alerts-disabled default.

Config gains an optional "alerts" section with webhook and syslog
sub-sections. Either or both may be set.

agent.New takes a new Emitter parameter (nil = noop). runCycle
snapshots hosts pre-scan, snapshots again post-prune, and diffs by
IP. Discovered events get fresh enrichment; vanished events get the
last-known pre-cycle row. Cycle-failed runs skip the diff to avoid
alert spam on transient DB errors.

11 new tests: multiplexer fan-out, sibling-survives-peer-error,
webhook auth-header round-trip, 5xx retry, 4xx no-retry, nil-on-
empty guards, syslog RFC 5424 format check against a real UDP
listener (PRI math, MSGID, JSON MSG), bad-scheme rejection,
end-to-end discovered+vanished events from the agent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CryptoJones CryptoJones merged commit d135d98 into main May 27, 2026
@CryptoJones CryptoJones deleted the sprint/26.13-alerts branch May 27, 2026 10:06
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.

1 participant