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
Class methods
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).
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.
#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.
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.
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.