|
| 1 | +== 4. Host-level Exclusion Predicates for Check Executions |
| 2 | + |
| 3 | +[width="100%",cols="<18%,<82%",] |
| 4 | +|=== |
| 5 | +|Feature Name | Host-level Exclusion Predicates for Check Executions |
| 6 | +|Start Date | Apr 22nd, 2026 |
| 7 | +|Category | Architecture, Checks |
| 8 | +|PR | TBD |
| 9 | +|=== |
| 10 | + |
| 11 | +== Summary |
| 12 | + |
| 13 | +Let a check declare an optional `exclude:` Rhai predicate; Wanda evaluates it per target using host metadata supplied by Trento Web, and drops excluded hosts before dispatching the gathering request. |
| 14 | + |
| 15 | +== Motivation |
| 16 | + |
| 17 | +Today a check execution runs against every host of the selected target — either a single host, or every host of a cluster. There is no way for a check author to declare "this check does not apply to host X" based on properties of the host itself. |
| 18 | + |
| 19 | +We want check authors to express, inside the check definition, a predicate that decides per-host whether the check should run at all, evaluated against data the Web UI already knows about each host. |
| 20 | + |
| 21 | +=== Use Cases outline |
| 22 | + |
| 23 | +* As a check author, I want to declare that a check does not apply to hosts with certain properties (e.g. provider, OS family, cluster role), so that I don't need to maintain near-duplicate checks. |
| 24 | +* As a user running an execution, I want excluded hosts to be clearly surfaced in the result, distinct from failed or skipped ones, so that I can tell why a host was not evaluated. |
| 25 | +* As a catalog maintainer, I want applicability logic to live next to the check it belongs to, instead of being scattered across Web, Wanda and catalog curation. |
| 26 | + |
| 27 | +== Detailed Design |
| 28 | + |
| 29 | +=== Overview |
| 30 | + |
| 31 | +* Trento Web attaches a small, structured "host data" payload to each target in the execution request. |
| 32 | +* The execution-request contract is extended so each target carries its host data alongside `agent_id` and `checks`. |
| 33 | +* A check definition gains an optional top-level `exclude:` field, containing a Rhai expression evaluated against the host data. |
| 34 | +* Wanda evaluates the `exclude` predicate per (host, check) pair and filters out excluded pairs before dispatching `FactsGatheringRequested` to the agents. |
| 35 | +* Excluded pairs are reported in the execution result with a clear "excluded by policy" status, distinct from failed or skipped, so the UI can render it. |
| 36 | + |
| 37 | +=== Component changes |
| 38 | + |
| 39 | +==== Trento Web |
| 40 | + |
| 41 | +For each host in the execution request, attach a free-form `attributes` map (see link:#_contracts[Contracts]). Initial keys are whatever the catalog asks for — plausible candidates drawn from the existing host read model include `provider`, `os_family`, `arch`, `roles`, `cluster_role`, `is_majority_maker` — but the proto does not fix the set, and Web is free to start emitting new keys when a check author needs them. |
| 42 | + |
| 43 | +No UI change is required for v1: the data is already available server-side; we just need to forward it. |
| 44 | + |
| 45 | +==== Contracts |
| 46 | + |
| 47 | +The change is confined to the `Target` message in `target.proto`, which is already carried by `ExecutionRequested.targets`. `FactsGatheringRequested` is *not* affected: host data is consumed by Wanda before dispatch and is never forwarded to agents. |
| 48 | + |
| 49 | +===== Current shape |
| 50 | + |
| 51 | +[source,proto] |
| 52 | +---- |
| 53 | +// contracts/proto/target.proto |
| 54 | +syntax = "proto3"; |
| 55 | +package Trento.Checks.V1; |
| 56 | +
|
| 57 | +message Target { |
| 58 | + string agent_id = 1; |
| 59 | + repeated string checks = 2; |
| 60 | +} |
| 61 | +---- |
| 62 | + |
| 63 | +===== Proposed shape |
| 64 | + |
| 65 | +Add a single new field `attributes` on `Target`, typed as `map<string, google.protobuf.Value>`. The map is free-form: there are no typed fields, no reserved keys, and no schema-level contract on the set of attributes Web emits. Values use `google.protobuf.Value` so scalars, lists and nested structs are all representable without breaking the wire format. This mirrors the existing `ExecutionRequested.env` field (same package, same import), so we do not introduce a new convention. |
| 66 | + |
| 67 | +[source,proto] |
| 68 | +---- |
| 69 | +// contracts/proto/target.proto |
| 70 | +syntax = "proto3"; |
| 71 | +package Trento.Checks.V1; |
| 72 | +
|
| 73 | +import "google/protobuf/struct.proto"; |
| 74 | +
|
| 75 | +message Target { |
| 76 | + string agent_id = 1; |
| 77 | + repeated string checks = 2; |
| 78 | +
|
| 79 | + // Free-form host attributes known to Trento Web at request time. |
| 80 | + // Consumed by Wanda to evaluate each check's `exclude` predicate; |
| 81 | + // never forwarded to agents. |
| 82 | + // |
| 83 | + // Keys are opaque strings agreed between Trento Web (producer) and |
| 84 | + // check authors (consumers); the contract on which keys exist and |
| 85 | + // what they mean lives outside the proto file. |
| 86 | + map<string, google.protobuf.Value> attributes = 3; |
| 87 | +} |
| 88 | +---- |
| 89 | + |
| 90 | +===== Rhai surface |
| 91 | + |
| 92 | +Wanda exposes `Target.attributes` to predicates as a single `host` object, where each entry is accessed by key: `host["provider"]`, `host["is_majority_maker"]`, `host["cluster_role"]`. Absent keys return `()` so predicates can guard with the usual Rhai patterns. |
| 93 | + |
| 94 | +==== Checks |
| 95 | + |
| 96 | +A new optional top-level field `exclude:` on a check, containing a Rhai expression. |
| 97 | + |
| 98 | +Contract: |
| 99 | + |
| 100 | +* The script receives one host's data as input and must return a boolean. |
| 101 | +* `true` => exclude this host from this check. |
| 102 | +* Absent field => never exclude (current behaviour). |
| 103 | + |
| 104 | +.Example: exclude majority maker nodes from a cluster check |
| 105 | +[source,yaml] |
| 106 | +---- |
| 107 | +id: "ABC123" |
| 108 | +name: Check that does not apply to majority maker nodes |
| 109 | +exclude: | |
| 110 | + host.is_majority_maker == true |
| 111 | +facts: |
| 112 | + - ... |
| 113 | +expectations: |
| 114 | + - ... |
| 115 | +---- |
| 116 | + |
| 117 | +In an HA cluster, a _majority maker_ node participates in quorum decisions but does not run the workload being checked. Checks targeting the workload (e.g. SAP or database configuration) should skip such nodes entirely rather than produce spurious failures or forcing authors to maintain a separate "non-majority-maker" variant of the check. |
| 118 | + |
| 119 | +==== Wanda |
| 120 | + |
| 121 | +Before dispatching `FactsGatheringRequested` to agents, Wanda: |
| 122 | + |
| 123 | +1. Evaluates each check's `exclude` predicate against each target's host data. |
| 124 | +2. Filters the (host, check) pairs accordingly. |
| 125 | +3. Reports excluded pairs in the execution result with a clear "excluded by policy" status, distinct from failed / skipped, so the UI can render it. |
| 126 | + |
| 127 | +The predicate runs in the same Rhai sandbox Wanda already uses for checks; no new sandboxing is introduced. |
| 128 | + |
| 129 | +== Non-goals |
| 130 | + |
| 131 | +* Changing how `when:` conditions work. |
| 132 | +* Letting the predicate query remote facts: it only sees host data Web already has; no extra round-trip. |
| 133 | +* User-authored ad-hoc exclusions from the UI. |
| 134 | +* Security sandboxing changes: `exclude` runs in the same Rhai sandbox Wanda already uses for checks. |
| 135 | + |
| 136 | +== Summary of Changes |
| 137 | + |
| 138 | +[cols="20,20,60"] |
| 139 | +|=== |
| 140 | +|Action |Target |Description |
| 141 | + |
| 142 | +|Attach host data to execution targets |
| 143 | +|Trento Web |
| 144 | +|For each host in an execution request, forward a structured "host data" payload (majority maker, provider, OS family, roles, arch, cluster role, ...) to Wanda. |
| 145 | + |
| 146 | +|Extend `Target` message |
| 147 | +|Contracts |
| 148 | +|Add `map<string, google.protobuf.Value> attributes = 3;` to `Target` in `target.proto`. Free-form: no typed fields, no reserved keys. Purely additive; producer/consumer alignment on key names lives outside the proto. |
| 149 | + |
| 150 | +|New optional `exclude:` field on checks |
| 151 | +|Checks catalog |
| 152 | +|Top-level Rhai expression that receives one host's data and returns a boolean. `true` excludes the host from the check; absent field preserves current behaviour. |
| 153 | + |
| 154 | +|Evaluate `exclude` predicates before dispatch |
| 155 | +|Wanda |
| 156 | +|Filter (host, check) pairs before sending `FactsGatheringRequested` to agents. Report excluded pairs in the execution result with a distinct "excluded by policy" status. |
| 157 | + |
| 158 | +|=== |
0 commit comments