mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
The include_role vars block referenced `_load_app_id`, a host fact
created by `set_fact` at the top of load_app.yml. Host facts live in
the non-persistent fact cache (vars/manager.py:405) and survive across
include frames, so every nested `include_tasks: utils/load_app.yml`
call overwrote `_load_app_id` for the whole play.
Inside the coturn deploy chain (web-svc-coturn -> sys-stk-full ->
sys-stk-backend -> sys-svc-compose), sys-svc-container.Setup Swapfile
triggers a nested load_app.yml(svc-opt-swapfile) BEFORE
sys-svc-compose/04_files.yml renders coturn's env.j2. The nested call
set_fact'd `_load_app_id='svc-opt-swapfile'` and left it as a host
fact. When env.j2 lazy-evaluated `COTURN_REALM -> domain ->
lookup('domain', application_id)`, the outer include_role's vars block
resolved `application_id` through `{{ _load_app_id }}` and read the
mutated value, yielding
lookup('domain'): failed to resolve domain for 'svc-opt-swapfile':
application_id 'svc-opt-swapfile' not found in domains mapping
(job 77879571693 on run 26451828677, debian, play 3)
The fix is two-step:
1. Switch the include_role to reference `load_app_id` directly.
`load_app_id` is the caller's `include_tasks vars:` argument,
attached to the TaskInclude object as task-scoped vars (Level 414
include params in vars/manager.py). It walks the `_parent` chain
via `task.get_include_params()`, and `_ACTION_ALL_INCLUDES`
(constants.py:101) covers both include_tasks and include_role, so
the outer TaskInclude's vars stay reachable from any depth. Each
nested include_tasks frame has its own TaskInclude.vars and cannot
mutate the outer frame's `load_app_id`.
2. Remove the `set_fact: _load_app_id` step entirely. The host fact
was the only mutation source for this variable in load_app.yml,
and dropping it eliminates the bug class for the whole loader.
`grep` across the repo confirms `_load_app_id` is unused elsewhere
and no role does `set_fact: application_id|service_role|service_item
|load_app_id`, so the include-params resolution chain stays immune
to host-fact races.
The 7 current callers of load_app.yml are verified safe:
- 6 pass literal strings (sys-svc-webserver-core, web-app-bluesky,
sys-stk-front-base, web-opt-rdr-www, sys-svc-container,
web-app-dashboard)
- 1 (sys-service-loader/load_service.yml) passes
`"{{ service_role }}"`, which resolves via outer-loop-scoped
`service_item.role` and is itself only ever set via include-tasks
vars or loop_control, never via set_fact.
Reuses the same defense-in-depth direction as commits f32266d53
(tls lookup defensive templating) and 7b9cb5617
(current_play_redirect_domains defensive templating): close the path
where a mutable side channel feeds into a load-bearing lookup.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cross-role tasks
This folder contains the cross role tasks