module

Krikri::VariableSubstitutor::HostvarsContext

Per-host variable context for hostvars[<other host>] values.

Ansible renders a value read through hostvars['other'] with THAT host's own templar (HostVarsVars): the value's {{ myname }} resolves against the other host's inventory vars/facts/registered vars/inventory_hostname, never the reading host's. This engine's re-render funnels (Rerender, ExpressionEvaluator's plain-lookup re-render, the span re-pass, JinjaRenderer.prepare_hostvars) all rendered with the CURRENT host's scope - found via a two-host inventory where who: "{{ myname }}" read through hostvars['h2'].who came back with the reading host's myname.

The rendering scope built here is the other host's own entry (inventory vars + facts + registered vars + inventory_hostname, as assembled by TaskExecutor#build_hostvars) laid OVER the reading host's scope: entry keys win, everything the entry does not carry (play/role vars, shared magic vars like groups/playbook_dir, hostvars itself) falls back to the reading host's scope - for two hosts in the same play those are identical in Ansible, and the entry carries every per-host difference. Lookups are unaffected: hostvars['h2'].missing_key still resolves against the raw entry alone (undefined), exactly as before - only the re-render of a value that WAS found uses this scope.

Unsafe-data semantics stay per host: the merged scope's inventory_hostname (and therefore its resolved-var-name registry via VarSubstitutor.host_from_vars) is the OTHER host's, so its own registered results/set_facts stay execution-resolved (never re-rendered) while the reading host's registry does not silence the other host's author vars. The value-level registry (UnsafeValues.unsafe_text?) is process-global text matching and already guards hostile content relayed through hostvars either way.

Constants

MAX_CACHE_ENTRIES = 256

Class methods

merged_vars(host : String, vars : Hash(String, JSON::Any)) : Hash(String, JSON::Any) | Nil

The other host's rendering scope: its raw entry laid over the reading host's vars (entry wins), with inventory_hostname pinned to the other host so the per-host unsafe-name registry resolves correctly even for an entry that lacks the marker. nil when host has no hostvars entry (the caller then keeps the current scope, the pre-existing behavior).

Source
origin_host(vars : Hash(String, JSON::Any) | Nil, source_expr : String | Nil) : String | Nil

The other host whose entry source_expr reads through, when the expression is rooted at the hostvars magic variable: hostvars['h2'].who, hostvars[item]['nm'], hostvars.h2.nm. The first bracket/attr key is resolved (a quoted literal directly, anything else through the evaluator, so hostvars[item] in a loop works), then validated against the actual hostvars hash - a plain variable the play happens to name "hostvars" is not treated as the magic (its entries would not carry the inventory_hostname marker the executor always writes). nil for every other shape: those keep the pre-existing current-scope rendering.

Source
origin_host_and_key(vars : Hash(String, JSON::Any) | Nil, source_expr : String | Nil) : Tuple(String, String) | Nil

#origin_host plus the first ENTRY-LEVEL key the expression reads (hostvars['h2'].r.stdout -> {"h2", "r"}) - the name the OTHER host's unsafe-name registry is consulted on, since registered results/set_facts are execution data and must never be re-rendered no matter which host reads them.

Source
origin_unsafe?(vars : Hash(String, JSON::Any) | Nil, source_expr : String | Nil) : Bool

Whether source_expr reads a hostvars entry key that is execution-resolved for the OWNING host (its registered results / set_facts / facts) - such values are execution data and must never be re-rendered, even though the READING host's own registry knows nothing of the name.

Source
substitutor_for(host : String, vars : Hash(String, JSON::Any)) : VarSubstitutor

A VarSubstitutor rendering as the other host - same merged scope, with the substitutor's own host identity (and therefore its unsafe-name registry lookups) pointed at the other host.

Source
substitutor_for?(host : String, vars : Hash(String, JSON::Any) | Nil) : VarSubstitutor | Nil

The same, but nil when host has no hostvars entry - for call sites that must fall back to their pre-existing behavior instead of rendering in a guessed scope.

Source