Three orthogonal fixes that together turn the wp-discourse round-trip
test from a 10-min hang into a 1.1-min green run:
1. Body fill: keyboard.press("Tab") from the title block lands on
the WP block-editor command-palette trigger, so the subsequent
keyboard.type(...) ends up in the palette's search field instead
of the post body. Use titleBox.press("Enter") — the natural
Gutenberg flow that splits a new paragraph block below, focused.
2. wp-discourse Gutenberg layout: the plugin (v2.6.x) registers a
PluginSidebar (toolbar icon, separate sidebar), NOT a
PluginDocumentSettingPanel inside the post sidebar. Click the
"Discourse" toolbar toggle first, then look for the publish
checkbox. The checkbox itself is `<input
className="wpdc-publish-topic-checkbox">` with no aria-label,
no name, no id — match by className.
3. finally-cleanup time budget: Playwright counts the finally block
toward the test timeout. The trash-link click's
waitForLoadState("domcontentloaded") hangs on some
WP/Gutenberg combos because move-to-trash is an XHR, not a
navigation. Cap the WP cleanup at 60s with Promise.race + add
per-op timeouts; the assertions all complete in <2 min so the
60s cap leaves comfortable margin.
Also bump test.setTimeout to 600s for this single test — two browser
contexts + two OIDC logins + WP editor + Discourse polling +
post-status cleanup across 5 statuses needs more than the default
300s budget on cold caches; individual step timeouts (60s OIDC, 30s
editor expects, 60s snackbar, 60s Discourse poll) keep regressions
fast-failing.
Validated locally: reuse-kept VARIANT=0 wordpress, 7/7 tests pass in
1.1 min, role re-deploy failed=0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First-time visits to the Gutenberg post editor show a "Welcome to the
editor" guide modal that hides the title textbox from the
accessibility tree. Scope the close to the dialog (its button only
carries aria-label="Close", which also matches unrelated toolbar
buttons via /close/i) with an Escape fallback.
Replace the brittle empty-block aria-label cascade for the body field
("Empty block" / "Type / to choose a block" / "Block: Paragraph" / …
churn across WP versions) with a single keyboard.press("Tab") from
the title — same path a real user takes, robust against future
aria-label renames.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
req-011 — descriptive role-level metadata
* Move logo / homepage / video / display out of
meta/main.yml.galaxy_info into a new project-owned
roles/<role>/meta/info.yml (file-root convention, no `info:` wrapper).
* 78 role meta/main.yml files migrated via ruamel round-trip
(key-order / quoting / inline-comments preserved). Roles with none
of the four fields are left untouched (no empty info.yml created).
* web-app-dashboard/lookup_plugins/docker_cards.py reads logo +
display from meta/info.yml; description / galaxy_tags continue to
come from galaxy_info (Galaxy-spec fields).
* docker_cards.py now routes ALL its file reads through the
process-wide caches: load_yaml_any for vars/main.yml,
meta/main.yml, meta/info.yml; read_text from utils.cache.files for
README.md. The orphan `import yaml` is gone.
New utils/cache/files (renamed from tests/utils/fs)
* The process-wide filesystem cache (read_text, iter_project_files,
iter_project_files_with_content, _all_project_files) is not
test-specific; it now lives under utils/cache/files.py next to the
other process-wide caches. 34 consumer imports rewritten across
tests/ and docs/. The empty tests/utils/ package is removed.
* New unit suite tests/unit/utils/cache/test_files.py — 12 cases
covering read_text, iter_project_files (skip-dirs, extensions,
exclude_tests, exclude_dirs, walk-cache invariants), the combined
iterator, non-UTF-8 silent skip, and _reset.
utils/cache redundancy cleanup
* applications.py + users.py + cli/create/inventory/services_disabler.py
drop their direct yaml.safe_load calls and their private
_load_yaml_mapping/_load_yaml_variant_list copies; everything routes
through utils.cache.yaml.load_yaml / load_yaml_any. The dead
utils.cache.base._load_yaml_mapping is removed.
* users.py:_load_store_users (tokens.yml read) likewise migrated.
* _load_yaml_variant_list moves out of base.py to applications.py
(its only consumer) and is renamed _load_variants_overrides — also
routes through the cached loader now.
* utils.cache.yaml._reset_cache_for_tests is renamed _reset (kept as
alias) for parity with the per-domain reset helpers.
* utils.cache.files grows a _reset() that clears its two LRU caches.
* utils.cache.__init__._reset_cache_for_tests now orchestrates ALL
six caches (applications, users, domains, base, yaml, files) so
tests get clean state across cache layers — needed because
applications/users now share the yaml cache and would otherwise
serve stale parses across reset boundaries.
* utils.cache.files imports PROJECT_ROOT from base instead of
redefining it.
* tests/unit/utils/cache/test_data.py + test_base.py: obsolete
TestLoadYamlMapping / TestLoadYamlVariantList classes deleted
(covered by test_yaml.py + test_variants.py).
* README.md updated for the new module list (files, yaml) and the
six-module orchestrator.
Galaxy schema lint
* tests/lint/repository/test_role_meta_main_galaxy_schema.py: split
galaxy_info field requirements into hard-required {author,
description, license} and soft-recommended {min_ansible_version,
platforms}. Soft-recommended -> emit a GitHub Actions ::warning::
annotation via utils.annotations.message.warning (titled
"meta/main.yml galaxy_info" with file= pointing at the offender),
no test failure. Rationale: min_ansible_version + platforms are
only enforced by Galaxy when *publishing*, which Infinito.Nexus
does not do.
* Galaxy lint goes green (was 226 offenders before req-011 +
license_url/repository/documentation strip; was 62 before this
relaxation; now 0 hard offenders, 62 soft warnings).
* roles/svc-opt-keyboard-color/meta/main.yml: backfill `license`
(only role missing the hard-required field).
New lint
* tests/lint/repository/test_role_meta_info.py — enforces meta/info.yml
file-root convention, the four-field allowlist (logo, homepage,
video, display), and shape (logo.class string, homepage/video
non-empty strings, display bool). Also fails if any
meta/main.yml.galaxy_info still carries one of the four migrated
fields.
Docs
* docs/contributing/design/services/layout.md: new section
"Descriptive Role-Level Metadata — meta/info.yml" with allowed-
fields table, optionality rule, materialised path, and consumer
note. File-Layout table grows the meta/info.yml row;
meta/main.yml row gets the explicit "no logo/homepage/video/display"
note. req-011 added to header and Related Pages.
* utils/cache/README.md: files.py + yaml.py rows added; six-module
reset orchestrator documented.
Validation
* Unit: 1573 passed.
* Lint: 55 passed.
* Integration: 161 passed (1 skipped) via pre-commit hook.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
These three sub-keys lived under `galaxy_info:` purely as a workaround
because Galaxy silently ignores unknown keys (same pattern as the
`run_after` / `lifecycle` migration in req-010). The new lint
`tests/lint/repository/test_role_meta_main_galaxy_schema.py` rejects
them, and no consumer in `plugins/`, `utils/`, `cli/`, `filter_plugins/`
or `roles/` reads any of them, so the strip is safe.
Round-trip via ruamel.yaml so the remaining `galaxy_info:` block keeps
its key order, quoting style and inline comments.
224 files touched. Remaining lint offenders after this commit are still
present (`logo`, `homepage`, `video`, plus a handful of roles missing
required Galaxy fields like `min_ansible_version` / `platforms` /
`license`); those are a separate decision and not folded into this
change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single atomic landing for three back-to-back role-meta refactor requirements
(see docs/requirements/008-role-meta-layout.md, 009-per-role-networks-and-ports.md,
010-role-meta-runafter-lifecycle-migration.md).
req-008 — role meta layout
* Move every role's metadata under meta/<topic>.yml with file-root
convention (no wrapping key matching the basename):
- schema/main.yml + config/main.yml.credentials → meta/schema.yml
- users/main.yml → meta/users.yml
- config/main.yml.server → meta/server.yml
- config/main.yml.rbac → meta/rbac.yml
- config/main.yml.compose.services + every other top-level key
of config/main.yml inlined under <entity>.<key>
→ meta/services.yml
- config/main.yml.compose.volumes → meta/volumes.yml
* Drop the legacy schema/, users/, config/ directories from every role.
* meta/schema.yml: support nested credential keys, optional `default:`
field (Jinja literal, not rendered/validated/regenerated), implicit
`algorithm: plain`.
* Materialised paths drop the `compose.` prefix:
compose.services.<e>.<…> → services.<e>.<…>
compose.volumes.<k> → volumes.<k>
<former-top-level>.<…> → services.<entity_name>.<former-top-level>.<…>
* Update every Python consumer (utils/cache, utils/manager, utils/applications,
plugins/lookup, plugins/filter, cli/*) plus every Jinja template, vars/*.yml
and task file to the new paths.
* No legacy fallback. Repository-wide grep for `compose.services.` /
`compose.volumes.` / `schema/main.yml` / `users/main.yml` / `config/main.yml`
is empty outside docs/requirements/ and tasks/utils/migrate_meta_layout.py.
* Add tests/lint/repository/test_role_meta_layout.py guarding against the
old paths returning, and an explicit lint guard that meta/variants.yml
files do not carry a `compose:` wrapper.
req-009 — per-role networks/ports
* Move group_vars/all/08_networks.yml.defaults_networks.local.<role>.{subnet,
dns_resolver} into roles/<role>/meta/server.yml.networks.local.{subnet,
dns_resolver}.
* Delete group_vars/all/09_ports.yml entirely; ports now live on the
service entity that exposes them at
roles/<role>/meta/services.yml.<entity>.ports.{inter,local,public}.
* Rename + int-cast every former top-level `services.<entity>.port`
field to `services.<entity>.ports.inter` (single int).
* Always-category-keyed maps for ports.local / ports.public; relay
spans use {start, end} integers.
* Add PORT_BANDS map to group_vars/all/08_networks.yml — single source
of truth for the per-<scope>.<category> band ranges, consumed by both
the new CLI helpers and the lint test.
* New CLI helpers (cli meta ports suggest, cli meta networks suggest)
walk the live role tree gap-first then increment-fallback, with
deterministic output and non-zero exit on capacity overflow.
* cli create role consumes both helpers when scaffolding a new role.
* Lint extensions: bare-int port shape rejection, host-bound port
collision check (single ints + relay span integers, inter excluded),
band-membership check with documented allow-list for the legacy
web-app-bigbluebutton.bigbluebutton.local.http: 48087 entry.
req-010 — run_after / lifecycle migration
* Move galaxy_info.run_after and galaxy_info.lifecycle out of
meta/main.yml into meta/services.yml.<primary_entity>.{run_after,
lifecycle}; <primary_entity> is the value returned by
get_entity_name(role_name).
* For multi-entity roles whose primary entity has no compose service
(web-app-bluesky → bluesky, web-app-matrix → matrix, desk/dev/drv/
gen roles), create a dedicated metadata-only top-level entry under
<primary_entity>.
* Introduce utils/roles/meta_lookup.py with get_role_run_after /
get_role_lifecycle that resolve <primary_entity> once and read from
meta/services.yml; degrade gracefully when meta/services.yml is
absent OR the field is absent; raise MetaServicesShapeError when the
file exists but is malformed.
* Update every consumer (utils/service_registry, utils/invokable,
cli/meta/applications/resolution/run_after/__main__, cli/meta/roles/
lifecycle_filter/__main__, cli/meta/applications/resolution/combined,
cli/build/{tree,graph,role_include}, cli/deploy/development/{common,
init,deps,deploy}, plugins/filter/canonical_domains_map, etc.) to
use the helper.
* Lint guards: no run_after/lifecycle in meta/main.yml; lifecycle
values restricted to {planned,pre-alpha,alpha,beta,stable,deprecated};
run_after/lifecycle never on a non-primary entity; empty run_after
lists rejected.
Also folded into this change set
* services_disabler now writes `services.<svc>.{enabled,shared}` into
host_vars instead of the old `compose.services.<svc>.{...}` path; the
discover/find pair walks meta/services.yml and only treats entries
carrying enabled/shared/provides as provider declarations (so
metadata-only entries from req-010 do not create spurious provider
rows).
* service_registry skips `run_after` deps whose target was filtered
out via SERVICES_DISABLED instead of failing the whole load.
* cli/mirror/resolver and cli/create/inventory/mirror_overrides emit/
consume the new applications.<role>.services.<svc>.image shape
(mirror.md updated to match).
* utils/domains/list.build_applications_from_roles now reads
meta/server.yml directly (it had been silently returning {} since
config/main.yml stopped existing).
* utils/roles/resource_validation.filter_roles_by_min_storage reads
meta/services.yml.<entity>.min_storage instead of the legacy
compose.services path.
* web-app-bluesky pre-clones the social-app build context BEFORE
sys-svc-compose so the build context is on disk by the time
sys-stk-front-proxy/tasks/main.yml flushes the docker build handler.
* 4 meta/variants.yml files (svc-ai-ollama, web-app-phpmyadmin,
web-app-wordpress, web-app-keycloak) lose their `compose: services:`
wrapper so deep-merge over meta/services.yml lands on the new
file-root shape.
Validation
* Unit tests: 1571 passed.
* Lint tests: 50 passed.
* Integration tests: 161 passed (1 skipped).
* Full-cycle fresh-purged-apps deploy succeeded for the 8-app validation
set: web-app-{nextcloud,wordpress,yourls,bigbluebutton,bluesky,gitea,
discourse,mattermost}.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Multisite variant deploy (variant=1 in meta/variants.yml) failed at
06_multisite.yml's `wp config set` loop, then the next iter failed even
earlier at 02_install.yml's `wp core install`. Two distinct shapes of
the same root cause — Ansible's `command:` module pipes the rendered
argv string through shlex, which strips the outer single-quotes from
`'blog.infinito.example'` before exec — meaning wp-cli received a
bareword and, because the loop used `--raw`, wrote it verbatim:
define( 'DOMAIN_CURRENT_SITE', blog.infinito.example );
define( 'PATH_CURRENT_SITE', / );
…which PHP can't parse. From that moment on, every subsequent `wp` CLI
invocation died at parse-time before it could do anything. The state
also persists across reuse-kept iterations because it lives inside the
WordPress docker volume — `wp config delete` couldn't clean it up
either, since it loads wp-config through PHP itself.
Fix:
- Split the Multisite constant-write loop in 06_multisite.yml into two:
one with `--raw` for true PHP literals (booleans, ints — MULTISITE,
SUBDOMAIN_INSTALL, SITE_ID_CURRENT_SITE, BLOG_ID_CURRENT_SITE) and a
second one WITHOUT `--raw` for string constants (DOMAIN_CURRENT_SITE,
PATH_CURRENT_SITE). Without `--raw`, wp-cli detects the type and
quotes the value itself — which is the right SPOT for that decision.
- Add a defensive `sed -i` at the very top of 01_patch_config.yml that
removes any pre-existing
`define( 'DOMAIN_CURRENT_SITE'|'PATH_CURRENT_SITE', ... );` line
before any `wp` CLI call runs. Pure textual substitution survives an
unparseable wp-config.php, so this self-heals carryover state from
pre-fix runs. The canonical, properly quoted versions are then
rewritten by 06_multisite.yml's string-write loop on the same play.
Empirical proof: iter 5 (`make deploy-reuse-kept-apps APPS=
web-app-wordpress VARIANT=1`) finished `ok=1630 changed=158 failed=0`,
wp-config.php now contains all seven Multisite constants in valid PHP,
and the Playwright suite reports 3 passed / 4 correctly skipped (the
Multisite sentinel passes; the Single-Site RBAC scenarios skip via
`test.skip(multisiteEnabled)` as intended).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces per-role `meta/variants.yml` for declaring multi-variant
deploys (e.g. WordPress Single-Site vs. Multisite) and reshapes the
development deploy CLI to materialise one inventory folder per variant
and iterate them with co-located sync+async passes.
Why:
- The previous single-inventory + Pass-1-then-Pass-2 split deployed each
variant twice and ran the async pass against a host left in a
different variant's state, masking real bugs.
- Inventory and runtime concerns were tangled: variant selection lived
as a runtime extra-var and required cross-stage coordination.
How:
- `roles/<role>/meta/variants.yml` (file format docs at
docs/contributing/artefact/files/role/variants.md): YAML list of
deep-merge overrides on top of `config/main.yml`. Empty entry `{}` is
variant 0 (= legacy payload). 6 roles get their dev overrides moved
here from inventories/dev.yml; WordPress declares 2 variants.
- `utils/runtime_data.py` gains `_build_variants` / `get_variants` and
drops the retired `_active_variants` runtime selector. Loader always
returns variant 0 as the default; non-zero variants reach
`get_merged_applications` as inventory-level `applications.<app>`
overrides baked at init time.
- New `cli/deploy/development/inventory.py`: `DevInventorySpec`,
`plan_dev_inventory_matrix` (pure planner shared between init and
deploy), `build_dev_inventory_matrix` (materialises one folder per
round at `<dir>-<R>`, or `<dir>` when single-variant). Single-folder
paths preserve full backward compat.
- `cli/deploy/development/deploy.py`: matrix loop with per-round
cleanup (only apps whose variant changed AND that are about to
re-deploy), `--variant <idx>` (or `VARIANT=` env) to pin a single
round, and `--full-cycle` (or `FULL_CYCLE=true`) which interleaves
sync + async per round so the async re-deploy targets exactly the
host state the matching sync deploy just produced.
- `cli/deploy/development/exec.py` gains repeatable `--env KEY=VAL` so
bash callers can inject per-run context into in-container helpers
without inlining heredocs.
- `scripts/meta/env/inventory.sh` exports `INVENTORY_VARS_FILE`
(Python mirror in `cli.deploy.development.common.DEV_INVENTORY_VARS_FILE`;
test_inventory_vars_file_spot.py locks them against drift).
- `make deploy-fresh-purged-apps` and `scripts/tests/deploy/ci/dedicated.sh`
switch to a single init + single deploy `--full-cycle`. The
`make deploy-reuse-*` bash scripts honour `VARIANT=` by suffixing
`INVENTORY_DIR`/`INVENTORY_FILE`.
- The 5 `scripts/tests/deploy/local/deploy/*.sh` scripts now delegate
their in-container heredocs to dedicated helpers in
`scripts/tests/deploy/local/utils/`, removing the bash-in-bash
quoting hell.
Tests:
- `tests/unit/utils/test_variants.py` (renamed from
`test_application_variants.py`): loader edge cases, variant-zero
default, retired-`_active_variants` guard.
- `tests/unit/cli/deploy/development/test_inventory.py` (new): planner,
variant baking, builder, `filter_plan_to_variant`, `DevInventorySpec`.
- `tests/unit/cli/deploy/development/test_matrix_deploy.py` (new):
per-round deploy filter, cleanup decisions, `--variant` pinning,
`--full-cycle` per-round interleave.
- `tests/unit/cli/deploy/development/test_inventory_vars_file_spot.py`
(new): drift guard for the bash <-> Python literal pair.
- `tests/unit/cli/deploy/development/test_exec.py` extended for `--env`.
Docs:
- `docs/contributing/artefact/files/role/variants.md` (new): file format.
- `docs/contributing/design/matrix-deploy.md` (new): runtime mechanism
(folder layout, --variant, --full-cycle, cleanup logic).
- `docs/contributing/actions/deploy.md` (new): contributor dev-deploy
flow (make targets, --variant pinning).
- `docs/administration/deploy.md` slimmed to admin-only (was mixed
admin+dev), cross-links to the new contributor doc.
- `docs/agents/action/iteration/role.md` keeps only agent-iteration
recipes; mechanism explanation moved out and linked.
- `docs/contributing/documentation.md` adds the "no self-SPOT
declaration" rule; `applications.md` renamed its SoT header for
consistency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove every em-dash thought break (4 in 004, 24 in 006, 2 in
the WordPress role README) per the "no em-dashes" rule in
`docs/contributing/documentation.md`.
- Replace "is forbidden" with the RFC 2119 keyword "MUST NOT" in
005 and 006.
- Disambiguate the ambiguous `[rbac.md](...)` links in the role
README, the administration SPOT, and the design SPOT by giving
each link a descriptive label ("Administration RBAC" /
"RBAC design"), per the link-text rules in `documentation.md`.
- Move the emoji decorations on `roles/web-app-wordpress/tasks/
plugins/README.md` headings from before to after the heading
text and turn the U+2192 arrow into prose so the document
matches the heading-emoji rule.
- Update `docs/contributing/design/iam/rbac.md` so the LDAP
layout, Keycloak mapper, and `path_join` paragraphs reflect
the flat-CN + per-app mapper implementation that requirement
005 actually ships, instead of the hierarchical-OU vision the
page was originally drafted against.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements requirements 005, 006, and 007.
005: per-application Keycloak `group-ldap-mapper`s anchor each app's
role groups under `/roles/<application_id>/<role_name>` (and, for
tenant-aware apps, `/roles/<application_id>/<tenant>/<role_name>`).
The flat-CN LDAP layout keeps each CN globally unique so Keycloak's
group-tree resolver never collides; per-app mappers use the LDAP
`description` as the visible Keycloak group name to keep the path
free of redundant prefixes. Migration purges legacy pre-005 entries
and forces a Keycloak `logout-all` so stale tokens drop pre-005
group paths.
006: per-service `<SERVICE>_SERVICE_ENABLED` env flags and the
`service-gating.js` helper let role specs `skipUnlessServiceEnabled`
cleanly when a shared service is missing or `SERVICES_DISABLED` is
set; `MODE_CI` markers (`INFINITO_MAKE_DEPLOY`, `GITHUB_ACTIONS`,
`ACT`) propagate through the deploy CLI into the test stage.
007: WordPress->Discourse round-trip Playwright scenario gated on the
`web-app-discourse` deployment.
WordPress Playwright runs 5/5 green on the single-site flow; the
multisite + Discourse scenarios are correctly skipped when their
service prerequisites are absent. The `rbac_group_path` lookup is the
single sanctioned producer for OIDC group paths and is wired into
every consumer (yourls, lam, minio, openwebui, pgadmin, phpldapadmin,
phpmyadmin, prometheus, xwiki).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document the WordPress must-use plugins convention, the criteria for
adding code here (security/integration contract, must-always-load), and
the deploy path via 05_mu_plugins.yml, so future contributors understand
why this directory exists and what belongs in it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Auto-provision one OpenLDAP group per role declared under each application's
`rbac.roles` in `config/main.yml`, gated on `group_names` so only apps
deployed on the host contribute. Keycloak syncs the groups from LDAP, emits
the `groups` claim via the shared RBAC client scope, and WordPress maps that
claim to a local role in a mu-plugin (auto-loaded, cannot be deactivated
from the admin UI). No direct LDAP bind from WordPress.
Non-obvious design choice: the `groups` client scope MUST be a *default*
client scope, not optional. When optional + `include.in.token.scope=false`,
Keycloak 26 drops the group-membership mapper from userinfo even when the
scope is explicitly requested, so the mu-plugin receives no `groups` and
falls back to `subscriber` for every user. ID/access tokens still carry
the claim, but WP consumes userinfo.
Playwright verifies the three-role cycle (subscriber/editor/administrator)
serially and idempotently per requirement 004: the add step runs via the
Keycloak admin UI (detects "already a member" via a disabled picker
checkbox and skips); teardown runs via the Keycloak Admin REST API. Only
memberships the test itself added are removed, so any pre-existing
membership is preserved. Each hop (super-admin, biber, WP-admin) runs in
its own browser context so WP session cookies and Keycloak SSO cookies
cannot leak.
Resolves: docs/requirements/004-generic-rbac-ldap-auto-provisioning.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rename the third flag returned by lookup('service', ...) from 'needed'
to 'required' across code, tests and design docs. Same semantics: True
when some deployed app has the service with both enabled: true AND
shared: true, directly or transitively through its own enabled service
dependencies.
The new name reads as a contract ("this service is required by a real
consumer") rather than a suggestion ("something needs it"), which
matches the stricter-than-enabled semantic the flag already carries.
Touchpoints:
- plugins/lookup/service.py: _is_service_needed → _is_service_required,
any_needed var, dict key in _compute_flags, docstring rationale.
- roles/sys-utils-service-loader/tasks/main.yml: caller switched to
.required.
- roles/web-app-wordpress/config/main.yml + tasks/plugins/wp-discourse.yml:
wp-discourse plugin gate uses lookup('service', 'discourse').required;
compose.services.discourse declared as shared-service dependency so
the service loader auto-loads web-app-discourse when inventory sets
enabled: true. The legacy include_tasks: utils/load_app.yml in
plugins/wp-discourse.yml is removed — the service loader now handles
it.
- tests/unit/plugins/lookup/test_service.py: 25 assertions and 6 test
method names renamed (test_needed_* → test_required_*,
test_transitive_needed_resolved,
test_direct_provider_is_not_needed_without_shared_flag).
- tests/integration/services/test_transitive_dependencies.py: one
assertion renamed.
- docs/contributing/design/services/base.md: Lookup-Plugins section
replaces the 'needed' bullet with an expanded 'required' entry that
documents when consumers should gate on it and why plain enabled is
not enough.
Also moves wordpress-app-specific keys (title, max_upload_size,
plugins) under compose.services.wordpress to match the convention
the other role keys already follow, and updates vars/main.yml lookup
paths accordingly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move title, max_upload_size and the plugins block from the top of
config/main.yml into compose.services.wordpress, matching the convention
the other service-specific keys (version, image, name, cpus, …) already
follow for this role.
- title value now references service_provider.platform.titel (the
scalar) instead of the platform dict, so the rendered Blog title
reads e.g. "Blog by Infinito.Nexus" rather than a dict repr.
- vars/main.yml lookups updated to the new paths using the existing
'compose.services.' ~ entity_name ~ '.X' pattern that the rest of
this role already uses.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces inline `applications`/`users` merging with dedicated lookup
plugins and runtime hydration. Introduces `add_csp_hash` filter,
integrates sort into `find_dock_val_by_bkp_entr`, splits the Keycloak
one-shot realm import into `05a_realm_import_run.yml`, and fixes the
Mastodon administrator task YAML. Removes obsolete `csp_hashes` filter,
`sys-token-store` Jinja filter, and empty CLI setup modules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve conflicts:
- docs/agents/action/iteration.md: keep both new bullets (SERVICES_DISABLED
proposal + make test requirement)
- docs/contributing/design/services.md: deleted (renamed to
docs/contributing/code/services.md in this branch)
- group_vars/all/20_services.yml: keep deletion from this branch (req-001)
- tests/lint/ansible/roles/test_service_core_first_task_run_once.py:
use build_service_registry_from_roles_dir (req-001) with repo_root()
from main for robust path resolution
Fix broken relative links caused by rename and deletion of files:
- docs/contributing/design/architecture.md: services.md → ../code/services.md
- docs/contributing/design/exceptions.md: load_frontend_service.yml →
load_service.yml
- docs/requirements/001-service-registry-refactoring.md: fix paths for
unit.md, integration.md, principles.md, documentation.md, AGENTS.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete group_vars/all/20_services.yml; all service metadata now lives
in each role's config/main.yml under compose.services.<name>
- Rename generic 'database' service key to explicit 'mariadb'/'postgres'
across all app roles; remove legacy type: field
- Add utils/service_registry.py and plugins/lookup/service_registry.py
as the single source of truth for service discovery
- Add utils/database_service.py to centralize DB service resolution
- Delete service_load_enabled filter and service_should_load lookup;
replaced by service lookup plugin
- Update sys-utils-service-loader to run in constructor stage and use
generic load_service.yml; remove load_frontend_service.yml
- Add _service_loader_preloading guard to sys-svc-compose,
sys-svc-webserver-core and sys-ctl-alm-compose to prevent
health-checks/alarms from firing during preload phase
- Replace manual load_app.yml calls for shared DB/Mailu with assert-
based preload validation in sys-svc-rdbms and sys-svc-mail
- Update networks.yml.j2 templates to resolve LDAP/AI networks via
service_registry instead of hardcoded role names
- Migrate all tests to new service discovery API; delete obsolete tests
- Update docs/contributing/code/services.md to reflect role-local model
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Apply documentation.md rules across staged files (emojis after headlines, RFC 2119 keywords, correct link text, Credits format)
- Remove expired ChatGPT share URLs from 39+ role READMEs and templates
- Remove orphaned text after link deletion: bare # @see, {# @see #}, Discussion: #}, empty Chat Conversation sections, dangling "Conversation is here available.", "Refer to the conversation with ChatGPT ()", "See ChatGPT Conversation", "ChatGPT Session Transcript" references
- Remove empty ## Further Resources and ## Credits sections where no links remain
- Fix URL reachability test: treat 401/403/405 as ok, 429/500/503 as warn, Timeout as warn; add # nocheck: url suppression (inline and line-above)
- Add lint test for broken relative and repo-root-absolute markdown links
- Add # nocheck: url to AUR git clone URLs, CSP allowlist entries, and other non-browser URLs
- Restore docs.infinito.nexus/setup in documentation.md MUST NOT example
- Fix Mobilizon geospatial endpoints (Addok, Photon) reverted to original values with # nocheck: url
- Add integration test rule: MUST NOT write unit tests for integration test logic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add docs/contributing/code/role/readme.md defining the required section
order, formatting rules, and the explicit prohibition of emojis in role
README headings. Reference the new guide from documentation.md.
Apply the standard to the four roles touched in the previous commit:
remove emojis from all headings, fix duplicate paragraph in WordPress,
correct typo in OpenProject compose template (crom/cental → from/central),
and align Credits sections to the canonical format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace templated Dockerfiles with plain Dockerfiles for web-app-littlejs,
web-app-openproject, web-app-roulette-wheel, and web-app-wordpress.
Variables are passed as Docker ARG without defaults, keeping config/main.yml
and vars/main.yml as the single source of truth. Build args are declared
in compose.yml.j2 directly after the build.yml.j2 lookup. Image and version
for roulette-wheel and littlejs are added to config/main.yml and exposed
via vars/main.yml.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add shared: true to logout, oauth2, and greenlight services wherever
enabled: true was set without a shared declaration
- Add # noqa: shared comment before role-local internal containers
(redis, worker, web, opensearch, search) that are intentionally
not shared
- Add missing enabled: false to ollama in svc-ai-ollama (shared was
declared without enabled, violating the new consistency rule)
- Add integration test test_service_shared_consistency to enforce:
Rule 1: enabled=true requires shared=true or a # noqa: shared comment
Rule 2: shared key requires enabled key to be present
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ensures web-svc-cdn is loaded whenever any app has css or javascript
injection enabled, by registering css and javascript as service keys in
20_services.yml (both mapping to web-svc-cdn). Adds shared: true to all
css/javascript compose service entries across app and svc role configs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* replace compose.services.desktop with compose.services.dashboard across all roles
* rename role web-app-desktop to web-app-dashboard
* rename sys-front-inj-desktop to sys-front-inj-dashboard
* update resolver mapping to include web-app-dashboard when dashboard is enabled
* adjust CSP filters to reference dashboard for frame-ancestors handling
* update lookup plugins, filter plugins, templates, vars and run_once flags
* migrate all tests to dashboard naming and service key
* update role dependencies and port mappings
* remove legacy desktop role and variables
BREAKING CHANGE:
The service key compose.services.desktop has been renamed to compose.services.dashboard.
All custom configs, inventories, or overrides must be updated accordingly.
https://chatgpt.com/share/698da7ad-1978-800f-82b4-4755b17be026
- Rename lookup plugin from `docker` to `container`
- Switch all lookups from `lookup('docker', ...)` to `lookup('container', ...)`
- Normalize compose file keys:
- docker_compose → compose
- docker_compose_override → compose_override
- docker_compose_ca_override → compose_ca_override
- Centralize compose path resolution via docker_paths_utils
- Update compose_file_args to always derive paths from DIR_COMPOSITIONS
- Align all roles, templates, and vars with container/compose naming
- Update and rename unit tests accordingly
This prepares the codebase for engine-agnostic container backends
(e.g. Docker ↔ Podman) and removes legacy docker-specific assumptions.
https://chatgpt.com/share/6987a7cc-d374-800f-99ad-bb4c8f92c59e
Replace direct `docker` command usage, references, and documentation with
the `container` wrapper to ensure engine abstraction and future portability
(e.g. Docker → Podman).
This includes:
- Shell scripts, Ansible tasks, handlers, and Python helpers
- Documentation, comments, help texts, and meta descriptions
- Network, volume, system, and image operations
- Test expectations and comments
All runtime behavior remains unchanged while enforcing the repository-wide
rule to avoid raw Docker invocations.
https://chatgpt.com/share/698721c3-e270-800f-be7a-e4435fb54506
- Rename all docker-compose.yml templates to compose.yml
- Update override and CA override filenames accordingly
- Adjust compose wrapper, lookup plugins, filters and path helpers
- Update roles, tasks, handlers and scripts to use compose CLI
- Rewrite documentation and admin guides to reference compose
- Update unit and integration tests to expect compose.yml
- Keep internal variable keys for backward compatibility where needed
https://chatgpt.com/share/69870272-b168-800f-84c3-6417398e441f
Replace all `docker compose build` invocations with the `compose` wrapper
to ensure automatic env-file detection and override handling. Update
handlers, notify targets, direct commands, and log labels consistently
across roles.
https://chatgpt.com/share/69866b76-ce6c-800f-ac9e-dba6bba27008
Replace usages of the get_app_conf filter with the config lookup plugin
across roles and templates. Adjust quoting where required to keep YAML
and Jinja syntax valid. No functional changes intended.
https://chatgpt.com/share/69860678-cef8-800f-8402-7fe6231fdc2d
The TLS lookup has been renamed from `tls_resolve` to `tls` to better
reflect its responsibility as a strict TLS state resolver.
This refactor updates all usages across the codebase, including:
- lookup plugins and internal helpers
- roles, vars, tasks, and templates
- tests, stubs, and error messages
- documentation and changelog references
Behavior, lookup API, and semantics remain unchanged.
https://chatgpt.com/share/6985e635-5a94-800f-b794-937e566ee308
Migrate all lookup() usages from the deprecated want= keyword argument to the
new strict positional want-path API. Update call order where required
(e.g. nginx_paths) and ensure consistency across vars, tasks, and templates.
https://chatgpt.com/share/6985d642-b40c-800f-9cdf-ed54fc484a92
Renamed docker-compose and docker-compose-ca roles to sys-svc-compose and sys-svc-compose-ca, updated template includes, handlers, lookups, tests, and variables, and moved compose instances to /opt/compose.
https://chatgpt.com/share/6985ab2c-8344-800f-9e9a-768f562d138d
- Replace lookup('docker', ..., want='...') with lookup('docker', ..., 'path')
- Allow both top-level keys (directories/files) and dotted paths
- Simplify docker lookup API and reduce lazy dict propagation
- Update unit tests to reflect new lookup semantics
https://chatgpt.com/share/6984ccea-8290-800f-a6d7-ec0e94df95a0
Remove implicit docker_compose variable usage and resolve all docker paths
(files, directories, env) via lookup('docker', application_id).
This prevents scope leakage, centralizes path resolution, and makes docker
context deterministic across roles and handlers.
https://chatgpt.com/share/6984ccea-8290-800f-a6d7-ec0e94df95a0
Replace attribute-style access (lookup('database', app).volume/.host/etc.)
with explicit want= lookups across docker-compose and matrix templates
to align with updated database lookup plugin interface.
https://chatgpt.com/share/6983ddfd-aee4-800f-bce9-18eca2144f25