Decision 0018 — Opt-in aggregate check events with governed `--limit-hosts`
Opt-in aggregate check events carrying the present host set, superseded by Decision 0019 which removes them.
Status: accepted · 2026-09-15
Context
Per-target check events cannot express "run once over the hosts that are
present": the headline case needs a set, not one event per host. The design
deferred a set-event because expanding an event into a run's --limit would let
attacker-influenced text shape argv — the trusted-source line
(design/check-triggered-runs.md §3.3). A
webhook body lands in a durable table and must never reach ansible argv; a
sweep-produced event is written by the daemon's own probe code.
Decision
A check may opt into an aggregate event (check_row.aggregate, default off).
When enabled, the sweep reads the present set (targets currently up) against a
threshold (min_up, 0 = all), and on a declared transition emits one event
keyed check:<check>:aggregate carrying Hosts — the sorted present target
names. The rollup's own hysteresis state lives in check_state under the
reserved target name *, which is not a valid target name and is filtered
everywhere a state row is read as a target.
reactive.Event gains Hosts []string and exposes event.hosts to CEL. The
field is populated only by the sweep; every webhook normalizer leaves it
empty, and a test pins that.
A playbook rule action may set --limit-hosts only when its source filter is
check. At fire time the limit is derived from event.Hosts under four
refusals: a non-check source, an empty host set, an invalid name, and a host not
declared as a target of the check the event names. The last two are what keep
the expansion inside the daemon-controlled set, so no event value reaches argv
unvalidated. A rule without --limit-hosts keeps its fixed, rule-add-time
--limit.
Consequences
- The feature is default-off; a check that does not set
aggregatebehaves exactly as before, and no existing rule changes. - The trusted-source line is enforced at the spec builder, not by convention: only daemon-produced sweep events can carry a host set, and only the named check's own targets can appear in the derived limit.
- A new dedup/cooldown key (
check:<check>:aggregate) sits alongside the per-target keys; the per-source cooldown split still governs. - Unproven until dogfooded. The "run over the present subset" need is the least-evidenced part of the design; if the operator's real work is already served by per-target rules, the feature is removed rather than carried (the plan's S9 kill criterion).