make autoformat reflowed a resource_filter call, a generator expression and five test files it touched along the way. The new architecture test also carried the two findings the first gate run stopped on: three RUF012, where a TestCase class attribute needs ClassVar, and two PERF401, where an append loop becomes list.extend over a generator.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collapsing the three GPU call sites into one lookup moved the whole decision behind lookup('gpu', ...), but both consumers' tests still stubbed lookup with a constant. One test per file went red; three more kept passing for the wrong reason, because the stub's {} is falsy and every negative case was therefore true no matter what the template did.
The stubs now branch on the lookup name and record what they were asked, so each file guards what it still owns: the answer reaches the compose key or the role variable, and the lookup is asked for this service. The declared-flag-times-device matrix stays with the lookup's own tests, which is also where the boolean() over bool() rationale now lives, checked rather than written beside the call it protects.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Whether a service gets a GPU needs two facts to agree: the declaration in
applications under services.<service>.gpu, and sys-svc-container's stat of
/dev/nvidia0 on the target host. The conjunction was restated at each call
site, and web-svc-libretranslate restated only the first half: it appended the
-cuda image suffix from the declaration alone. That tag has no arm64 manifest,
so the build died with `no match for platform in manifest`, which 10 of the 79
failing jobs of run 37252453141 carry, surfacing through
sys-svc-compose/handlers/{swarm,compose}.yml.
lookup('gpu', application_id[, service]) is now the single place that answers
it. Three call sites collapse onto it: both libretranslate roles' *_GPU var
and the runtime line of sys-svc-container's resource template. A lookup runs
on the controller and cannot stat the target, so it reads the register out of
the play's variables; the applications view comes through lookup_loader
because test_no_cache_imports_in_lookups forbids a lookup the direct
utils.cache import.
The coercion is ansible's boolean(), not python's bool(). The call sites this
replaces read the flag through Jinja's `| bool`, which answers False for the
strings "false", "False", "0" and "no" where bool() answers True, and a
templated declaration renders to such a string. Measured over 13 values the
two disagree on four of them; boolean(strict=False) matches `| bool` on all
13. Two tests pin it, and with bool() restored exactly the "false" string test
fails.
Measured rather than argued: over the merged applications, 1688
(application, service) pairs resolve the predicate identically under both
coercions, 0 disagreements. The predicate turns on for 21 services, not 5,
because resource_filter falls back to services.<entity_name>.gpu and every
service of a role whose entity declares gpu inherits it.
The assert_gpu include in both roles dropped its two trailing conditions: they
re-checked the register that *_GPU now already contains.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>