`roles/user/meta/users.yml` reserved two names against the identity directory by
indexing the primary domain from the left: `sld` took label 0 and `tld` took
label 1. That is only the SLD and the TLD when the domain has exactly two
labels. On `label-a.tld.test` it reserved `label-a` and `tld` and let the real
TLD through, and on any deeper domain it drifts further.
The count is a runtime fact, so a static YAML file cannot express it. The two
entries are gone and `utils/cache/users` now emits one `domain_label_<n>` per
label:
infinito.nexus -> infinito, nexus (unchanged)
main.infinito.test -> main, infinito, test (was: main, infinito)
label-a.tld.test -> label-a, tld, test (was: label-a, tld)
Nothing read the `sld` or `tld` keys: a grep over roles, plugins, utils and cli
finds only the generator itself. The entries exist to carry `accounts: []` into
`reserved_usernames`, which `web-app-keycloak/tasks/02_init.yml:6` joins into the
username deny pattern, so the shape of the keys is free and only the set of
usernames matters.
Worth stating so the next reader does not overestimate it: the pattern at
`UserProfileProvider.json.j2:31` allows `[a-z0-9]+`, so a label carrying a hyphen
could never have been registered anyway. What this actually wins is the third
label. What it mainly wins is that the rule no longer has to be revisited when
the domain depth changes.
The `KEYCLOAK_DOMAIN` and `domain` fallbacks go with it. Both ran through a
helper that recovered the primary domain by keeping the last two labels of a
host like `auth.<DOMAIN_PRIMARY>`, which cannot work without already knowing the
depth it is trying to find. `DOMAIN_PRIMARY` and `SYSTEM_EMAIL_DOMAIN` remain,
and every deploy path supplies one of them. The test that pinned the removed
fallback is inverted rather than deleted, so the removal stays visible.
The rest is the refactor pass the change pulls in. The function no longer
materialises aliases and is renamed to `_materialize_domain_label_reservations`;
its candidate tuple carried one extractor after the fallbacks left and is a plain
key loop now; an `or [primary_domain]` guard that would have reserved a nonsense
username for an all-dots domain is dropped. `roles/user/vars/main.yml` loses four
comments that restated the line below them, and the README documents the
reservation registry, which no longer shows its full content on disk.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RFC 6761 reserves .test for testing and .example for documentation, and the
project's own domain was doing both jobs under the documentation name. It is
what default.env hands every local deploy and what the test fixtures assert
against, so it is a testing name.
Rename it wherever it stands for the running machine: the environment
defaults, the development inventory, the workspace and trust scripts, the test
suites, and the four documents that describe real behaviour - the DNS mapping
to loopback, the browser CA trust, WSL2 resolution. Those documents would have
started lying the moment default.env changed.
The sandbox allowlist moves with them, since egress is matched per pattern and
nothing resolves the old name any more, and the sandbox document that mirrors
that list follows it.
Two places keep .example on purpose: the link-style sample in the
documentation guide, which is documentation about documentation, and every
generic fixture name elsewhere in the suite - a.example, foo.example and their
kin. Those are a larger sweep of their own and are not touched here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every script a role ships now lives under `files/<language>/`, and the unit
suite is split the same way: `tests/unit/python/` beside new sibling trees for
the other languages. `tests/lint/filesystem/test_role_files_layout.py` keeps it
that way; directories whose shape is dictated from outside carry a `.nocheck`
naming the constraint (Docker build contexts, WordPress mu-plugins, the Joomla
extension, Odoo addons, Playwright suites, the `files/test/test.sh` harness
that `test-e2e-cli` resolves generically).
This cannot be applied in halves. Moving a file and repointing what names it
are the same change: 14 references resolve only at deploy time, so the tree is
green either before or after, never between.
Those 14 were found by static resolution over every reference class, not by the
test suite -- a full `make quality-high` was green while all of them were
broken:
* 7 `src:` values built from a role variable holding a bare filename
(svc-bkp-remote-2-local, decidim, espocrm, friendica, joomla, pixelfed x2).
The variable doubles as the destination name inside the container, so the
language segment belongs at the call site, not in the variable.
* `sys-service/tasks/04_base.yml` resolved every consumer's systemd script
through `first_found(..., errors='strict')` over `files/script.{sh,py}`.
18 roles ship exactly that and have no template fallback: the play would
have died on each.
* `sys-front-inj-javascript` fell back to `<role>/files/javascript.js`; the
interface now reads `files/javascript/javascript.js`.
* `sys-front-inj-logout/templates/head_sub.j2` built the cache-busting mtime
path through `path_absolute(... 'files', ...)`.
* 4 `ansible.builtin.script:` invocations naming a bare command
(pull.py, prune.sh, set_postgres_superuser_password.sh, ganesha-serve.sh).
* `dev-python/files/shell/install.sh` pointed at a sibling role's script.
Depth-dependent paths inside the moved trees were bumped accordingly: 59
`parents[N]` computations, 12 relative climbs, and the per-role recover CLI,
whose path `cli/administration/recover/recoverers.py` still assembled from the
pre-move location.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>