Skip to content

Levels vs occurrences

DeclaresArbitrated?Types
Desired-statea level — what a target should beyes, continuously across every automation sharing the targetkubernetes.scale, kubernetes.cronjob.suspend, kubernetes.cordon
Edgean occurrenceno — fires on this automation’s own transition and owns nothingkubernetes.restart, http.request, notification.*, homeassistant.service, qbittorrent.*, unifi.wlan.*, unifi.poe.cycle

kubernetes.scale works through the scale subresource, so kind: Deployment and kind: StatefulSet take the same path and Reactor never has to know where a kind keeps its replicas. target.kind is still a closed list, on purpose: a kind is only reachable if the chart granted RBAC for it, and RBAC has to name resources explicitly — so an open field would turn a typo into a Forbidden discovered during the outage, instead of a rejected write at admission.

A level is ordered and nothing else: lower is more restrictive, and a shared target resolves to the lowest anyone asked for. For kubernetes.scale that is the replica count, so shedding wins. For kubernetes.cronjob.suspend and kubernetes.cordon it is a switch, ordered so that suspended and cordoned are the restrictive answers — which means suspended wins over running for exactly the same reason 0 replicas wins over 3, and with no new rule to learn. status.targets[].level says which in words.

What decides which column an action lands in is not whether it expresses a level. Pausing torrents plainly does, and so does a WLAN being enabled, and both are edge actions anyway. It is whether there is somewhere to record the value the target held before Reactor claimed it — because without that, release cannot put it back, and an automation that cannot hand a target back has no business claiming it. For a Kubernetes object that place is an annotation on the object. For anything else there is no answer yet, which is why every desired-state action so far is a kubernetes.* one, and why the actions that reach outside the cluster are named as verbs.

The HPA decline path is the same rule seen from the other side: a scalable target another controller drives can be refused and handed back to what it was, and it can be handed back precisely because the baseline is on the object. Nothing outside the cluster has that, so nothing outside the cluster can be declined back to anything.