The docs container builds every documented version of the repository: latest, each release tag and the deployed working tree. The generators ship in the image, while the utils package they import comes from the checkout of whichever version is being built, because builder.py:96 puts that checkout first on PYTHONPATH. The try/except around the entity_name import is what spans the rename of get_entity_name: the try covers the branch snapshot, the except every published ref.
Merge de3127683f resolved both sides of that rename to the new name, so the except branch re-ran the statement that had just raised and could never succeed. latest, v15.0.0 and v14.2.0 all died with a doubled ImportError traceback, / redirects to /latest/, and server.py answers 500 for a failed version, which is the 5xx the shared guest persona reports in every job that deploys web-app-docs.
An AST scan of all ten generator modules lists six symbols they import from the documented ref; against main and the five buildable tags, entity_name was the only one missing. Tags v11.6.0 and older lack whole modules (utils/roles/order.py, utils/roles/entity/name.py) and cannot build regardless; that is older than this merge and is reported separately.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Run 37553816857 failed 59 jobs, and web-app-docs is in five of every six
that named a role. The spec waits 88 minutes for a German site that never
arrives:
tests/test-languages.js:25:7 › the deployed working tree is published in de
Expected the de site of the deployed working tree to be built
Expected: 200 Received: 202
The container log of any job that deployed the role carries the whole chain:
"GET /deployed/\1/ HTTP/1.1" 202
build latest failed: Command '[... 'infinito_docs.generators.integrations' ...]'
returned non-zero exit status 1.
"GET /deployed/de/ HTTP/1.1" 202 (every 31s, for 88 minutes)
Four defects, in the order they fire.
The preload asked for a path named `\1/`. The replacement reached re.sub with
its backslash still escaped, so the backreference stayed literal and no
language was ever warmed; the first build then started when a visitor polled.
Reproduced through Templar rather than argued: the old expression renders
['', '\1/'] and the new one ['de/', '']. Plain Jinja2 with Ansible's filters
renders the old one correctly, which is why the first measurement of this
cleared code that the access log had already convicted. The replacement
appends with '$' and carries no backreference to escape.
`latest` then failed to build at all. builder.py puts the documented checkout
first on PYTHONPATH, so the image's generator meets the utils of the version
it is documenting, and a05071fe78 dropped the get_ prefix from entity_name.
A builder that documents every version has to tolerate every version's API,
so the import falls back.
A failed build kept answering 202 and re-queueing itself, so every client
waited its whole timeout and a doomed build restarted on each poll. This is
what turned a one-minute failure into 88-minute waits and drove one job into
the six-hour ceiling, where it was killed with 137. The server now answers
500, through a queue accessor that reuses the comparison `request` already
makes: a failure at an older ref still retries. For a translation the parent
version counts too, because here /deployed/de/ polled while `latest` was the
build that had failed.
The failure line now carries the captured log tail. The detail was never
lost -- _run merges stderr into the build state -- but it was absent from the
container log, which is what a rescue bundle holds, so the traceback had to be
reproduced locally to be read at all.
Nothing here is deployed. The 500 is a live change for three consumers that
accept 202 -- the preload task, test-front-page and test-on-demand-build --
but it is reachable only when a version is unservable AND its build failed at
the ref it would build now, so a healthy deployment sees what it saw before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The entity name keys a role's instance directory, its compose project and its container name, and was derived only by stripping the longest matching category prefix. Two roles under different categories whose names share a suffix therefore collapse onto one entity and overwrite each other's rendered stack, which no provides: can repair.
A literal entity_name in vars/main.yml now wins over the derivation. web-app-seaweedfs-console takes its shorter name back and declares the entity seaweedfs-console, which frees the seaweedfs key in its own services map for the engine consumer and removes the provides:, covers: and alias entries that stood in for it. A new lint pairs the declaration with the primary key it has to match, because a drift between them would drop the role from the provider set in silence.
Compared across all 288 roles, exactly one entity changes, and it restores what main already keeps on disk, so no instance directory moves.
The function and the filter lose their get_ prefix. Two lints read that prefix as the discriminator between the variable and the filter that fills it; both discriminate on the pipe now. Ansible's templar resolves a filter and a same-named variable independently, verified against ansible-core 2.20.2.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cli/build/docs/integration_matrix was 1189 lines a human ran through
'make integration-matrix', and its output sat frozen as a 125-line grid in
requirement 027. Its ENTITIES and EDGES lists were hand-maintained, so the
grid could only drift: it covered 84 entities while the tree holds 98 roles,
named two roles that no longer exist, and omitted sixteen current ones -
api, checkmk, docs, hermes, homeassistant, jellyfin, meta, mirror, openbao,
openclaw, penpot, pihole, s1, seaweedfs, seaweedfs-console and semaphore.
infinito_docs/generators/integrations.py replaces it and joins the nine
sibling generators in that package, so the overview, services and addons
pages are written into generated/integrations/ on every docs build rather
than when someone remembers the target. index.rst takes the overview and the
new docs/architecture/integrations.rst references the other two.
Requirement 027 becomes prose. The make target and its i8inma alias are
removed as a pair, which keeps the alias lints satisfied.
Coverage is not strictly larger. The removed EDGES list carried a curated
upstream project URL per integration, 96 of them, and the new edges have no
such field; only 48 of 159 addon declarations hold update.upstream_id or
config.repository, so about a third is re-derivable and the rest existed only
in that list. Its fourth field is no real loss - 57 entries read 'check', so
it was a near-constant rather than an axis. In exchange the old module had
zero tests, not one file under tests/ referenced it, and the generator ships
310 lines including generate().
Residuals: the HTML build runs without -W, but i18n.py:57 runs the gettext
builder with -W and check=True, and that build has never seen
integrations.rst - no catalog mentions generated/integrations. And
docs/requirements/036:72 still carries a checked criterion asserting a table
in 027 that no longer exists.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>