docs: fix repo-wide drift against documentation.md style rules

Three classes of drift cleaned up across 85 .md files:

- Em-dashes: replaced ` — ` with the contextually correct separator
  (colon for `term: explanation`, period to split clauses across
  sentences/source lines, parentheses for asides, semicolon for
  tight-linked continuations). Two intentional exceptions remain
  in `docs/contributing/documentation.md` (the rule literal) and
  inside a fenced ```sh block in `docs/contributing/actions/debugging/ci.md`.
- SPOT self-labels: rewrote "This is the SPOT for X" to describe
  the subject directly ("This directory holds X" / "This file
  describes X"). Cross-references to other artefacts' canonical
  role remain unchanged.
- Heading-emoji position: moved leading-emoji headings (`# 🧩 Title`)
  to trailing position (`# Title 🧩`) per the headline rule that
  emojis MUST come after the title text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 4.7 committed 2026-05-15 20:12:07 +02:00
1 parent 98457f98fd
commit 16bdbf03d7
85 files changed
+383 -383

No files matched your search

+1 -1
View File
@@ -51,7 +51,7 @@ Agents MUST write all transient files (downloaded logs, intermediate output, scr
## Container-Owned Filesystem Entries 🐳
Files produced by the containerized runner (e.g. `__pycache__/*.pyc` under `tests/`, build artefacts) are often owned by `nobody` or another in-container UID and cannot be removed from the host. When a host-level `rm`/`chmod`/edit fails with `Permission denied` on such paths, agents MUST run the cleanup via `make exec` (see [compose.yml](compose.yml) — the repo is mounted at `/opt/src/infinito`) and MUST NOT ask the operator which path to take.
Files produced by the containerized runner (e.g. `__pycache__/*.pyc` under `tests/`, build artefacts) are often owned by `nobody` or another in-container UID and cannot be removed from the host. When a host-level `rm`/`chmod`/edit fails with `Permission denied` on such paths, agents MUST run the cleanup via `make exec` (see [compose.yml](compose.yml); the repo is mounted at `/opt/src/infinito`) and MUST NOT ask the operator which path to take.
## Commit-Time Context Compaction 📦
+1 -1
View File
@@ -4,7 +4,7 @@ In order to foster a welcoming, open, and respectful community for everyone, we
## Our Pledge
We are committed to creating a friendly, safe, and inclusive environment for all members of our community—regardless of age, race, gender, sexual orientation, disability, religion, or any other status. We pledge to treat everyone with respect and courtesy.
We are committed to creating a friendly, safe, and inclusive environment for all members of our community, regardless of age, race, gender, sexual orientation, disability, religion, or any other status. We pledge to treat everyone with respect and courtesy.
## Expected Behavior
+1 -1
View File
@@ -48,4 +48,4 @@ This page collects the places to go when you need help, a professional setup, or
---
For communication guidelines — including which topics belong in public channels and which must stay internal — see [communication.md](docs/contributing/organisation/communication.md).
For communication guidelines (including which topics belong in public channels and which must stay internal), see [communication.md](docs/contributing/organisation/communication.md).
+2 -2
View File
@@ -18,6 +18,6 @@ applications without hitting an external resolver.
## Files 📄
- `Corefile.tmpl` — `envsubst` template processed at stack start.
- `Corefile` — rendered output, bind-mounted read-only into the
- `Corefile.tmpl`: `envsubst` template processed at stack start.
- `Corefile`: rendered output, bind-mounted read-only into the
`coredns` container at `/Corefile`.
+2 -2
View File
@@ -3,9 +3,9 @@
- You MUST run `make autoformat` before every commit. If `make autoformat` modifies any files that were already staged, you MUST re-stage those files before running the commit.
- You MUST commit only after all required checks pass.
- You MUST NOT commit automatically without explicit confirmation from the user. You MUST ask before committing.
- `make test` is run automatically by pre-commit before every commit when staged changes include at least one file that is not `.md`, `.rst`, or `.txt`. You do NOT need to run it manually — the pre-commit hook enforces it.
- `make test` is run automatically by pre-commit before every commit when staged changes include at least one file that is not `.md`, `.rst`, or `.txt`. You do NOT need to run it manually. The pre-commit hook enforces it.
- If the pre-commit hook fails, fix the underlying issue and retry the commit.
- You MUST NOT use `--no-verify` unless the user gives a direct, unambiguous instruction to bypass the hook (e.g. "skip the hook", "bypass pre-commit").
- Generic commit requests — such as "commit the changes", "commit", or any equivalent in any language — MUST NOT qualify. When in doubt, you MUST NOT use `--no-verify`.
- Generic commit requests (such as "commit the changes", "commit", or any equivalent in any language) MUST NOT qualify. When in doubt, you MUST NOT use `--no-verify`.
- Permission to use `--no-verify` is valid for ONE commit only. You MUST NOT carry it over to subsequent commits unless the user explicitly grants it again.
- For markdown/reStructuredText/text-only changes, pre-commit skips `make test` automatically. You MUST NOT run it manually either.
+1 -1
View File
@@ -40,7 +40,7 @@ Use this page for the fully automated triage of a GitHub Actions / CI run failur
## Playwright Failures
This section is the SPOT for analyzing Playwright failures within a CI triage. Playwright assets are downloaded as part of Step 1's `gh run download` invocation and live under `/tmp/<child-run-id>-artefacts/`.
This section describes how to analyze Playwright failures within a CI triage. Playwright assets are downloaded as part of Step 1's `gh run download` invocation and live under `/tmp/<child-run-id>-artefacts/`.
- You MUST output the storage path of the Playwright assets for the affected `<child-run-id>`.
- You MUST analyze the Playwright assets together with the matching `/tmp/<child-run-id>-job-logs.txt`.
+4 -4
View File
@@ -18,7 +18,7 @@ MUST NOT batch through a shell loop. If the operator answers *"none"*, cancel no
## 2. Push
Target `<branch>` is the branch the operator names. If none is named, the agent MUST NOT silently assume the current branch — instead, resolve the current branch via `git rev-parse --abbrev-ref HEAD`, propose it to the operator, and wait for explicit confirmation before proceeding. Only after the operator confirms (or names a different branch) is the value frozen as `<branch>`. All subsequent steps operate on the same frozen `<branch>` regardless of what is checked out later — the operator MUST be free to switch branches and keep working while the agent runs.
Target `<branch>` is the branch the operator names. If none is named, the agent MUST NOT silently assume the current branch. Instead, resolve the current branch via `git rev-parse --abbrev-ref HEAD`, propose it to the operator, and wait for explicit confirmation before proceeding. Only after the operator confirms (or names a different branch) is the value frozen as `<branch>`. All subsequent steps operate on the same frozen `<branch>` regardless of what is checked out later. The operator MUST be free to switch branches and keep working while the agent runs.
The agent MUST NOT run `git checkout`, `git switch`, or any other command that changes `HEAD`, stages files, or modifies the working tree. `git push` operates on refs by name and does not require `<branch>` to be checked out. The push goes to the local `origin` remote.
@@ -38,7 +38,7 @@ Query the open PRs for the frozen `<branch>`:
gh pr list --head <branch> --state open --json number,isDraft,url
```
Dispatch on the result (GitHub's `state=open` includes both ready and draft PRs — use `isDraft` to distinguish):
Dispatch on the result (GitHub's `state=open` includes both ready and draft PRs; use `isDraft` to distinguish):
- Empty result (no open PR): create one as draft via `gh pr create --draft` using the matching template per [pull-request.md](./pull-request.md).
- Exactly one result with `isDraft == false` (ready PR): run `gh pr ready --undo <number>` on that result's `number` to demote it to draft.
@@ -66,7 +66,7 @@ Compute scope from `git diff --name-only origin/main...<branch>`.
- `distros`: a change counts as **distro-specific** only if the diff is confined to either
- files under `packaging/<distro>/` for one or more specific distros, or
- role tasks gated by a distro conditional (e.g. `when: ansible_os_family == '...'`, `when: ansible_distribution == '...'`), touching only the branches for one or more specific distros.
Pass those distros as a space-separated list via `-f distros="<distros>"`. In every other case, omit `-f distros`; `entry-manual.yml` then applies its own default (currently `debian` only — it does NOT run all distros).
Pass those distros as a space-separated list via `-f distros="<distros>"`. In every other case, omit `-f distros`; `entry-manual.yml` then applies its own default (currently `debian` only, it does NOT run all distros).
Before triggering, capture the currently latest run id for this workflow+branch as `<prev-run-id>` (empty string if none exists). This is required so the post-trigger resolve can distinguish the new run from any stale completed run on the same branch:
@@ -88,7 +88,7 @@ gh workflow run entry-manual.yml --ref <branch> \
[-f whitelist="<role>"]
```
`gh workflow run` does NOT print the new run id. Resolve it by repeating the same list query until the returned databaseId is non-empty AND differs from `<prev-run-id>` — that is `<run-id>`. Each repeat MUST be a separate tool invocation; no shell loop. Do NOT accept a result equal to `<prev-run-id>`; that would attach the watcher to the stale run and return its old exit status immediately.
`gh workflow run` does NOT print the new run id. Resolve it by repeating the same list query until the returned databaseId is non-empty AND differs from `<prev-run-id>`. That value is `<run-id>`. Each repeat MUST be a separate tool invocation; no shell loop. Do NOT accept a result equal to `<prev-run-id>`; that would attach the watcher to the stale run and return its old exit status immediately.
Watch the new run and propagate its exit status:
+2 -2
View File
@@ -12,9 +12,9 @@ After every commit you MUST ask the user:
## Refactoring Steps
1. You MUST re-read `AGENTS.md` and follow all instructions in it before proceeding.
2. You MUST apply all rules from `AGENTS.md` and `docs/contributing/` to every affected file — not only code rules, but also documentation, naming, structure, and any other applicable guidelines.
2. You MUST apply all rules from `AGENTS.md` and `docs/contributing/` to every affected file, not only code rules, but also documentation, naming, structure, and any other applicable guidelines.
3. For every affected documentation file, you MUST optimize the text so it follows the standards defined in [documentation.md](../../contributing/documentation.md), including RFC 2119 keywords, link formatting, and writing style.
4. If the change affects an Ansible role, you MUST refactor and optimize the **entire role**, not only the files that were directly modified.
- If the change affects **more than one role**, you MUST ask the user before proceeding:
> "The following roles are affected: [list]. Which roles should be refactored — specific ones, or all?"
> "The following roles are affected: [list]. Which roles should be refactored, specific ones or all?"
- Then refactor only the roles confirmed by the user.
+2 -2
View File
@@ -1,8 +1,8 @@
# Testing
## Filesystem Scans — Use the Shared Cache
## Filesystem Scans: Use the Shared Cache
- When writing or modifying a test that scans the project tree, you MUST route the walk and file reads through [`utils.cache.files`](../../../utils/cache/files.py) — specifically `iter_project_files(...)`, `iter_project_files_with_content(...)`, or `read_text(...)`. You MUST NOT introduce raw `os.walk(<project-root>)`, `Path(<project-root>).rglob("*")`, or broad un-scoped globs on the repo tree in test code.
- When writing or modifying a test that scans the project tree, you MUST route the walk and file reads through [`utils.cache.files`](../../../utils/cache/files.py), specifically `iter_project_files(...)`, `iter_project_files_with_content(...)`, or `read_text(...)`. You MUST NOT introduce raw `os.walk(<project-root>)`, `Path(<project-root>).rglob("*")`, or broad un-scoped globs on the repo tree in test code.
- Reason: pytest runs every test in one process. Without a shared cache each test re-walks the same tree and re-reads the same files; the walk cost multiplies by the number of tests that use it. Migrating a single test from a raw walk to `iter_project_files_with_content` dropped it from ~26 s to ~1.3 s because every subsequent test served the walk and the file contents from the process-level cache.
- When to skip the rule: narrow globs that hit a fixed small set of paths (e.g. `roles_dir.glob("*/meta/services.yml")`) do not benefit meaningfully; walks inside an isolated `tempfile.TemporaryDirectory()` fixture are out of scope because the cache is read-only by design. Even in those cases, use `utils.cache.files.read_text` for individual file contents so other tests reading the same path get a cache hit.
- API reference: `iter_project_files(extensions=…, exclude_tests=…, exclude_dirs=…)` yields absolute paths, the walk is cached once per process. `iter_project_files_with_content(…)` yields `(path, content)` with both layers cached. `read_text(path)` returns cached UTF-8 content. The walker automatically prunes `.git`, `.cache`, `.pytest_cache`, `.ruff_cache`, `.venv`, `venv`, `__pycache__`, and `node_modules`.
+1 -1
View File
@@ -30,7 +30,7 @@ More information [here](https://hub.infinito.nexus/t/infinito-nexus-ci-cd-debugg
## Reproducing Runner Resource Limits Locally 💾
Some CI failures — most notably OOM-kills such as [#162](https://github.com/infinito-nexus/core/issues/162) (PeerTube plugin install, `rc=137`) — only surface under the memory and CPU ceiling of the GitHub-hosted runner and cannot be reproduced on a workstation with plenty of RAM. To reproduce these failures locally, you MUST cap the resources of the top-level `infinito` service in [compose.yml](../../../../compose.yml) to values that match (or undercut) the runner specs below.
Some CI failures (most notably OOM-kills such as [#162](https://github.com/infinito-nexus/core/issues/162), PeerTube plugin install with `rc=137`) only surface under the memory and CPU ceiling of the GitHub-hosted runner and cannot be reproduced on a workstation with plenty of RAM. To reproduce these failures locally, you MUST cap the resources of the top-level `infinito` service in [compose.yml](../../../../compose.yml) to values that match (or undercut) the runner specs below.
### GitHub-Hosted Runner Specs
+1 -1
View File
@@ -14,7 +14,7 @@ The development deploy CLI uses a **folder-per-round model** that splits cleanly
2. **Per-round variant selection.** In round R every primary app uses variant index `R if R < its_variant_count else 0`. So a single-variant app stays on variant 0 in every round, and a 3-variant app in a 5-round deploy follows `0, 1, 2, 0, 0`.
3. **Inventory folders.** [init.py](../../../cli/administration/deploy/development/init.py) calls [build_dev_inventory_matrix](../../../cli/administration/deploy/development/inventory/builder.py), which creates one folder per round at `<inventory-dir>-<round>` (or just `<inventory-dir>` when there is a single round). For each folder, `build_dev_inventory` resolves the round's per-app variant payload and bakes it into the inventory's `host_vars` as `applications.<app>: <variant-payload>` overrides. The inventory is therefore variant-resolved on disk; no runtime selector is needed.
4. **Deploy iteration.** [deploy.py](../../../cli/administration/deploy/development/deploy.py) re-derives the same plan via `plan_dev_inventory_matrix` (the planner is a pure function shared between init and deploy) and runs one deploy per folder. **Every round deploys its full variant-aware include set.** Round R starts from a clean host (the previous round's include is purged before round R begins, see point 5), so every dep round R needs MUST be redeployed.
5. **Cleanup between rounds.** Before each non-first iteration the wrapper runs [entity.sh](../../../scripts/tests/deploy/local/purge/entity.sh) for **every** app in the previous round's include set. The next round therefore boots from an empty host, which is the SPOT for cross-round state coherence: nothing the previous round deployed (whether it stays in the current round's closure or falls out) can leak into round R, so variants are free to disagree on which deps they pull in. The final round is followed by no purge so the last state remains available for inspection or follow-up specs.
5. **Cleanup between rounds.** Before each non-first iteration the wrapper runs [entity.sh](../../../scripts/tests/deploy/local/purge/entity.sh) for **every** app in the previous round's include set. The next round therefore boots from an empty host, which guarantees cross-round state coherence: nothing the previous round deployed (whether it stays in the current round's closure or falls out) can leak into round R, so variants are free to disagree on which deps they pull in. The final round is followed by no purge so the last state remains available for inspection or follow-up specs.
6. **`--full-cycle` (async update pass).** When set (or `FULL_CYCLE=true` in the environment), the wrapper runs each round's deploy TWICE: first the regular sync pass, then immediately a second pass with `-e ASYNC_ENABLED=true` overriding the host_var, against the SAME variant's folder, BEFORE moving to the next round. The two passes are therefore always co-located on the same host state; the async pass never accidentally targets a host that a previous round's variant left behind.
7. **Pinning to a single round.** Both init and deploy accept `--variant <idx>` (or read the `VARIANT` environment variable) to pin operations to a single round's folder. In single-round mode no inter-round cleanup runs, since there is no previous round to diff against. The `make deploy-reuse-*` targets honour the same `VARIANT` env-var by suffixing `INVENTORY_DIR` / `INVENTORY_FILE` to `<base>-<idx>`, so a redeploy can target one specific variant without iterating the full matrix.
+2 -2
View File
@@ -2,7 +2,7 @@
This page defines which operating systems deployments, scripts, and programs MUST support.
Following this policy ensures that Infinito Nexus runs reliably across a broad range of server
environments — from community-managed long-term-support servers to enterprise
environments, from community-managed long-term-support servers to enterprise
[Red Hat](https://www.redhat.com) deployments and cutting-edge rolling-release development machines.
## Deployed Systems
@@ -26,7 +26,7 @@ offers long-term support without commercial backing requirements.
[Fedora](https://fedoraproject.org) and [CentOS](https://www.centos.org) are required to ensure
compatibility with [Red Hat Enterprise Linux (RHEL)](https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux)
environments. Red Hat support MUST be guaranteed because enterprise customers operate under SLA
and security compliance requirements that mandate a RHEL-compatible platform — including certified
and security compliance requirements that mandate a RHEL-compatible platform, including certified
package versions, SELinux enforcement, and vendor-backed security advisories.
[Arch Linux](https://archlinux.org) is included to catch integration problems with newer software
versions early in the development cycle before they reach stable distributions.
@@ -12,8 +12,8 @@ Infinito.Nexus is committed to transparency and actively encourages communicatio
Public communication MUST be conducted through the following channels:
- **Matrix** — `#public:infinito.nexus` for general community discussion.
- **Hub** — [hub.infinito.nexus](https://hub.infinito.nexus) for tutorials, FAQs, and community support.
- **Matrix**: `#public:infinito.nexus` for general community discussion.
- **Hub**: [hub.infinito.nexus](https://hub.infinito.nexus) for tutorials, FAQs, and community support.
Examples of topics suitable for public channels:
@@ -40,7 +40,7 @@ Examples of topics that MUST stay internal:
### Bugs and Issues 🐛
- Bugs and actionable issues MUST be reported at [s.infinito.nexus/issues](https://s.infinito.nexus/issues).
- Security vulnerabilities MUST NOT be reported there — see [SECURITY.md](../../../SECURITY.md) instead.
- Security vulnerabilities MUST NOT be reported there. See [SECURITY.md](../../../SECURITY.md) instead.
### Features and Work Items 🗂️
+2 -2
View File
@@ -7,5 +7,5 @@ Its scope is how repository-standard tools should be used, when they apply, and
| Helper | Purpose |
|---|---|
| [ports-suggest.md](ports-suggest.md) | `cli meta ports suggest` — propose free host-bound ports inside `PORT_BANDS.<scope>.<category>`. |
| [networks-suggest.md](networks-suggest.md) | `cli meta networks suggest` — propose free per-role subnets gap-first within established umbrella blocks. |
| [ports-suggest.md](ports-suggest.md) | `cli meta ports suggest`: propose free host-bound ports inside `PORT_BANDS.<scope>.<category>`. |
| [networks-suggest.md](networks-suggest.md) | `cli meta networks suggest`: propose free per-role subnets gap-first within established umbrella blocks. |
@@ -1,6 +1,6 @@
# 006 - Service-gated Playwright tests
## Update — MODE_CI retired
## Update: MODE_CI retired
The `MODE_CI` flag introduced by this requirement was later retired in
favour of a direct `RUNTIME` check. The Playwright include in
@@ -124,7 +124,7 @@ Therefore:
inside the runner pulls through
`http://package-cache:8081/repository/apt-…/`.
- [ ] When the `cache` profile is INACTIVE, the override is omitted
and package managers default to upstream — no failed deploys.
and package managers default to upstream (no failed deploys).
### Coexistence with `registry-cache`
@@ -2,7 +2,7 @@
## User Story
As an operator running an Infinito.Nexus stack, I want [KIX Start](https://www.kixdesk.com/) deployed as a first-class `web-app-*` role, fronted by `web-app-oauth2-proxy` so that **every** access to KIX is gated by Keycloak SSO with the realm's 2FA policy enforced — and so that helpdesk tickets share the same identity store, mail relay, dashboard, logout flow and proxy edge as every other service on the host.
As an operator running an Infinito.Nexus stack, I want [KIX Start](https://www.kixdesk.com/) deployed as a first-class `web-app-*` role, fronted by `web-app-oauth2-proxy` so that **every** access to KIX is gated by Keycloak SSO with the realm's 2FA policy enforced, and so that helpdesk tickets share the same identity store, mail relay, dashboard, logout flow and proxy edge as every other service on the host.
## Acceptance Criteria
@@ -11,8 +11,8 @@ As an operator running an Infinito.Nexus stack, I want [KIX Start](https://www.k
- [x] The role declares a per-role docker subnet under `meta/server.yml.networks.local.subnet`, picked via `cli meta networks suggest`.
- [x] KIX is reachable at `kix.{{ DOMAIN_PRIMARY }}` over TLS through `sys-stk-front-proxy` and emits HSTS.
- [x] An `web-app-oauth2-proxy` instance sits between `sys-stk-front-proxy` and the KIX backend: every request to `kix.{{ DOMAIN_PRIMARY }}` that does not carry a valid OAuth2-proxy session cookie is redirected to Keycloak. The KIX backend is NOT reachable directly from the front proxy without traversing the OAuth2 proxy.
- [x] 2FA enforcement is realm-level: the Keycloak realm that backs the OAuth2 proxy has an OTP / WebAuthn flow configured, so a user without a second factor cannot complete the OAuth2 proxy redirect chain. The realm-level configuration is the single source of truth — no 2FA logic lives in KIX or the role itself.
- [x] LDAP is wired as KIX's user-directory backend against `svc-db-openldap` (`Auth::LDAP` + `Auth::Sync::LDAP`): when a user authenticates against KIX their profile (display name, email, role group) is sourced from LDAP — no manual KIX-side user pre-creation is required.
- [x] 2FA enforcement is realm-level: the Keycloak realm that backs the OAuth2 proxy has an OTP / WebAuthn flow configured, so a user without a second factor cannot complete the OAuth2 proxy redirect chain. The realm-level configuration is the single source of truth, so no 2FA logic lives in KIX or the role itself.
- [x] LDAP is wired as KIX's user-directory backend against `svc-db-openldap` (`Auth::LDAP` + `Auth::Sync::LDAP`): when a user authenticates against KIX their profile (display name, email, role group) is sourced from LDAP, so no manual KIX-side user pre-creation is required.
- [x] The `web-app-dashboard` role surfaces a card for KIX that links to its canonical URL, with the logo / title resolved via the standard `lookup('config', 'web-app-kix', ...)` path used by every other dashboard tile.
- [x] The universal logout endpoint terminates a KIX session like any other Infinito.Nexus app: the KIX session cookie is cleared and the browser lands on the project logout page.
- [x] An end-to-end Playwright spec at `roles/web-app-kix/files/playwright.spec.js` covers the in-scope flow: TLS root + HSTS at `kix.{{ DOMAIN_PRIMARY }}`; anonymous request to the canonical URL redirects into the OAuth2-proxy / Keycloak chain (the realm-level 2FA step is exercised by the shared `web-app-keycloak` Playwright suite, not duplicated per-app).
@@ -20,7 +20,7 @@ As an operator running an Infinito.Nexus stack, I want [KIX Start](https://www.k
## Deferred (KIX 18 OSS frontend limitation)
KIX 18 OSS exposes both `Auth::HTTPBasicAuth` (which reads
`REMOTE_USER` / `HTTP_REMOTE_USER` — semantically equivalent to
`REMOTE_USER` / `HTTP_REMOTE_USER`, semantically equivalent to
OTRS' `Auth::HTTPHeaderModule`) and a fully featured
`Auth::LDAP` + `Auth::Sync::LDAP` backend, so both are wired by
this role. The KIX 18 Angular frontend SPA, however, ships its own
@@ -32,6 +32,6 @@ SPA still renders its own login form on first visit; the user
re-authenticates against the KIX login form against `Auth::LDAP`.
A truly transparent SPA-side SSO (login form skipped) requires a
small KIX frontend modification — wired to the existing
`Auth::HTTPBasicAuth` endpoint — and is tracked as a follow-on
small KIX frontend modification (wired to the existing
`Auth::HTTPBasicAuth` endpoint) and is tracked as a follow-on
iteration, not part of req-015.
+16 -16
View File
@@ -1,4 +1,4 @@
# 015 — Self-built Moodle image (replaces `bitnamilegacy/moodle`)
# 015: Self-built Moodle image (replaces `bitnamilegacy/moodle`)
## User Story
@@ -64,7 +64,7 @@ listed in [Profile-field mapping](#profile-field-mapping-keycloak-↔-ldap-↔-m
The variants differ only in the **login** path and in whether
Keycloak is part of the topology at all.
### Variant 0 — `auth_oidc` + `auth_ldap` sync-only (hybrid)
### Variant 0: `auth_oidc` + `auth_ldap` sync-only (hybrid)
- [x] `auth_oidc` is enabled and set as the **primary login method**
(`config_plugin auth_oidc`, plus `auth = 'oidc,ldap'` global
@@ -101,7 +101,7 @@ Keycloak is part of the topology at all.
- [x] On idempotent re-deploy, both `auth_oidc` and `auth_ldap`
configuration MUST report `changed=0`.
### Variant 1 — `auth_ldap` only (no OIDC)
### Variant 1: `auth_ldap` only (no OIDC)
- [x] `auth_oidc` is **disabled** (`disable_plugin auth_oidc`),
and `services.oidc.enabled` in this variant's
@@ -328,12 +328,12 @@ cron natively, so the role MUST add a dedicated sidecar.
<https://moodledev.io/general/app/development/setup/docker-images>.
- [x] The role's `Administration.md` is removed (no Bitnami paths
to retarget; the operator-facing CLI lives at `MOODLE_CLI_DIR`
which is the SPOT in
defined in
[vars/main.yml](../../roles/web-app-moodle/vars/main.yml)).
- [x] The role's `TODO.md` is removed: the Bitnami-issue link is
gone, and the only residual concern (sendmail availability for
`mail()`) is now covered by the msmtp install + healthcheck —
no follow-up is outstanding against the self-built layout.
`mail()`) is now covered by the msmtp install + healthcheck.
No follow-up is outstanding against the self-built layout.
## OIDC end-to-end coverage
@@ -361,9 +361,9 @@ The role MUST enforce the following layered contract:
in the [mapping table](#mapping-table). Every other component
in the system reads from LDAP (directly or transitively) and
never holds an authoritative copy that can drift from it.
2. **Definition of attribute correspondence (the SPOT)**: the
**Keycloak LDAP user-federation mapper** is the single source
of truth for "which LDAP attribute backs which Keycloak user-
2. **Definition of attribute correspondence**: the
**Keycloak LDAP user-federation mapper** owns the binding from
"which LDAP attribute backs which Keycloak user-
profile attribute". All other consumers MUST derive their
field-name pairing from this mapper:
- `auth_ldap` in Moodle MUST bind each Moodle column to the
@@ -375,7 +375,7 @@ The role MUST enforce the following layered contract:
The
[mapping table](#mapping-table) in this document MUST stay in
sync with the Keycloak federation mapper definition; the
table is the documented expression of the same SPOT.
table is the documented expression of the same mapping.
3. **Edit surface**: end-user edits MUST happen exclusively via
the **Keycloak Account Console** (or its admin equivalent for
admin-managed attributes). Moodle MUST NOT expose editable
@@ -398,10 +398,10 @@ that everything else MUST match.
Goal: every Moodle user-profile field that the standard
`mdl_user` table or a Moodle custom profile field can hold MUST
flow from the LDAP canonical store transparently into Moodle —
flow from the LDAP canonical store transparently into Moodle,
through Keycloak's federation mapper for the OIDC path, and
through Moodle's `auth_ldap` field-mapping for the LDAP-sync
path — both bound to the **same** LDAP attribute names.
path, both bound to the **same** LDAP attribute names.
The mapped surface MUST cover at minimum the following Moodle
fields, all of which are exposed as configurable claim mappings
@@ -512,7 +512,7 @@ standard claims (`given_name`, `family_name`, …) which already
do not use URNs. The role MUST NOT introduce a project-specific
URN prefix for these claims.
### Keycloak OIDC client mapping (Moodle as RP) — variant 0 only
### Keycloak OIDC client mapping (Moodle as RP), variant 0 only
The Keycloak OIDC client representing Moodle MUST be provisioned
exclusively through the existing
@@ -586,7 +586,7 @@ Console.
MUST configure the `auth_ldap` plugin with:
- LDAP server URL, bind DN, bind password, user contexts,
user attribute (`uid` / `sAMAccountName` / per-tenant), and
object class — all sourced from the same shared LDAP
object class, all sourced from the same shared LDAP
service definition that other apps consume, so the
connection contract is not duplicated per role.
- field-mapping for every Moodle column whose row in the
@@ -651,7 +651,7 @@ without login (both variants), and direct LDAP-bind login
`SERVICES_DISABLED`, and skip the LDAP cases cleanly when
the LDAP shared service is disabled.
#### Variant 0 — OIDC + LDAP sync (hybrid)
#### Variant 0: OIDC + LDAP sync (hybrid)
- [ ] After biber signs into Moodle via **Keycloak OIDC**, the
spec MUST navigate to biber's Moodle profile page (or
@@ -683,7 +683,7 @@ without login (both variants), and direct LDAP-bind login
altogether). Only the Account Console may edit; Moodle
MUST refuse local edits.
#### Variant 1 — LDAP-only
#### Variant 1: LDAP-only
- [x] biber MUST sign into Moodle by submitting his LDAP `uid`
and password directly to Moodle's login form. The spec
@@ -15,6 +15,6 @@ As an Infinito.Nexus admin, I want to host multiple Fediwalls under distinct pat
- [ ] An authenticated `GET /admin/walls` returns the list of currently published slugs as JSON.
- [ ] The public root `/` renders an HTML link list of all currently published walls; the list updates on the next page load after a successful upload or delete (no admin login required to view).
- [ ] Wall configs and slug directories survive container restart and `make deploy-reuse-kept-apps APPS=web-app-fediwall`; an existing wall is reachable byte-identical after a redeploy.
- [ ] CSP `connect-src` for every `/<slug>/` continues to be governed by [`lookup('fediwall_active', 'url_bases')`](../../plugins/lookup/fediwall_active.py) — the multi-wall feature MUST NOT bypass the existing whitelist.
- [ ] CSP `connect-src` for every `/<slug>/` continues to be governed by [`lookup('fediwall_active', 'url_bases')`](../../plugins/lookup/fediwall_active.py); the multi-wall feature MUST NOT bypass the existing whitelist.
- [ ] End-to-end Playwright in [`roles/web-app-fediwall/files/playwright.spec.js`](../../roles/web-app-fediwall/files/playwright.spec.js) covers: anonymous read of an existing `/<slug>/`, anonymous `401` on `/admin/walls`, authenticated round-trip (upload → read → delete → `404`), slug-collision rejection (`409`), and slug-validation rejection (`400`).
- [ ] This requirement file is cross-linked from the implementing PR; the PR description references this file.
+15 -15
View File
@@ -3,8 +3,8 @@
## User Story
As an operator, I want a `web-app-hugo` role that builds and serves
[Hugo](https://gohugo.io/) static sites — one per canonical domain
configured for the role — so that I can host fast, dependency-free
[Hugo](https://gohugo.io/) static sites (one per canonical domain
configured for the role) so that I can host fast, dependency-free
HTML/CSS/JS websites (blogs, marketing pages, documentation portals)
on infinito-nexus with the same lifecycle, deploy, and observability
guarantees as every other `web-app-*` role.
@@ -30,7 +30,7 @@ build step:
the `builder` stage runs `hugo --minify` against the cloned source;
the `serve` stage is a pinned `nginx:<version>-alpine` that COPYs
the builder's `/public/` into `/usr/share/nginx/html`. There is no
separate long-running build container — `compose build` re-bakes
separate long-running build container; `compose build` re-bakes
the static output whenever the cloned source changes, and `compose
up` restarts nginx with the new image.
@@ -51,7 +51,7 @@ hash is unchanged.
## Scope
V1 (this requirement) implements **single canonical domain** per
deploy — the most common shape for a Hugo site, matching every
deploy, the most common shape for a Hugo site, matching every
existing simple `web-app-*` role in the tree. Multi-canonical-domain
support (one role deploy serving N independent Hugo sites with
distinct `--baseURL`s) is **out of scope** for V1; if needed, a
@@ -65,14 +65,14 @@ fail explicitly if more than one canonical domain is configured.
[requirement 008](008-role-meta-layout.md).
- Reuses the standard compose includes
(`sys-svc-compose/templates/base.yml.j2`,
`sys-svc-container/templates/...`) — see
`sys-svc-container/templates/...`); see
[roles/web-app-yourls/templates/compose.yml.j2](../../roles/web-app-yourls/templates/compose.yml.j2)
as the structural reference.
- Pulls Hugo from upstream via the `package-cache` profile when active
(requirement [012](012-package-cache-nexus3-oss.md)). When the
profile is inactive, the role MUST still deploy by going to upstream
directly.
- Does NOT require a database, OIDC, or LDAP — the served output is
- Does NOT require a database, OIDC, or LDAP; the served output is
fully static.
## Acceptance Criteria
@@ -100,8 +100,8 @@ fail explicitly if more than one canonical domain is configured.
- [x] The serving image is pinned (`nginx:1.28.0-alpine`) in
`meta/services.yml`, surfaced through `vars/main.yml`.
- [x] Both image references go through
`lookup('config', application_id, 'services.hugo.{image,version,builder_image,builder_version}')`
— same path used by every other `web-app-*` role — so they
`lookup('config', application_id, 'services.hugo.{image,version,builder_image,builder_version}')`,
the same path used by every other `web-app-*` role, so they
appear in the global image-version drift report.
### Configuration surface
@@ -113,25 +113,25 @@ fail explicitly if more than one canonical domain is configured.
- [x] Inventory configuration under
`applications.web-app-hugo.services.hugo`:
- `source_repository` (HTTPS Git URL, OPTIONAL; default
`https://github.com/gohugoio/hugoDocs.git` — see **Default
`https://github.com/gohugoio/hugoDocs.git`; see **Default
site** below)
- `source_version` (branch, tag, or commit; OPTIONAL; default
value is the pinned tag from **Default site**)
- `hugo_environment` (OPTIONAL; default `production`)
- `hugo_minify` (OPTIONAL boolean; default `true`)
- Hugo `--baseURL` is derived from
`lookup('tls', application_id, 'url.base')` — the role's TLS
`lookup('tls', application_id, 'url.base')`; the role's TLS
URL is single-source-of-truth for the canonical scheme+host
and an inventory override would silently fight that lookup.
- [x] Theme handling: theme is whatever the upstream content repo
ships in `themes/` or `go.mod` (Hugo modules). The role does
NOT clone a separate theme repo in V1 — keeping theme + content
NOT clone a separate theme repo in V1; keeping theme + content
bundled mirrors how `web-app-littlejs` consumes its upstream
and avoids per-site theme version drift. A separate theme
override is deferred to the multi-site follow-up.
- [x] Defaults live in `meta/services.yml` (read via
`lookup('config', application_id, …)` from `vars/main.yml`),
not in a role-level schema validator — same convention as
not in a role-level schema validator; same convention as
every other `web-app-*` role.
### Default site
@@ -172,7 +172,7 @@ fail explicitly if more than one canonical domain is configured.
`compose build` / `compose up` handler fires.
- [x] Build failures (Hugo non-zero exit) fail `compose build` and
therefore the play. Because the new image is never produced,
`compose up` keeps serving the previous image — content never
`compose up` keeps serving the previous image, so content never
flips to a half-built state. Observed during this iteration:
an npm-modules build error (`Could not resolve "alpinejs"`)
failed the play with no image swap.
@@ -187,7 +187,7 @@ fail explicitly if more than one canonical domain is configured.
(BusyBox wget; the pinned `nginx:1.28.0-alpine` ships no curl).
Container reports `Up (healthy)` after first deploy.
- [x] CSP (`content-security-policy: default-src 'self'; …`),
`server: openresty/…` (no version leak from the role's nginx —
`server: openresty/…` (no version leak from the role's nginx,
reverse-proxy strips server header), and HSTS
(`strict-transport-security: max-age=15768000`) are emitted by
the front proxy. Validated by `curl -sk -I https://hugo.infinito.example/`.
@@ -227,7 +227,7 @@ fail explicitly if more than one canonical domain is configured.
- Authoring UI (Hugo has none; content lives in Git).
- Server-side comments, search-as-a-service, or any dynamic backend.
- OIDC / LDAP integration — the served content is public-by-design.
- OIDC / LDAP integration; the served content is public-by-design.
A future requirement MAY add an oauth2-proxy wrapper for staging
sites; this requirement does NOT.
- Multi-tenant theme marketplace. Themes are bundled with the
@@ -23,7 +23,7 @@ absent).
A static-analysis pass (added in
[tests/lint/ansible/roles/web-app/playwright/test_env_keys_used.py](../../tests/lint/ansible/roles/web-app/playwright/test_env_keys_used.py))
surfaced multiple roles that **declared** the `BIBER_*` env keys but
never read them in the spec — the env file was prepared, the spec was
never read them in the spec; the env file was prepared, the spec was
never written. The most prominent example is `web-app-joomla`: it
ships an OIDC variant (`plg_system_keycloak`) and an LDAP variant
(core LDAP auth plugin) per
@@ -56,7 +56,7 @@ rather than role by role.
- [ ] The biber scenario MUST end on a deterministic post-login
surface (a URL, a heading, or a stable role-specific selector) that
is observable to a non-admin user. Asserting on admin-only UI from
the biber session is forbidden — the point of the scenario is to
the biber session is forbidden. The point of the scenario is to
prove the non-admin path works, not to re-test admin.
- [ ] Where the role's RBAC contract maps biber to a specific
application role (e.g. `editor`, `member`), the assertion SHOULD
@@ -95,30 +95,30 @@ commit message why the role is exempt and removing it from this list.
#### In-scope: biber scenario MUST be added
- [ ] [web-app-joomla](../../roles/web-app-joomla/) — OIDC
- [ ] [web-app-joomla](../../roles/web-app-joomla/): OIDC
(`plg_system_keycloak`) AND LDAP variants per
[006](006-playwright-service-gated-tests.md). Smoking gun for this
requirement: env keys were declared, never consumed.
- [ ] [web-app-baserow](../../roles/web-app-baserow/) — confirm OIDC/LDAP scope.
- [ ] [web-app-bookwyrm](../../roles/web-app-bookwyrm/) — has both `oidc` and `ldap` gated paths in the spec; biber should ride one of them.
- [ ] [web-app-bluesky](../../roles/web-app-bluesky/) — variant-A+ login-broker. Biber MAY require the broker's app-password handoff; document the outcome of the audit.
- [ ] [web-app-flowise](../../roles/web-app-flowise/) — OIDC via oauth2-proxy.
- [ ] [web-app-jenkins](../../roles/web-app-jenkins/) — OIDC plus LDAP variant.
- [ ] [web-app-akaunting](../../roles/web-app-akaunting/) — OIDC plus LDAP variant.
- [ ] [web-app-minio](../../roles/web-app-minio/) — OIDC plus LDAP variant.
- [ ] [web-app-taiga](../../roles/web-app-taiga/) — OIDC.
- [ ] [web-app-gitea](../../roles/web-app-gitea/) — OIDC plus optional LDAP.
- [ ] [web-app-postmarks](../../roles/web-app-postmarks/) — confirm scope.
- [ ] [web-app-baserow](../../roles/web-app-baserow/): confirm OIDC/LDAP scope.
- [ ] [web-app-bookwyrm](../../roles/web-app-bookwyrm/): has both `oidc` and `ldap` gated paths in the spec; biber should ride one of them.
- [ ] [web-app-bluesky](../../roles/web-app-bluesky/): variant-A+ login-broker. Biber MAY require the broker's app-password handoff; document the outcome of the audit.
- [ ] [web-app-flowise](../../roles/web-app-flowise/): OIDC via oauth2-proxy.
- [ ] [web-app-jenkins](../../roles/web-app-jenkins/): OIDC plus LDAP variant.
- [ ] [web-app-akaunting](../../roles/web-app-akaunting/): OIDC plus LDAP variant.
- [ ] [web-app-minio](../../roles/web-app-minio/): OIDC plus LDAP variant.
- [ ] [web-app-taiga](../../roles/web-app-taiga/): OIDC.
- [ ] [web-app-gitea](../../roles/web-app-gitea/): OIDC plus optional LDAP.
- [ ] [web-app-postmarks](../../roles/web-app-postmarks/): confirm scope.
#### Identity providers: separate scope
- [ ] [web-app-keycloak](../../roles/web-app-keycloak/) — IS the OIDC
- [ ] [web-app-keycloak](../../roles/web-app-keycloak/): IS the OIDC
provider; biber's existence in Keycloak is the prerequisite for
every other role's biber scenario. The role's spec already covers
the realm-side flow; this requirement does NOT touch it. Audit the
spec only to confirm no drift from
[004](004-generic-rbac-ldap-auto-provisioning.md).
- [ ] [web-app-fusiondirectory](../../roles/web-app-fusiondirectory/) —
- [ ] [web-app-fusiondirectory](../../roles/web-app-fusiondirectory/):
LDAP admin UI; biber as a *managed* user appears in the directory
view, but the role's own login is admin-only. Audit and document.
@@ -126,11 +126,11 @@ commit message why the role is exempt and removing it from this list.
The following roles MUST NOT carry `BIBER_*` env keys nor a biber
scenario; closing each item is a NOOP except when a stale env-key
shows up — in that case remove it.
shows up; in that case remove it.
- [ ] [web-svc-cdn](../../roles/web-svc-cdn/), [web-svc-simpleicons](../../roles/web-svc-simpleicons/), [web-svc-xmpp](../../roles/web-svc-xmpp/), [web-svc-libretranslate](../../roles/web-svc-libretranslate/) — backend services with no end-user UI.
- [ ] [web-app-hugo](../../roles/web-app-hugo/), [web-app-sphinx](../../roles/web-app-sphinx/), [web-app-bridgy-fed](../../roles/web-app-bridgy-fed/), [web-app-mig](../../roles/web-app-mig/) — static or pure-publishing content with no authenticated path.
- [ ] [web-app-dashboard](../../roles/web-app-dashboard/), [web-app-matomo](../../roles/web-app-matomo/) — own admin authentication is in scope of their own admin tests; biber-as-non-admin is not a meaningful surface here. Document and exempt.
- [ ] [web-svc-cdn](../../roles/web-svc-cdn/), [web-svc-simpleicons](../../roles/web-svc-simpleicons/), [web-svc-xmpp](../../roles/web-svc-xmpp/), [web-svc-libretranslate](../../roles/web-svc-libretranslate/): backend services with no end-user UI.
- [ ] [web-app-hugo](../../roles/web-app-hugo/), [web-app-sphinx](../../roles/web-app-sphinx/), [web-app-bridgy-fed](../../roles/web-app-bridgy-fed/), [web-app-mig](../../roles/web-app-mig/): static or pure-publishing content with no authenticated path.
- [ ] [web-app-dashboard](../../roles/web-app-dashboard/), [web-app-matomo](../../roles/web-app-matomo/): own admin authentication is in scope of their own admin tests; biber-as-non-admin is not a meaningful surface here. Document and exempt.
### Verification
@@ -57,7 +57,7 @@ consistently across the role tree is what this requirement tracks.
`skipped: LDAP_SERVICE_ENABLED=false`, never `failed`.
- [ ] A role that ships BOTH `oidc.enabled` and `ldap.enabled`
scenarios MUST keep them as **separate** test bodies. Combining the
two flags in a single scenario is forbidden — the matrix-deploy
two flags in a single scenario is forbidden. The matrix-deploy
variants exist precisely to drive each branch in isolation, and
bundling them defeats the variant matrix.
- [ ] The LDAP scenario MUST exercise BOTH the canonical admin user
@@ -94,22 +94,22 @@ reachable login UI).
#### In-scope: LDAP scenario MUST be added
- [ ] [web-app-opencloud](../../roles/web-app-opencloud/) — trigger role for this requirement; variant 1 is LDAP-only by design but the spec gates everything on `oidcEnabled`.
- [ ] [web-app-joomla](../../roles/web-app-joomla/) — Joomla's core LDAP authentication plugin is the LDAP-variant surface per [006 audit row](006-playwright-service-gated-tests.md). Pairs with the missing biber scenario from [017](017-playwright-biber-rbac-coverage.md).
- [ ] [web-app-jenkins](../../roles/web-app-jenkins/) — Jenkins LDAP security realm is a separate auth provider from the OIDC plugin.
- [ ] [web-app-bookwyrm](../../roles/web-app-bookwyrm/) — declares both `oidc` and `ldap` gates.
- [ ] [web-app-mattermost](../../roles/web-app-mattermost/) — AD/LDAP login surface independent of OIDC.
- [ ] [web-app-friendica](../../roles/web-app-friendica/) — LDAP module surface.
- [ ] [web-app-bigbluebutton](../../roles/web-app-bigbluebutton/) — confirm whether the LDAP path has a Playwright-drivable surface; document the outcome.
- [ ] [web-app-akaunting](../../roles/web-app-akaunting/) — LDAP authentication plugin.
- [ ] [web-app-discourse](../../roles/web-app-discourse/) — LDAP plugin.
- [ ] [web-app-minio](../../roles/web-app-minio/) — MinIO LDAP IDP.
- [ ] [web-app-flowise](../../roles/web-app-flowise/) — confirm scope.
- [ ] [web-app-espocrm](../../roles/web-app-espocrm/) — built-in LDAP authentication.
- [ ] [web-app-shopware](../../roles/web-app-shopware/) — confirm scope.
- [ ] [web-app-odoo](../../roles/web-app-odoo/) — `auth_ldap` module.
- [ ] [web-app-mobilizon](../../roles/web-app-mobilizon/) — confirm scope.
- [ ] [web-app-openwebui](../../roles/web-app-openwebui/) — confirm scope.
- [ ] [web-app-opencloud](../../roles/web-app-opencloud/): trigger role for this requirement; variant 1 is LDAP-only by design but the spec gates everything on `oidcEnabled`.
- [ ] [web-app-joomla](../../roles/web-app-joomla/): Joomla's core LDAP authentication plugin is the LDAP-variant surface per [006 audit row](006-playwright-service-gated-tests.md). Pairs with the missing biber scenario from [017](017-playwright-biber-rbac-coverage.md).
- [ ] [web-app-jenkins](../../roles/web-app-jenkins/): Jenkins LDAP security realm is a separate auth provider from the OIDC plugin.
- [ ] [web-app-bookwyrm](../../roles/web-app-bookwyrm/): declares both `oidc` and `ldap` gates.
- [ ] [web-app-mattermost](../../roles/web-app-mattermost/): AD/LDAP login surface independent of OIDC.
- [ ] [web-app-friendica](../../roles/web-app-friendica/): LDAP module surface.
- [ ] [web-app-bigbluebutton](../../roles/web-app-bigbluebutton/): confirm whether the LDAP path has a Playwright-drivable surface; document the outcome.
- [ ] [web-app-akaunting](../../roles/web-app-akaunting/): LDAP authentication plugin.
- [ ] [web-app-discourse](../../roles/web-app-discourse/): LDAP plugin.
- [ ] [web-app-minio](../../roles/web-app-minio/): MinIO LDAP IDP.
- [ ] [web-app-flowise](../../roles/web-app-flowise/): confirm scope.
- [ ] [web-app-espocrm](../../roles/web-app-espocrm/): built-in LDAP authentication.
- [ ] [web-app-shopware](../../roles/web-app-shopware/): confirm scope.
- [ ] [web-app-odoo](../../roles/web-app-odoo/): `auth_ldap` module.
- [ ] [web-app-mobilizon](../../roles/web-app-mobilizon/): confirm scope.
- [ ] [web-app-openwebui](../../roles/web-app-openwebui/): confirm scope.
#### Out of scope: no LDAP scenario
@@ -118,9 +118,9 @@ test-drivable LDAP login surface (consumer-side only, infrastructural,
or already covered by their own service-level test). Closing each
item is a NOOP unless a stale env key shows up.
- [ ] [web-app-keycloak](../../roles/web-app-keycloak/) — IS the OIDC provider; LDAP federation is tested via Keycloak's own user-storage spec, not via a per-role Playwright surface.
- [ ] [web-app-fusiondirectory](../../roles/web-app-fusiondirectory/) — IS the LDAP admin UI; its admin login already covers the LDAP bind path by construction.
- [ ] [svc-db-openldap](../../roles/svc-db-openldap/) — backend service with no end-user UI; covered by integration tests, not Playwright.
- [ ] [web-app-keycloak](../../roles/web-app-keycloak/): IS the OIDC provider; LDAP federation is tested via Keycloak's own user-storage spec, not via a per-role Playwright surface.
- [ ] [web-app-fusiondirectory](../../roles/web-app-fusiondirectory/): IS the LDAP admin UI; its admin login already covers the LDAP bind path by construction.
- [ ] [svc-db-openldap](../../roles/svc-db-openldap/): backend service with no end-user UI; covered by integration tests, not Playwright.
### Verification
@@ -35,11 +35,11 @@ The acceptance criteria below are the mechanical translation of this contract.
| 6 | A new env key without a spec consumer is a regression. | [test_env_keys_used.py](../../tests/lint/ansible/roles/web-app/playwright/test_env_keys_used.py) |
| 7 | Every persona scenario AND every contract test in `files/playwright.spec.js` MUST simulate a real user flow with at least one `expect(...)` / `await <fn>(...)` / equivalent assertion. Stub bodies (`TODO`, `STUB`, `FIXME`, empty, or only `skipUnlessServiceEnabled`) are FORBIDDEN. The rollout's intent is real flows that fail when the integration breaks; a passing-by-default body provides no signal. | [test_no_stub_tests.py](../../tests/lint/ansible/roles/web-app/playwright/test_no_stub_tests.py) |
| 8 | Tests MUST drive user-initiated actions through the rendered UI (click on logout button / link / menu, click on submit button, …) and MUST NOT short-circuit them with `page.goto(<action-endpoint>)`. The logout step in particular MUST click the role's own in-app logout control on the currently authenticated surface (or open a user / account menu first when the control is nested). When the universal-logout service is attached, its injected JavaScript auto-rewrites the click target to redirect through Keycloak's end-session endpoint, so the test does NOT branch on whether universal-logout is active. Navigating directly to `${LOGOUT_URL}` is forbidden. | review (this requirement) |
| 9 | Every persona scenario MUST drive a real, role-specific interaction after the auth chain settles (or directly on the role surface when no auth is required). The `biber` interaction MUST exercise a regular end-user action; the `administrator` interaction MUST exercise an admin-only surface. Specs pass the interaction in via `runBiberFlow(page, { biberInteraction })` / `runAdminFlow(page, { adminInteraction })`. No generic default exists — a generic "click any link" assertion tests nothing role-specific. | review (this requirement) |
| 9 | Every persona scenario MUST drive a real, role-specific interaction after the auth chain settles (or directly on the role surface when no auth is required). The `biber` interaction MUST exercise a regular end-user action; the `administrator` interaction MUST exercise an admin-only surface. Specs pass the interaction in via `runBiberFlow(page, { biberInteraction })` / `runAdminFlow(page, { adminInteraction })`. No generic default exists; a generic "click any link" assertion tests nothing role-specific. | review (this requirement) |
| 10 | When the role supports peer-to-peer interaction (messaging, comment threads, federation round-trips, calendar invites, …), the spec MUST include a separate `biber ↔ administrator: <flow>` test that opens two browser contexts, drives the round-trip end-to-end, and asserts both sides see the expected payload. The shared `runPeerExchangeFlow(browser, { peerExchange })` helper provides the two-context scaffolding. Roles whose upstream offers no peer interaction surface MUST NOT add the test. | review (this requirement) |
| 11 | Persona scenarios MUST FAIL LOUDLY when the persona cannot execute the contracted journey, never silently `test.skip(...)` on runtime detection of "no logout button" / "no authenticated surface" / "no admin UI marker". A silent skip hides real regressions (broken OIDC mapping, removed logout button, misconfigured oauth2-proxy, drifted UI selectors) behind a green deploy. The ONLY clean-skip mechanism is an EXPLICIT env opt-out declared by the role: `PERSONA_BIBER_BLOCKED=true` / `PERSONA_ADMINISTRATOR_BLOCKED=true` / `PERSONA_GUEST_BLOCKED=true` rendered in `templates/playwright.env.j2`, with a documented rationale in the role's README or TODO. Without the flag the persona helper hard-fails the test. | [test_strict_mode.py](../../tests/lint/ansible/roles/web-app/playwright/persona/test_strict_mode.py) (`test_persona_skips_only_via_explicit_opt_out`) + persona-helper bodies in `roles/test-e2e-playwright/files/personas/{biber,admin,guest}.js` |
| 12 | Direct-probe deny-checks at prometheus / matomo MUST validate the response body, not only the status code. A `200 OK` is acceptable ONLY when the body contains role-specific markers proving the response is the genuine provider surface (e.g. `prometheus_build_info`, `<title>Prometheus</title>` for prometheus; matomo's login-form markers / `piwik` or `matomo` for matomo). Any 200 with a non-matching body is treated as a misconfigured proxy or a denial-as-200 surface and fails loudly. | [test_strict_mode.py](../../tests/lint/ansible/roles/web-app/playwright/persona/test_strict_mode.py) (`test_deny_helpers_validate_body_on_200`) + bodies in `roles/web-app-prometheus/files/playwright.spec.js` and `roles/web-app-matomo/files/playwright.spec.js` (per Rule 13: provider-owned SPOT) |
| 13 | **SPOT-owned cross-service assertions.** Dashboard tile reachability, prometheus scrape parity, and matomo tracker presence are owned by the provider's own spec, not duplicated across consumer roles. Each provider's `templates/playwright.env.j2` renders a `<NAME>_TARGET_ROLES_JSON` manifest via the `lookup('roles_with_service', '<svc>')` Ansible lookup ([plugins/lookup/roles_with_service.py](../../plugins/lookup/roles_with_service.py)), enumerating every role whose merged applications config declares the service with both `enabled` and `shared` truthy and whose role exposes a canonical domain. The provider's `files/playwright.spec.js` parameterises one assertion per consumer over that manifest. The shared persona helpers (`runBiberFlow`, `runAdminFlow`) consequently no longer drive cross-service probes — they exercise the role under test only. | provider specs `roles/web-app-{dashboard,prometheus,matomo}/files/playwright.spec.js` and the `roles_with_service` lookup |
| 13 | **SPOT-owned cross-service assertions.** Dashboard tile reachability, prometheus scrape parity, and matomo tracker presence are owned by the provider's own spec, not duplicated across consumer roles. Each provider's `templates/playwright.env.j2` renders a `<NAME>_TARGET_ROLES_JSON` manifest via the `lookup('roles_with_service', '<svc>')` Ansible lookup ([plugins/lookup/roles_with_service.py](../../plugins/lookup/roles_with_service.py)), enumerating every role whose merged applications config declares the service with both `enabled` and `shared` truthy and whose role exposes a canonical domain. The provider's `files/playwright.spec.js` parameterises one assertion per consumer over that manifest. The shared persona helpers (`runBiberFlow`, `runAdminFlow`) consequently no longer drive cross-service probes; they exercise the role under test only. | provider specs `roles/web-app-{dashboard,prometheus,matomo}/files/playwright.spec.js` and the `roles_with_service` lookup |
| 14 | **Post-deploy log inspection.** After every deploy cycle the agent MUST inspect the Playwright logs (`list` reporter, `playwright-report/index.html`, `test-results/<test>/error-context.md`, plus the trace / video captured under `PLAYWRIGHT_KEEP_ALL=true`) and verify both (a) the intended per-persona and per-service behavior is really wired into the role's `files/playwright.spec.js`, and (b) every wired assertion was actually executed by Playwright. A green exit alone is NOT sufficient evidence: a silent `test.skip(...)`, a scenario that exits before the role-specific interaction fires, or a `<NAME>_SERVICE_ENABLED=true` gate whose body never ran all violate this rule. Gaps MUST be closed by extending the spec to cover the missing behavior; existing assertions stay per [Preserving existing tests](#preserving-existing-tests), and removal is permitted only when the deleted assertion is demonstrably faulty. | review (this requirement) + log inspection per [Playwright Spec Loop](../agents/action/iteration/playwright.md#procedure) step 4 |
## Per-service scenario catalogue
@@ -74,74 +74,74 @@ Legend (`v0` / `v1` / `v2`): ⏳ untested, ✅ passed (full Per-role flow incl.
Columns immediately after `Role`:
- **`total`** — priority signal (consumer fan-out from `meta/services.yml` cross-references); higher = more downstream impact. Data rows are sorted DESC by this column.
- **`total`**: priority signal (consumer fan-out from `meta/services.yml` cross-references); higher = more downstream impact. Data rows are sorted DESC by this column.
| Role | total | has env | has spec | v0 | v1 | v2 | notes |
| --- | ---: | --- | --- | --- | --- | --- | --- |
| ~~`web-app-prometheus`~~ | 173 | ✅ | ✅ | ✅ | ✅ | | oauth2-proxy gates the role on `web-app-prometheus-administrator`; biber lacks the role so the proxy denies the session and biber has no in-app surface to drive a logout from — opt out via `PERSONA_BIBER_BLOCKED=true` (Rule 11). The administrator persona runs the standard oauth2-proxy → Keycloak chain. Bespoke `metricz`, `dashboard-to-prometheus admin SSO`, and `biber-denied-access` tests cover the SPOT-owned probes. Logout-icon injected via `templates/javascript.js.j2` (gated on services.oauth2.enabled) because the upstream UI ships no in-app logout. Variant=1 collapses oauth2 → PERSONA_ADMINISTRATOR_BLOCKED renders true via env and bespoke tests `safeSkipUnlessEnabled("oauth2")` |
| ~~`web-app-prometheus`~~ | 173 | ✅ | ✅ | ✅ | ✅ | | oauth2-proxy gates the role on `web-app-prometheus-administrator`; biber lacks the role so the proxy denies the session and biber has no in-app surface to drive a logout from. Opt out via `PERSONA_BIBER_BLOCKED=true` (Rule 11). The administrator persona runs the standard oauth2-proxy → Keycloak chain. Bespoke `metricz`, `dashboard-to-prometheus admin SSO`, and `biber-denied-access` tests cover the SPOT-owned probes. Logout-icon injected via `templates/javascript.js.j2` (gated on services.oauth2.enabled) because the upstream UI ships no in-app logout. Variant=1 collapses oauth2 → PERSONA_ADMINISTRATOR_BLOCKED renders true via env and bespoke tests `safeSkipUnlessEnabled("oauth2")` |
| ~~`web-app-matomo`~~ | 168 | ✅ | ✅ | ✅ | ✅ | | admin-only role: persona stubs explicit-skipped via `PERSONA_BIBER_BLOCKED=true` / `PERSONA_ADMINISTRATOR_BLOCKED=true` in env (Rule 11); bespoke "matomo administrator" test covers the admin journey via matomo's own login form. The biber-deny test gates on `isServiceEnabled("oauth2")` and parks until matomo's oauth2-proxy gate is wired (TODO in `meta/services.yml`) |
| ~~`web-app-dashboard`~~ | 162 | ✅ | ✅ | ✅ | ✅ | | OIDC silent-SSO chain ships `parent.postMessage(location.href, location.origin)` in `templates/nginx/sso.html.conf.j2` so `keycloak.init()` can complete; `oidc.js` then sets `window.__oidcLoginReady` after wiring the click interceptor, and the shared persona helper waits for that signal before clicking Login (avoids the static-href fallback that would skip PKCE). Variant=1 disables every shared service — env renders `PERSONA_{BIBER,ADMINISTRATOR}_BLOCKED` from `services.oidc.enabled`, and the asset / login-swap tests skip cleanly on `cdn`/`logout`/`oidc`-disabled |
| ~~`web-svc-cdn`~~ | 144 | ✅ | ✅ | ✅ | ✅ | | infra role; bespoke "cdn index served under canonical domain with TLS" test covers the surface — persona stubs collapse to the auth-less skip (no APP_BASE_URL surface), no further fix needed |
| ~~`web-app-dashboard`~~ | 162 | ✅ | ✅ | ✅ | ✅ | | OIDC silent-SSO chain ships `parent.postMessage(location.href, location.origin)` in `templates/nginx/sso.html.conf.j2` so `keycloak.init()` can complete; `oidc.js` then sets `window.__oidcLoginReady` after wiring the click interceptor, and the shared persona helper waits for that signal before clicking Login (avoids the static-href fallback that would skip PKCE). Variant=1 disables every shared service: env renders `PERSONA_{BIBER,ADMINISTRATOR}_BLOCKED` from `services.oidc.enabled`, and the asset / login-swap tests skip cleanly on `cdn`/`logout`/`oidc`-disabled |
| ~~`web-svc-cdn`~~ | 144 | ✅ | ✅ | ✅ | ✅ | | infra role; bespoke "cdn index served under canonical domain with TLS" test covers the surface; persona stubs collapse to the auth-less skip (no APP_BASE_URL surface), no further fix needed |
| ~~`web-app-mailu`~~ | 139 | ✅ | ✅ | ✅ | ✅ | | bespoke `dashboard → mailu sso → admin → logout` and `biber → email → administrator → receives` tests own the persona coverage (they exercise mailu's iframe-wrapped auth chain directly), both `safeSkipUnlessEnabled("oidc")`-gated so the no-OIDC variant collapses cleanly; the shared persona scenarios route through the dashboard main-frame Account menu and depend on the dashboard OIDC silent-SSO that is currently in escape, so PERSONA_BIBER_BLOCKED + PERSONA_ADMINISTRATOR_BLOCKED collapse them cleanly |
| ~~`web-app-keycloak`~~ | 130 | ✅ | ✅ | ✅ | ✅ | | auth-provider exception: generic persona scenarios are exempt; bespoke "master-realm super administrator", "normal-realm administrator", "normal-realm biber" tests cover the persona contract via the realm account UI. Variant=1 disables LDAP federation; the bespoke biber test collapses via `safeSkipUnlessEnabled("ldap")` — biber MUST NEVER be seeded directly via kcadm, only the administrator persona is seeded for the headless ops loop |
| ~~`web-svc-simpleicons`~~ | 92 | ✅ | ✅ | ✅ | ✅ | | infra role; bespoke "simpleicons serves keycloak assets directly on its own domain" test owns the surface; persona stub collapses to the auth-less skip (no APP_BASE_URL). Variant=1 toggles only the `prometheus` flag — no app-surface change, bespoke surface test passes identically |
| ~~`web-app-nextcloud`~~ | 27 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25890032686 — v0 and v1 deploys clean (PLAY RECAP `failed=0`); only v2 fail'd in `tasks/plugins/user_ldap.yml:69` (Warm LDAP cache) with `Failed opening required '/var/www/html/lib/versioncheck.php'`. Source-code analysis confirms `lib/versioncheck.php` exists in `stable33` and is loaded via `__DIR__` (cwd-independent), so the missing-file error implies the entrypoint rsync into the `data:` volume did not stage the source for v2's matrix-deploy round. Same environmental-flake family as the prior C14 (CI 25774452286): v0+v1 succeeded with identical code, only v2 hit a round-isolated volume-state issue. Treated as environmental — does not reproduce locally — until a future CI run re-hits the same v2 failure. |
| ~~`web-app-bigbluebutton`~~ | 24 | ✅ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-keycloak`~~ | 130 | ✅ | ✅ | ✅ | ✅ | | auth-provider exception: generic persona scenarios are exempt; bespoke "master-realm super administrator", "normal-realm administrator", "normal-realm biber" tests cover the persona contract via the realm account UI. Variant=1 disables LDAP federation; the bespoke biber test collapses via `safeSkipUnlessEnabled("ldap")`. Biber MUST NEVER be seeded directly via kcadm, only the administrator persona is seeded for the headless ops loop |
| ~~`web-svc-simpleicons`~~ | 92 | ✅ | ✅ | ✅ | ✅ | | infra role; bespoke "simpleicons serves keycloak assets directly on its own domain" test owns the surface; persona stub collapses to the auth-less skip (no APP_BASE_URL). Variant=1 toggles only the `prometheus` flag (no app-surface change), bespoke surface test passes identically |
| ~~`web-app-nextcloud`~~ | 27 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25890032686: v0 and v1 deploys clean (PLAY RECAP `failed=0`); only v2 fail'd in `tasks/plugins/user_ldap.yml:69` (Warm LDAP cache) with `Failed opening required '/var/www/html/lib/versioncheck.php'`. Source-code analysis confirms `lib/versioncheck.php` exists in `stable33` and is loaded via `__DIR__` (cwd-independent), so the missing-file error implies the entrypoint rsync into the `data:` volume did not stage the source for v2's matrix-deploy round. Same environmental-flake family as the prior C14 (CI 25774452286): v0+v1 succeeded with identical code, only v2 hit a round-isolated volume-state issue. Treated as environmental (does not reproduce locally) until a future CI run re-hits the same v2 failure. |
| ~~`web-app-bigbluebutton`~~ | 24 | ✅ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| ~~`web-app-discourse`~~ | 24 | ✅ | ✅ | ✅ | ✅ | | Local FULL_CYCLE v0+v1 ✅. Root cause for CI **C8 pgvector**: variant 1's `postgres.shared: false` forced Discourse to use its bundled launcher postgres which lacks the `vector` extension. Fix: pinned `services.postgres.shared: true` in `meta/services.yml` (`svc-db-postgres` image has pgvector compiled via Dockerfile.j2), removed override from `meta/variants.yml`. Also gated the two OIDC-dependent tests (`administrator/biber: dashboard to discourse OIDC login and logout`) on `OIDC_SERVICE_ENABLED=true` |
| ~~`web-app-mastodon`~~ | 23 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| `web-app-friendica` | 23 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2b (dashboard tile) + per-spec (post-login surface)** mixed: four tests fail — (1) `dashboard → friendica: admin login, verify ui, logout` — `getByRole('link', { name: /Explore Friendica/i })` click times out (dashboard returned Keycloak login instead of the dashboard view, session/auth setup); (2) `friendica: biber and administrator log in side by side` — post-login `#topbar-first, #navbar-apps-menu, a[href*='/logout']` never visible (post-login home not reached); (3) `biber: dashboard → app → universal logout` — oauth2-proxy emits `403 Forbidden` (biber rejected at proxy); (4) `administrator: dashboard → prometheus → app → universal logout` — `inAppLogout` finds no logout control on Friendica's authenticated surface (still on /login form). Split between dashboard auth chain (C2b) and a post-login surface regression (LDAP autocreate / session — recent change in Friendica's post-login surface). |
| ~~`web-app-opentalk`~~ | 23 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants (`PERSONA_*_BLOCKED` env fix from commit f1898dd77 verified) |
| ~~`web-app-listmonk`~~ | 22 | ❌ | ✅ | ✅ | ✅ | | CI run 25890032686 — deploy + Playwright PASS for all declared variants (v1 verified; the prior C11 DB-upgrade race did not reproduce) |
| `web-app-gitea` | 22 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C12 compose-up handler regression**: `sys-svc-compose/handlers/main.yml:76` (`compose up`) exits non-zero again right after the gitea image pull. Same orphan-default-network signature that commit `c6affc96f` previously fixed; the recent `602c05ed3 Reverted matrix fix` rolled the purge primitive partially back, re-introducing the failure for the gitea+matrix pair. Re-apply the entity-keyed `scripts/container/purge/entity/network.sh` + global `docker network prune -f` to close. Spec env (`PROMETHEUS_BASE_URL`, `PROMETHEUS_SERVICE_ENABLED`) from commit `1e5a47f67` still in place; the gitea-specific Playwright issues (prometheus scrape contract + universal-logout round-trip) remain Deep and stay un-tested until compose-up clears. |
| ~~`web-app-openwebui`~~ | 22 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25797277810 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-flowise`~~ | 22 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| `web-app-bookwyrm` | 22 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2d (deploy regression masquerading as Playwright)**: BookWyrm container still on the first-run `Instance Configuration / Continue` wizard, so Keycloak's username field never renders. The persona helper times out (60 s) on `getByRole('textbox', { name: /username|email/i })`; snapshot heading reads `"Installing BookWyrm"` / `"Instance Configuration"`. Root cause is deploy-side — the role's post-install bootstrap never completed; not a Playwright spec bug. |
| ~~`web-app-minio`~~ | 22 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25797277810 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-xwiki`~~ | 21 | ❌ | ✅ | ✅ | ✅ | | CI run 25797277810 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-shopware`~~ | 21 | ❌ | ✅ | ✅ | ✅ | ✅ | CI run 25797277810 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-pretix`~~ | 21 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| `web-app-odoo` | 21 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C-new-odoo-ldap-insert**: `web-app-odoo/tasks/04_ldap.yml:54` (Create new LDAP configuration) fails with `Cannot execute SQL 'INSERT INTO res_company_ldap (sequence, company, ldap_server, …)'`. Regression introduced by the recent commit `762586a03 fix: matrix docker-network label clash + odoo res.company.ldap column rename` — the column rename was not reflected in the `INSERT` statement. |
| ~~`web-app-mobilizon`~~ | 21 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| `web-app-matrix` | 21 | ✅ | ✅ | ❌ | ⏳ | | CI run 25890032686 — **C12 matrix compose-up recurrence**: `web-app-matrix/tasks/01_docker.yml:76` fails with `network matrix was found but has incorrect label com.docker.compose.network set to "" (expected: "default")`. Identical to the prior C12 in CI 25774452286; the orphan-network purge primitive (commit `c6affc96f`) was partially reverted by `602c05ed3 Reverted matrix fix`. Re-applying the entity-keyed purge from `scripts/container/purge/entity/network.sh` closes this row again. Playwright DM-scenario remains gated behind the compose-up fix. |
| ~~`web-app-gitlab`~~ | 21 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-mastodon`~~ | 23 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| `web-app-friendica` | 23 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2b (dashboard tile) + per-spec (post-login surface)** mixed: four tests fail. (1) `dashboard → friendica: admin login, verify ui, logout`: `getByRole('link', { name: /Explore Friendica/i })` click times out (dashboard returned Keycloak login instead of the dashboard view, session/auth setup). (2) `friendica: biber and administrator log in side by side`: post-login `#topbar-first, #navbar-apps-menu, a[href*='/logout']` never visible (post-login home not reached). (3) `biber: dashboard → app → universal logout`: oauth2-proxy emits `403 Forbidden` (biber rejected at proxy). (4) `administrator: dashboard → prometheus → app → universal logout`: `inAppLogout` finds no logout control on Friendica's authenticated surface (still on /login form). Split between dashboard auth chain (C2b) and a post-login surface regression (LDAP autocreate / session, recent change in Friendica's post-login surface). |
| ~~`web-app-opentalk`~~ | 23 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants (`PERSONA_*_BLOCKED` env fix from commit f1898dd77 verified) |
| ~~`web-app-listmonk`~~ | 22 | ❌ | ✅ | ✅ | ✅ | | CI run 25890032686: deploy + Playwright PASS for all declared variants (v1 verified; the prior C11 DB-upgrade race did not reproduce) |
| `web-app-gitea` | 22 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686: **C12 compose-up handler regression**: `sys-svc-compose/handlers/main.yml:76` (`compose up`) exits non-zero again right after the gitea image pull. Same orphan-default-network signature that commit `c6affc96f` previously fixed; the recent `602c05ed3 Reverted matrix fix` rolled the purge primitive partially back, re-introducing the failure for the gitea+matrix pair. Re-apply the entity-keyed `scripts/container/purge/entity/network.sh` + global `docker network prune -f` to close. Spec env (`PROMETHEUS_BASE_URL`, `PROMETHEUS_SERVICE_ENABLED`) from commit `1e5a47f67` still in place; the gitea-specific Playwright issues (prometheus scrape contract + universal-logout round-trip) remain Deep and stay un-tested until compose-up clears. |
| ~~`web-app-openwebui`~~ | 22 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25797277810: deploy + Playwright PASS for all declared variants |
| ~~`web-app-flowise`~~ | 22 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| `web-app-bookwyrm` | 22 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2d (deploy regression masquerading as Playwright)**: BookWyrm container still on the first-run `Instance Configuration / Continue` wizard, so Keycloak's username field never renders. The persona helper times out (60 s) on `getByRole('textbox', { name: /username|email/i })`; snapshot heading reads `"Installing BookWyrm"` / `"Instance Configuration"`. Root cause is deploy-side: the role's post-install bootstrap never completed; not a Playwright spec bug. |
| ~~`web-app-minio`~~ | 22 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25797277810: deploy + Playwright PASS for all declared variants |
| ~~`web-app-xwiki`~~ | 21 | ❌ | ✅ | ✅ | ✅ | | CI run 25797277810: deploy + Playwright PASS for all declared variants |
| ~~`web-app-shopware`~~ | 21 | ❌ | ✅ | ✅ | ✅ | ✅ | CI run 25797277810: deploy + Playwright PASS for all declared variants |
| ~~`web-app-pretix`~~ | 21 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| `web-app-odoo` | 21 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686: **C-new-odoo-ldap-insert**: `web-app-odoo/tasks/04_ldap.yml:54` (Create new LDAP configuration) fails with `Cannot execute SQL 'INSERT INTO res_company_ldap (sequence, company, ldap_server, …)'`. Regression introduced by the recent commit `762586a03 fix: matrix docker-network label clash + odoo res.company.ldap column rename`; the column rename was not reflected in the `INSERT` statement. |
| ~~`web-app-mobilizon`~~ | 21 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| `web-app-matrix` | 21 | ✅ | ✅ | ❌ | ⏳ | | CI run 25890032686: **C12 matrix compose-up recurrence**: `web-app-matrix/tasks/01_docker.yml:76` fails with `network matrix was found but has incorrect label com.docker.compose.network set to "" (expected: "default")`. Identical to the prior C12 in CI 25774452286; the orphan-network purge primitive (commit `c6affc96f`) was partially reverted by `602c05ed3 Reverted matrix fix`. Re-applying the entity-keyed purge from `scripts/container/purge/entity/network.sh` closes this row again. Playwright DM-scenario remains gated behind the compose-up fix. |
| ~~`web-app-gitlab`~~ | 21 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| ~~`web-app-espocrm`~~ | 21 | ❌ | ✅ | ✅ | ✅ | ✅ | Local FULL_CYCLE v0+v1+v2 ✅ (failed=0 both passes for every variant). Root cause for the CI fail was a **duplicate `depends_on:` block** in `roles/web-app-espocrm/templates/compose.yml.j2` (websocket service rendered both the `dmbs_excl.yml.j2` include AND a manual `depends_on:` → `failed to parse compose.yml: yaml: mapping key "depends_on" already defined`); commits `eaa51b39d` then `1f5689e44` consolidate daemon + websocket services to use the `dmbs_incl.yml.j2` include so the DB/redis deps and the manual espocrm-service dep live under a single `depends_on:` key. |
| `web-app-taiga` | 21 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2b/C2c (dashboard tile + role login link both missing)**: `expect(getByRole('link', { name: 'Explore Taiga' })).toBeVisible()` fails across two tests (dashboard tile and themed-routes); snapshot shows the upstream `taiga.io` discover-projects marketing page with Login/Sign-up `href="#"` placeholders — the dashboard nav injection and the role's own login route are both absent. Looks deploy-side (nginx serving the marketing fallback), not a spec selector bug. |
| `web-app-mattermost` | 21 | ✅ | ✅ | ❌ | ⏳ | | CI run 25890032686 — **C2b (dashboard tile injection missing)**: `getByRole('link', { name: 'Explore Mattermost' })` times out at 300 s; the page that loaded is Mattermost's `"View in Desktop App / View in Browser"` interstitial — the dashboard navbar tile for Mattermost is not being rendered. Bespoke DM-UI selector + universal-logout Keycloak round-trip remain Deep but only after the dashboard-tile injection is restored. |
| ~~`web-app-wordpress`~~ | 21 | ✅ | ✅ | ✅ | ✅ | | CI run 25797277810 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-moodle`~~ | 21 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-joomla`~~ | 21 | ✅ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| `web-app-fider` | 21 | ✅ | ✅ | ❌ | ⏳ | | CI run 25890032686 — **C2b (selectors + dashboard tile)**: biber test fails on `getByRole('link', { name: /sign in/i }).first()` (30 s waitFor — Fider's "Sign in" link not present on the rendered page); the `dashboard → fider` test sees only the autoindex `"Index of /"` listing, so the dashboard tile for Fider was never injected. Both paths point at a deploy-side gap (Fider front-page not deployed + dashboard nav drift) rather than a per-spec selector regression. |
| ~~`web-app-decidim`~~ | 21 | ✅ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| `web-app-baserow` | 21 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2c (deploy-side gap surfaced via guest probe)**: both the baseline `Baserow responds on the canonical domain` and the guest `public-landing → never authenticated` tests fail with `Expected: < 500 Received: 500`. The nginx upstream returns `heading 'connect ECONNREFUSED 127.0.0.1:8000' [level=1]` — the Baserow Django backend never came up. Root cause is deploy-side, not Playwright. |
| ~~`web-app-akaunting`~~ | 21 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants (re-verified). biber + administrator personas explicit-skipped via `PERSONA_*_BLOCKED` in env; OIDC auto-provisioning not wired, see role TODO.md |
| `web-app-fediwall` | 21 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2b (per-spec submit-step missing)**: only one test fails — `each wall surfaces biber's posts according to its servers list` — Test timeout 300000ms. Trace shows the flow stuck on Keycloak `Sign in to your account` page with `biber` + password already pre-filled but the Sign In button never clicked. Likely a missing submit step in the wall-iteration loop of `roles/web-app-fediwall/files/playwright.spec.js`. |
| ~~`web-app-suitecrm`~~ | 20 | ❌ | ✅ | ✅ | ✅ | ✅ | CI run 25797277810 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-snipe-it`~~ | 20 | ❌ | ✅ | ✅ | ✅ | ✅ | CI run 25797277810 — deploy + Playwright PASS for all declared variants |
| `web-app-openproject` | 20 | ❌ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686 — OOM still reproducing: `web-app-openproject/tasks/01_settings.yml:15` (`Run database migrations`) still exits `rc=137` after 30 retries despite commit `bdd59b9db` bumping `web.mem_limit` 4g → 6g. The `rails db:migrate` peak appears to exceed even 6 GB on the public CI runner; consider a further bump to 8g and/or breaking the migration into smaller transactions. |
| `web-app-mediawiki` | 20 | ❌ | ✅ | ❌ | ⏳ | | CI run 25890032686 — **C-new-mediawiki-mariadb-missing**: `web-app-mediawiki/tasks/main.yml:25` (`Wait until database container is healthy`) loops 60× then fails because `docker container inspect mariadb` returns no entry — the mariadb container is literally absent. Sibling of C12 (its compose-up failed earlier in the round). |
| ~~`web-app-funkwhale`~~ | 20 | ❌ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| `web-app-pixelfed` | 20 | ✅ | ✅ | ❌ | ⏳ | | CI run 25890032686 — **C2b/C2c (dashboard tile never rendered)**: both biber and administrator `dashboard → pixelfed oidc login` tests fail with `Timed out waiting for the Pixelfed entry on the dashboard`; snapshot shows the raw nginx autoindex `heading 'Index of /'` instead of the dashboard SPA — no SSO-injected nav. Deploy-side gap. |
| ~~`web-app-jenkins`~~ | 20 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| `web-app-fusiondirectory` | 20 | ✅ | ✅ | ❌ | ❌ | ⏳ | CI run 25890032686 — **C9 Keycloak permanent admin login**: `web-app-keycloak/tasks/04_login.yml:17:7` (Try login with permanent admin) loops `HTTP Error 502: Bad Gateway` then fatals out before the fusiondirectory consumer can run. Different signature than the v0 ✅ Local FULL_CYCLE saw previously — this run never reached the `Wait until application is ready` 502 (v1 oauth2-vhost gating issue) because the round bricks at Keycloak. The earlier `b3d5bf466` LDAP pin still stands. |
| `web-app-peertube` | 20 | ✅ | ✅ | ❌ | ⏳ | | CI run 25890032686 — **C6 family (PeerTube schema)**: `web-app-peertube/tasks/fix-application-schema.yml:25` (`Wait for peertube to create the application table`) retries 6× then FAILS — postgres is reachable but the PeerTube `application` table never appears. Same family as the documented C6 svc-db-postgres purge cascade; Meta load-bearing fix from [020](020-ci-run-25705903504-deploy-remediation.md#meta-root-cause-for-bundles-b-c5-c6-and-most-c1) still pending. |
| `web-app-bluesky` | 20 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2c (deploy-side routing gap on guest probe)**: same role-pair as before (`Playwright failed for roles: ['web-app-bluesky', 'web-app-mailu']`) but artifacts show the bluesky vhost is serving `'Missing X-Forwarded-User from oauth2-proxy. Refusing handoff.'` and the mailu vhost is serving the bare PDS ASCII banner (`'This is an AT Protocol Personal Data Server'`). Both surfaces hit the 60 s test timeout before any assertion — root cause is the oauth2-proxy hand-off / vhost wiring, not the spec. |
| ~~`web-app-opencloud`~~ | 20 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants (cross-verified). Bespoke `opencloud sso login (administrator/biber) lands on files view` covers both personas end-to-end via opencloud's own auth-route; persona shared scenarios `PERSONA_*_BLOCKED` |
| ~~`web-app-pgadmin`~~ | 19 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-lam`~~ | 19 | ❌ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants. Commit `b3d5bf466` additionally pins `services.ldap.{enabled,shared}: true` and drops the `ldap:` variant overrides (LDAP IS the storage backend for LAM). |
| `web-app-taiga` | 21 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2b/C2c (dashboard tile + role login link both missing)**: `expect(getByRole('link', { name: 'Explore Taiga' })).toBeVisible()` fails across two tests (dashboard tile and themed-routes); snapshot shows the upstream `taiga.io` discover-projects marketing page with Login/Sign-up `href="#"` placeholders; the dashboard nav injection and the role's own login route are both absent. Looks deploy-side (nginx serving the marketing fallback), not a spec selector bug. |
| `web-app-mattermost` | 21 | ✅ | ✅ | ❌ | ⏳ | | CI run 25890032686: **C2b (dashboard tile injection missing)**: `getByRole('link', { name: 'Explore Mattermost' })` times out at 300 s; the page that loaded is Mattermost's `"View in Desktop App / View in Browser"` interstitial; the dashboard navbar tile for Mattermost is not being rendered. Bespoke DM-UI selector + universal-logout Keycloak round-trip remain Deep but only after the dashboard-tile injection is restored. |
| ~~`web-app-wordpress`~~ | 21 | ✅ | ✅ | ✅ | ✅ | | CI run 25797277810: deploy + Playwright PASS for all declared variants |
| ~~`web-app-moodle`~~ | 21 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| ~~`web-app-joomla`~~ | 21 | ✅ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| `web-app-fider` | 21 | ✅ | ✅ | ❌ | ⏳ | | CI run 25890032686: **C2b (selectors + dashboard tile)**: biber test fails on `getByRole('link', { name: /sign in/i }).first()` (30 s waitFor; Fider's "Sign in" link not present on the rendered page); the `dashboard → fider` test sees only the autoindex `"Index of /"` listing, so the dashboard tile for Fider was never injected. Both paths point at a deploy-side gap (Fider front-page not deployed + dashboard nav drift) rather than a per-spec selector regression. |
| ~~`web-app-decidim`~~ | 21 | ✅ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| `web-app-baserow` | 21 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2c (deploy-side gap surfaced via guest probe)**: both the baseline `Baserow responds on the canonical domain` and the guest `public-landing → never authenticated` tests fail with `Expected: < 500 Received: 500`. The nginx upstream returns `heading 'connect ECONNREFUSED 127.0.0.1:8000' [level=1]`; the Baserow Django backend never came up. Root cause is deploy-side, not Playwright. |
| ~~`web-app-akaunting`~~ | 21 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants (re-verified). biber + administrator personas explicit-skipped via `PERSONA_*_BLOCKED` in env; OIDC auto-provisioning not wired, see role TODO.md |
| `web-app-fediwall` | 21 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2b (per-spec submit-step missing)**: only one test fails (`each wall surfaces biber's posts according to its servers list`), Test timeout 300000ms. Trace shows the flow stuck on Keycloak `Sign in to your account` page with `biber` + password already pre-filled but the Sign In button never clicked. Likely a missing submit step in the wall-iteration loop of `roles/web-app-fediwall/files/playwright.spec.js`. |
| ~~`web-app-suitecrm`~~ | 20 | ❌ | ✅ | ✅ | ✅ | ✅ | CI run 25797277810: deploy + Playwright PASS for all declared variants |
| ~~`web-app-snipe-it`~~ | 20 | ❌ | ✅ | ✅ | ✅ | ✅ | CI run 25797277810: deploy + Playwright PASS for all declared variants |
| `web-app-openproject` | 20 | ❌ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686: OOM still reproducing: `web-app-openproject/tasks/01_settings.yml:15` (`Run database migrations`) still exits `rc=137` after 30 retries despite commit `bdd59b9db` bumping `web.mem_limit` 4g → 6g. The `rails db:migrate` peak appears to exceed even 6 GB on the public CI runner; consider a further bump to 8g and/or breaking the migration into smaller transactions. |
| `web-app-mediawiki` | 20 | ❌ | ✅ | ❌ | ⏳ | | CI run 25890032686: **C-new-mediawiki-mariadb-missing**: `web-app-mediawiki/tasks/main.yml:25` (`Wait until database container is healthy`) loops 60× then fails because `docker container inspect mariadb` returns no entry; the mariadb container is literally absent. Sibling of C12 (its compose-up failed earlier in the round). |
| ~~`web-app-funkwhale`~~ | 20 | ❌ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| `web-app-pixelfed` | 20 | ✅ | ✅ | ❌ | ⏳ | | CI run 25890032686: **C2b/C2c (dashboard tile never rendered)**: both biber and administrator `dashboard → pixelfed oidc login` tests fail with `Timed out waiting for the Pixelfed entry on the dashboard`; snapshot shows the raw nginx autoindex `heading 'Index of /'` instead of the dashboard SPA; no SSO-injected nav. Deploy-side gap. |
| ~~`web-app-jenkins`~~ | 20 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| `web-app-fusiondirectory` | 20 | ✅ | ✅ | ❌ | ❌ | ⏳ | CI run 25890032686: **C9 Keycloak permanent admin login**: `web-app-keycloak/tasks/04_login.yml:17:7` (Try login with permanent admin) loops `HTTP Error 502: Bad Gateway` then fatals out before the fusiondirectory consumer can run. Different signature than the v0 ✅ Local FULL_CYCLE saw previously. This run never reached the `Wait until application is ready` 502 (v1 oauth2-vhost gating issue) because the round bricks at Keycloak. The earlier `b3d5bf466` LDAP pin still stands. |
| `web-app-peertube` | 20 | ✅ | ✅ | ❌ | ⏳ | | CI run 25890032686: **C6 family (PeerTube schema)**: `web-app-peertube/tasks/fix-application-schema.yml:25` (`Wait for peertube to create the application table`) retries 6× then FAILS. Postgres is reachable but the PeerTube `application` table never appears. Same family as the documented C6 svc-db-postgres purge cascade; Meta load-bearing fix from [020](020-ci-run-25705903504-deploy-remediation.md#meta-root-cause-for-bundles-b-c5-c6-and-most-c1) still pending. |
| `web-app-bluesky` | 20 | ✅ | ✅ | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2c (deploy-side routing gap on guest probe)**: same role-pair as before (`Playwright failed for roles: ['web-app-bluesky', 'web-app-mailu']`) but artifacts show the bluesky vhost is serving `'Missing X-Forwarded-User from oauth2-proxy. Refusing handoff.'` and the mailu vhost is serving the bare PDS ASCII banner (`'This is an AT Protocol Personal Data Server'`). Both surfaces hit the 60 s test timeout before any assertion; root cause is the oauth2-proxy hand-off / vhost wiring, not the spec. |
| ~~`web-app-opencloud`~~ | 20 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants (cross-verified). Bespoke `opencloud sso login (administrator/biber) lands on files view` covers both personas end-to-end via opencloud's own auth-route; persona shared scenarios `PERSONA_*_BLOCKED` |
| ~~`web-app-pgadmin`~~ | 19 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| ~~`web-app-lam`~~ | 19 | ❌ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants. Commit `b3d5bf466` additionally pins `services.ldap.{enabled,shared}: true` and drops the `ldap:` variant overrides (LDAP IS the storage backend for LAM). |
| `web-app-kix` | 19 | ✅ | ✅ | ⏳ | ⏳ | ⏳ | |
| ~~`web-app-yourls`~~ | 19 | ✅ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-phpmyadmin`~~ | 18 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-postmarks`~~ | 18 | ✅ | ✅ | ✅ | ✅ | | CI run 25868178119 — deploy + Playwright PASS for all declared variants (prior C2 regression resolved by persona-helper fast-fail + `PERSONA_BIBER_BLOCKED`) |
| ~~`web-app-chess`~~ | 18 | ❌ | ✅ | ✅ | ✅ | | CI run 25680106742 — deploy success; auth-less small-app, spec collapses cleanly without env |
| ~~`web-app-sphinx`~~ | 17 | ✅ | ✅ | ✅ | ✅ | | CI run 25680106742 — 3/3 active tests pass, 2 personas cleanly skipped |
| ~~`web-app-roulette-wheel`~~ | 17 | ❌ | ✅ | ✅ | ✅ | | CI run 25680106742 — deploy success; auth-less small-app, spec collapses cleanly without env |
| ~~`web-app-mini-qr`~~ | 17 | ❌ | ✅ | ✅ | ✅ | | CI run 25680106742 — deploy success; auth-less small-app, spec collapses cleanly without env |
| ~~`web-app-mig`~~ | 17 | ✅ | ✅ | ✅ | ✅ | | CI run 25680106742 — 3/3 active tests pass, 2 personas cleanly skipped |
| ~~`web-app-littlejs`~~ | 17 | ❌ | ✅ | ✅ | ✅ | | CI run 25680106742 — deploy success; auth-less small-app, spec collapses cleanly without env |
| ~~`web-app-hugo`~~ | 17 | ✅ | ✅ | ✅ | ✅ | | CI run 25680106742 — 4/4 active tests pass, 2 personas cleanly skipped |
| ~~`web-app-bridgy-fed`~~ | 17 | ✅ | ✅ | ✅ | ✅ | | CI run 25680106742 — 3/3 active tests pass, 2 personas cleanly skipped |
| ~~`web-svc-xmpp`~~ | 16 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| ~~`web-svc-libretranslate`~~ | 16 | ✅ | ✅ | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants |
| ~~`web-app-yourls`~~ | 19 | ✅ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| ~~`web-app-phpmyadmin`~~ | 18 | ❌ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| ~~`web-app-postmarks`~~ | 18 | ✅ | ✅ | ✅ | ✅ | | CI run 25868178119: deploy + Playwright PASS for all declared variants (prior C2 regression resolved by persona-helper fast-fail + `PERSONA_BIBER_BLOCKED`) |
| ~~`web-app-chess`~~ | 18 | ❌ | ✅ | ✅ | ✅ | | CI run 25680106742: deploy success; auth-less small-app, spec collapses cleanly without env |
| ~~`web-app-sphinx`~~ | 17 | ✅ | ✅ | ✅ | ✅ | | CI run 25680106742: 3/3 active tests pass, 2 personas cleanly skipped |
| ~~`web-app-roulette-wheel`~~ | 17 | ❌ | ✅ | ✅ | ✅ | | CI run 25680106742: deploy success; auth-less small-app, spec collapses cleanly without env |
| ~~`web-app-mini-qr`~~ | 17 | ❌ | ✅ | ✅ | ✅ | | CI run 25680106742: deploy success; auth-less small-app, spec collapses cleanly without env |
| ~~`web-app-mig`~~ | 17 | ✅ | ✅ | ✅ | ✅ | | CI run 25680106742: 3/3 active tests pass, 2 personas cleanly skipped |
| ~~`web-app-littlejs`~~ | 17 | ❌ | ✅ | ✅ | ✅ | | CI run 25680106742: deploy success; auth-less small-app, spec collapses cleanly without env |
| ~~`web-app-hugo`~~ | 17 | ✅ | ✅ | ✅ | ✅ | | CI run 25680106742: 4/4 active tests pass, 2 personas cleanly skipped |
| ~~`web-app-bridgy-fed`~~ | 17 | ✅ | ✅ | ✅ | ✅ | | CI run 25680106742: 3/3 active tests pass, 2 personas cleanly skipped |
| ~~`web-svc-xmpp`~~ | 16 | ✅ | ✅ | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants |
| ~~`web-svc-libretranslate`~~ | 16 | ✅ | ✅ | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants |
Rows with `has env ❌` and `has spec ✅` ship the auth-less collapse exception per Rule 3: the spec contains a single baseline reachability scenario and no env template is rendered because the role has no `<NAME>_SERVICE_ENABLED=` flags to gate on.
The matrix only lists roles that already ship a Playwright spec. A role with neither artefact is out of scope until it grows one; when that happens, the new spec MUST ship the three persona scenarios per Rule 3 (or document the auth-less collapse explicitly) AND the env template MUST satisfy this requirement from day one.
@@ -184,13 +184,13 @@ The matrix tracks flag closure; the iteration tracks role closure.
For each role in the [Iteration order](#iteration-order) below:
1. Run `make test` before EVERY deploy and EVERY redeploy in this loop — no exceptions, no per-test cherry-picking. `make test` IS the test gate; individual lint / integration test invocations are absorbed by it.
1. Run `make test` before EVERY deploy and EVERY redeploy in this loop. No exceptions, no per-test cherry-picking. `make test` IS the test gate; individual lint / integration test invocations are absorbed by it.
On failure, fix the underlying issue if it is rollout-related; per [Autonomy](#autonomy), unrelated failures are ignored.
2. Run `make deploy-fresh-purged-apps APPS=<role> FULL_CYCLE=true` to establish a fresh full-cycle baseline WITHOUT `SERVICES_DISABLED`.
For matrix-variant roles, iterate through every variant declared in `roles/<role>/meta/variants.yml` via `VARIANT=<idx>` per [Role Loop → Matrix variants](../agents/action/iteration/role.md#matrix-variants); the role is NOT role-closed until every variant has produced a passing deploy plus passing Playwright spec.
3. If the deploy fails or the spec fails, follow [Role Loop](../agents/action/iteration/role.md) and [Playwright Spec Loop](../agents/action/iteration/playwright.md) to fix the root cause.
Apply the fix in the repository files (NOT in the staged copy or the running container).
4. If a specific service genuinely cannot work for the role (upstream limitation, infrastructural exclusion, scope conflict), perform a **flag closure** through one of the closure paths above: either disable the service in `roles/<role>/meta/services.yml` (when the role legitimately has no business consuming it), or mark the entry with `# nocheck: playwright-service-flag` and document the rationale in a one-line comment above the key (e.g. `# nocheck: playwright-service-flag — self-provider`, `# nocheck: playwright-service-flag — infrastructural, no Playwright surface`, `# nocheck: playwright-service-flag — covered by tests/integration/services/test_<x>.py`, `# nocheck: playwright-service-flag — upstream offers no <svc> integration`).
4. If a specific service genuinely cannot work for the role (upstream limitation, infrastructural exclusion, scope conflict), perform a **flag closure** through one of the closure paths above: either disable the service in `roles/<role>/meta/services.yml` (when the role legitimately has no business consuming it), or mark the entry with `# nocheck: playwright-service-flag` and document the rationale in a one-line comment above the key (e.g. `# nocheck: playwright-service-flag: self-provider`, `# nocheck: playwright-service-flag: infrastructural, no Playwright surface`, `# nocheck: playwright-service-flag: covered by tests/integration/services/test_<x>.py`, `# nocheck: playwright-service-flag: upstream offers no <svc> integration`).
The agent decides which path applies based on the role's documented contract; it does NOT ask the operator.
5. The role's `roles/<role>/meta/variants.yml` MAY need adjustment when its declared variants do not exercise the service combinations the spec gates on.
The agent MUST edit `meta/variants.yml` whenever any of the following holds:
@@ -230,7 +230,7 @@ Learnings to propagate include every per-persona assertion shape from the [per-s
- the `ldap` bind path (admin AND `biber`);
- the `email`, `discourse`, federation, and any other service-pair flow that the role itself initiates.
The SPOT-owned cross-service probes from Rule 13 are explicitly **out of scope** for pattern transfer — `dashboard` tile reachability, `prometheus` scrape parity (`up=1` per consumer), `matomo` tracker presence, and the per-consumer biber/administrator deny / accept checks at the prometheus and matomo admin surfaces all live in `roles/web-app-{dashboard,prometheus,matomo}/files/playwright.spec.js`, parameterised over `*_TARGET_ROLES_JSON`. Consumer specs MUST NOT carry these patterns.
The SPOT-owned cross-service probes from Rule 13 are explicitly **out of scope** for pattern transfer. `dashboard` tile reachability, `prometheus` scrape parity (`up=1` per consumer), `matomo` tracker presence, and the per-consumer biber/administrator deny / accept checks at the prometheus and matomo admin surfaces all live in `roles/web-app-{dashboard,prometheus,matomo}/files/playwright.spec.js`, parameterised over `*_TARGET_ROLES_JSON`. Consumer specs MUST NOT carry these patterns.
For every receiving role, the agent MUST adapt the propagated pattern to the role-specific selectors and credentials.
Receiving roles whose `meta/services.yml` does NOT declare the relevant service MUST be skipped from that particular pattern transfer.
@@ -8,13 +8,13 @@ Companion to [019](019-playwright-meta-services-parity.md). Tracks the closure o
| Bundle | Cluster | Roles | State |
| --- | --- | ---: | --- |
| A | C3 — `POSTGRES_ALLOWED_AVG_CONNECTIONS' is undefined` | 7 | **VERIFIED GREEN** in CI run 25774452286 (discourse, mastodon, listmonk, mobilizon, gitlab, pretix, openproject all passed deploy; discourse's failure is now C8, not C3) |
| B | C1 — Mailu 502 cascade | 25 | **DISSOLVED** in CI run 25774452286 — `web-app-mailu` deploy passed in its own slot; the bundle's C1? roles now split across C2 (Playwright regression) and the in-progress in_progress queue (see [CI Run 25774452286 follow-up snapshot](#ci-run-25774452286-follow-up-snapshot)) |
| C | C2 — app-local Playwright failures | 7 → 10 | grew in CI 25774452286 (10 roles: bluesky, baserow, bookwyrm, fediwall, fider, friendica, mattermost, pixelfed, postmarks, taiga); per-role inner-loop spec fixes |
| D | C5 — post-deploy `uri` 5xx | 2 | not reproduced in CI 25774452286 (fusiondirectory now C10; odoo still in_progress) |
| E | C6 — peertube PG `connection refused` | 1 | reproduced in CI run 25774452286; same root cause; Meta load-bearing fix still pending |
| F | C7 — logs truncated | 7 | reclassified in CI run 25774452286 — see follow-up snapshot below |
| G | hub apps with truncated logs | 3 | reclassified GREEN — `web-app-{prometheus, keycloak, opentalk}` all passed deploy in CI run 25774452286 |
| A | C3: `POSTGRES_ALLOWED_AVG_CONNECTIONS' is undefined` | 7 | **VERIFIED GREEN** in CI run 25774452286 (discourse, mastodon, listmonk, mobilizon, gitlab, pretix, openproject all passed deploy; discourse's failure is now C8, not C3) |
| B | C1: Mailu 502 cascade | 25 | **DISSOLVED** in CI run 25774452286: `web-app-mailu` deploy passed in its own slot; the bundle's C1? roles now split across C2 (Playwright regression) and the in-progress in_progress queue (see [CI Run 25774452286 follow-up snapshot](#ci-run-25774452286-follow-up-snapshot)) |
| C | C2: app-local Playwright failures | 7 → 10 | grew in CI 25774452286 (10 roles: bluesky, baserow, bookwyrm, fediwall, fider, friendica, mattermost, pixelfed, postmarks, taiga); per-role inner-loop spec fixes |
| D | C5: post-deploy `uri` 5xx | 2 | not reproduced in CI 25774452286 (fusiondirectory now C10; odoo still in_progress) |
| E | C6: peertube PG `connection refused` | 1 | reproduced in CI run 25774452286; same root cause; Meta load-bearing fix still pending |
| F | C7: logs truncated | 7 | reclassified in CI run 25774452286: see follow-up snapshot below |
| G | hub apps with truncated logs | 3 | reclassified GREEN: `web-app-{prometheus, keycloak, opentalk}` all passed deploy in CI run 25774452286 |
## How an agent uses this doc
@@ -30,15 +30,15 @@ The procedural rules (autonomy, closure vocabulary, resumability, single closing
| ID | Name | Root cause (one-liner) | Fix anchor |
| --- | --- | --- | --- |
| **C1** | Mailu Playwright 502 cascade | Shared mailu / dashboard container purged by inter-round purge but not redeployed; consumer specs land on a 502 vhost. Manifests as `Error: guest visit must not 5xx — Received: 502` from `personas/guest.js:56`. | [Meta root cause](#meta-root-cause-for-bundles-b-c-d-e) — fix the variant-only closure. |
| **C1** | Mailu Playwright 502 cascade | Shared mailu / dashboard container purged by inter-round purge but not redeployed; consumer specs land on a 502 vhost. Manifests as `Error: guest visit must not 5xx; Received: 502` from `personas/guest.js:56`. | [Meta root cause](#meta-root-cause-for-bundles-b-c-d-e): fix the variant-only closure. |
| **C2** | App-local Playwright failures | Role-specific selector drift after Keycloak round-trip (e.g. Friendica `#topbar-first` no longer matches). | Per role: `roles/web-app-<id>/files/playwright.spec.js`. |
| **C2a** | C2 sub-cluster — universal-logout/Keycloak round-trip timeout | The persona finishes the auth chain but the universal-logout step never returns to the role's own domain; `Test timeout` fires on the post-logout navigation. | Verify Keycloak's `post_logout_redirect_uris` for the role, the universal-logout JS injection, and the end-session endpoint. |
| **C2b** | C2 sub-cluster — missing in-app selector after auth | After the auth chain settles the spec cannot find the expected UI element (`Explore <App>` dashboard tile, role's own `Sign in` link, etc.). The page rendered is the upstream marketing site or an autoindex `"Index of /"`. | Almost always deploy-side (dashboard nav injection / role front-page docroot drift). Compare with a known-good role's nginx vhost + dashboard tile manifest. |
| **C2c** | C2 sub-cluster — deploy-side gap surfaced via guest probe | Guest probe lands on a non-200 response or on an oauth2-proxy refusal banner (`Missing X-Forwarded-User from oauth2-proxy. Refusing handoff.`, `ECONNREFUSED <upstream-port>`, bare PDS banner). | Inspect `docker logs <role>-app` for the upstream container; check the oauth2-proxy / nginx upstream wiring. |
| **C2d** | C2 sub-cluster — deploy regression masquerading as Playwright | The role's bootstrap never finished (first-run wizard still showing, no admin user provisioned, etc.), so the Playwright spec times out because the prerequisite UI was never rendered. | Re-run the deploy with the role's post-install task; the Playwright failure is downstream of a deploy gap. |
| **C-new-mediawiki-mariadb-missing** | mediawiki mariadb container literally absent | `web-app-mediawiki/tasks/main.yml:25` (Wait until db container healthy) loops 60× because `docker container inspect mariadb` returns no entry. Sibling of C12 — the mariadb compose-up failed earlier in the round. | Re-apply the entity-keyed purge primitive; then re-deploy. |
| **C-new-odoo-ldap-insert** | odoo res.company.ldap column rename regression | `web-app-odoo/tasks/04_ldap.yml:54` fails with `Cannot execute SQL 'INSERT INTO res_company_ldap (sequence, company, ldap_server, …)'` — the column rename in commit `762586a03` was not propagated into the INSERT statement. | Update the INSERT to use the renamed columns. |
| **C-new-nextcloud-ldap-warm** (environmental, not actively remediated) | nextcloud user_ldap warm-cache PHP file missing | `web-app-nextcloud/tasks/plugins/user_ldap.yml:69` fails with `Failed opening required '/var/www/html/lib/versioncheck.php'`. Initially suspected as a Nextcloud image-bump regression (see issue #227), but source-code review against `stable33` confirms the file exists and is `__DIR__`-loaded (cwd-irrelevant). v0+v1 of the same CI run passed cleanly — only v2's matrix-deploy round mis-staged the volume. Same flake family as C14. | No code change — environmental. Re-evaluate if a future CI run hits the same v2 failure with the same volume-state pattern. |
| **C2a** | C2 sub-cluster: universal-logout/Keycloak round-trip timeout | The persona finishes the auth chain but the universal-logout step never returns to the role's own domain; `Test timeout` fires on the post-logout navigation. | Verify Keycloak's `post_logout_redirect_uris` for the role, the universal-logout JS injection, and the end-session endpoint. |
| **C2b** | C2 sub-cluster: missing in-app selector after auth | After the auth chain settles the spec cannot find the expected UI element (`Explore <App>` dashboard tile, role's own `Sign in` link, etc.). The page rendered is the upstream marketing site or an autoindex `"Index of /"`. | Almost always deploy-side (dashboard nav injection / role front-page docroot drift). Compare with a known-good role's nginx vhost + dashboard tile manifest. |
| **C2c** | C2 sub-cluster: deploy-side gap surfaced via guest probe | Guest probe lands on a non-200 response or on an oauth2-proxy refusal banner (`Missing X-Forwarded-User from oauth2-proxy. Refusing handoff.`, `ECONNREFUSED <upstream-port>`, bare PDS banner). | Inspect `docker logs <role>-app` for the upstream container; check the oauth2-proxy / nginx upstream wiring. |
| **C2d** | C2 sub-cluster: deploy regression masquerading as Playwright | The role's bootstrap never finished (first-run wizard still showing, no admin user provisioned, etc.), so the Playwright spec times out because the prerequisite UI was never rendered. | Re-run the deploy with the role's post-install task; the Playwright failure is downstream of a deploy gap. |
| **C-new-mediawiki-mariadb-missing** | mediawiki mariadb container literally absent | `web-app-mediawiki/tasks/main.yml:25` (Wait until db container healthy) loops 60× because `docker container inspect mariadb` returns no entry. Sibling of C12: the mariadb compose-up failed earlier in the round. | Re-apply the entity-keyed purge primitive; then re-deploy. |
| **C-new-odoo-ldap-insert** | odoo res.company.ldap column rename regression | `web-app-odoo/tasks/04_ldap.yml:54` fails with `Cannot execute SQL 'INSERT INTO res_company_ldap (sequence, company, ldap_server, …)'`; the column rename in commit `762586a03` was not propagated into the INSERT statement. | Update the INSERT to use the renamed columns. |
| **C-new-nextcloud-ldap-warm** (environmental, not actively remediated) | nextcloud user_ldap warm-cache PHP file missing | `web-app-nextcloud/tasks/plugins/user_ldap.yml:69` fails with `Failed opening required '/var/www/html/lib/versioncheck.php'`. Initially suspected as a Nextcloud image-bump regression (see issue #227), but source-code review against `stable33` confirms the file exists and is `__DIR__`-loaded (cwd-irrelevant). v0+v1 of the same CI run passed cleanly; only v2's matrix-deploy round mis-staged the volume. Same flake family as C14. | No code change; environmental. Re-evaluate if a future CI run hits the same v2 failure with the same volume-state pattern. |
| **C3** | `POSTGRES_ALLOWED_AVG_CONNECTIONS' is undefined` | A `set_fact run_once: true` did not survive the matrix-deploy round split. | **LANDED**: vars hoisted to [group_vars/all/17_resource.yml](../../group_vars/all/17_resource.yml); filter relocated to [plugins/filter/split_postgres_connections.py](../../plugins/filter/split_postgres_connections.py). |
| **C5** | Post-deploy `uri` 5xx | Upstream container 500 / 502 during the role's own post-deploy health probe. Same shape as C1 (purged dep) but masked as a role-local failure. | Capture `docker logs <container>` of the failing role; widen the `uri` probe or add a `wait_for` if the container is genuinely slow. |
| **C6** | Peertube `Connection refused to 127.0.0.1:5432` | svc-db-postgres container purged between rounds without being re-deployed; peertube's `postgresql_query` from the ansible controller hits an empty host port. | Same fix as C1 / [Meta root cause](#meta-root-cause-for-bundles-b-c-d-e). |
@@ -51,23 +51,23 @@ The procedural rules (autonomy, closure vocabulary, resumability, single closing
| **C13** | Mediawiki image missing | `docker image inspect failed for mediawiki:1.45: Error response from daemon: No such image: mediawiki:1.45`. The role references a tag that is not in the local image cache and `docker pull` did not run (or it failed earlier). | Either pin to an available mediawiki tag or add the missing `docker_image` pull step to the role. |
| **C14** | Container networking port collision (CI-only, environmental) | `Error response from daemon: failed to set up container networking: Address already in use` during `Container <role>-proxy Starting`. Observed only in CI matrix-deploy rounds; does NOT reproduce on a local full-cycle deploy, so the root cause is contention between concurrently-deploying roles in the CI runner's shared port space, not a defect in the role's `compose.yml.j2`. | No code change needed at the role level. If recurrent across runs, isolate the CI matrix-deploy rounds so port-mapped proxies do not co-exist, or drop host-port bindings for non-customer-facing containers. |
> Cluster **C4** (TestEnv NGINX-STALE, 5 distros) is out of scope here — it lives in `test-development`, not `test-deploy-server`.
> Cluster **C4** (TestEnv NGINX-STALE, 5 distros) is out of scope here; it lives in `test-development`, not `test-deploy-server`.
## Bundle fix recipes
Bundles MUST be worked through in the order below, because Bundles A and B close most cells via hub fixes.
### Bundle A — `POSTGRES_ALLOWED_AVG_CONNECTIONS` (LANDED)
### Bundle A: `POSTGRES_ALLOWED_AVG_CONNECTIONS` (LANDED)
Hub fix already on the branch. Verification gate: next CI run reports `success` for `web-app-{discourse,mastodon,listmonk,mobilizon,gitlab,pretix,openproject}`. Once observed, strike those rows through here and in [019](019-playwright-meta-services-parity.md#per-role-iteration-matrix).
### Bundle B — Mailu 502 cascade (LOAD-BEARING)
### Bundle B: Mailu 502 cascade (LOAD-BEARING)
Land the [Meta root cause fix](#meta-root-cause-for-bundles-b-c-d-e). It closes Bundles B + E + most of F automatically. After it lands, re-deploy the C1? candidates and reclassify any that still fail.
Roles: `web-app-mailu` (verified), `web-app-bookwyrm` (verified), plus 21 suspected C1? (see matrix).
### Bundle C — App-local Playwright selector fixes
### Bundle C: App-local Playwright selector fixes
Per role, inner-loop work via [Playwright Spec Loop](../agents/action/iteration/playwright.md):
@@ -77,7 +77,7 @@ Per role, inner-loop work via [Playwright Spec Loop](../agents/action/iteration/
Roles: `web-app-mailu`, `web-app-opencloud`, `web-app-friendica`, `web-app-mattermost`, `web-app-taiga`, `web-app-fediwall`, `web-app-postmarks`.
### Bundle D — Post-deploy `uri` 5xx
### Bundle D: Post-deploy `uri` 5xx
Per role:
@@ -87,17 +87,17 @@ Per role:
Roles: `web-app-fusiondirectory`, `web-app-odoo`.
### Bundle E — Peertube PG
### Bundle E: Peertube PG
Closed by the [Meta root cause fix](#meta-root-cause-for-bundles-b-c-d-e). No separate per-role work needed once that fix lands.
### Bundle F — Reclassify
### Bundle F: Reclassify
Re-deploy the role locally after Bundles A+B have closed; the CI log was truncated and tells us nothing. Move the role into its actual cluster, then apply that cluster's recipe.
Roles: `web-app-nextcloud`, `web-app-gitea`, `web-app-shopware`, `web-app-matrix`, `web-app-espocrm`, `web-app-mediawiki`, `web-app-funkwhale`.
### Bundle G — Hub apps with truncated logs
### Bundle G: Hub apps with truncated logs
Same as Bundle F. Roles: `web-app-prometheus`, `web-app-keycloak`, `web-app-opentalk`.
@@ -109,55 +109,55 @@ Sorted DESC by `total` (carried over from [019](019-playwright-meta-services-par
| Role | total | cluster | bundle | v0 | v1 | v2 | notes |
| --- | ---: | --- | --- | --- | --- | --- | --- |
| ~~`web-app-prometheus`~~ | 173 | C7 | G | ✅ | ✅ | | Local full-cycle re-deploy v0+v1 — both rounds (PASS 1+2) `failed=0`, 0 Playwright failures. CI flake; not a real cluster-C7 issue. |
| ~~`web-app-mailu`~~ | 139 | C1+C2 | B+C | ✅ | ✅ | | Local full-cycle re-deploy v0+v1 — all 4 PASS rounds `failed=0`; guest persona scenario passed in 531ms. CI 502 does NOT reproduce locally; environmental flake. |
| ~~`web-app-keycloak`~~ | 130 | C7 | G | ✅ | ✅ | | Local full-cycle re-deploy v0+v1 — all 4 PASS rounds `failed=0`, 0 Playwright failures. CI flake. |
| ~~`web-app-nextcloud`~~ | 27 | environmental | — | ✅ | ✅ | ✅ | CI run 25890032686 — v0+v1 deploys clean (`failed=0` in PLAY RECAPs); only v2 fail'd in `tasks/plugins/user_ldap.yml:69` with `versioncheck.php No such file or directory`. Source-code review confirms file exists in `stable33` and is `__DIR__`-loaded (cwd-independent), so missing-file means the entrypoint rsync did not stage `/var/www/html` for v2's round. Same env-flake family as the prior C14 — re-struck pending recurrence in a future CI run. |
| ~~`web-app-bigbluebutton`~~ | 24 | — | — | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-discourse`~~ | 24 | C8 | — | ✅ | ✅ | | Local FULL_CYCLE v0+v1 ✅. Real root cause was variant 1's `postgres.shared: false` forcing the bundled launcher postgres (no pgvector); fixed by pinning `services.postgres.shared: true` since `svc-db-postgres` has pgvector compiled. OIDC tests now gated on `OIDC_SERVICE_ENABLED=true`. |
| ~~`web-app-mastodon`~~ | 23 | — | — | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants (C3 hub fix verified). |
| `web-app-friendica` | 23 | C2b+per-spec | C | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2b (dashboard tile) + per-spec (post-login surface)**: 4 failing tests — admin dashboard click times out, side-by-side login never sees `#topbar-first`/`#navbar-apps-menu`/logout link, biber 403 at oauth2-proxy, admin `inAppLogout` finds no logout control. Mixed: dashboard auth chain failure + post-login surface regression (LDAP autocreate / session). |
| ~~`web-app-opentalk`~~ | 23 | — | — | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-listmonk`~~ | 22 | — | — | ✅ | ✅ | | CI run 25890032686 — deploy + Playwright PASS for all declared variants (v1 verified; C11 DB-upgrade race did not reproduce). |
| `web-app-gitea` | 22 | C12 | — | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C12 compose-up recurrence**: `sys-svc-compose/handlers/main.yml:76` (`compose up`) exits non-zero again after gitea image pull. Same orphan-default-network signature; the prior commit `c6affc96f` purge primitive was partially reverted by `602c05ed3 Reverted matrix fix`. Re-apply `scripts/container/purge/entity/network.sh` + global `docker network prune -f`. Playwright issues stay gated behind compose-up. |
| ~~`web-app-openwebui`~~ | 22 | — | — | ✅ | ✅ | ✅ | CI run 25797277810 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-flowise`~~ | 22 | — | — | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| `web-app-bookwyrm` | 22 | C2d | C | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2d (deploy regression masquerading as Playwright)**: container still on the `Instance Configuration / Continue` wizard; the persona helper times out on `getByRole('textbox', { name: /username|email/i })`. Root cause is deploy-side bootstrap, not a Playwright spec bug. |
| ~~`web-app-minio`~~ | 22 | — | — | ✅ | ✅ | ✅ | CI run 25797277810 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-xwiki`~~ | 21 | — | — | ✅ | ✅ | | CI run 25797277810 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-shopware`~~ | 21 | — | — | ✅ | ✅ | ✅ | CI run 25797277810 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-pretix`~~ | 21 | — | — | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants (C3 hub fix verified). |
| `web-app-odoo` | 21 | C-new-odoo-ldap-insert | — | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C-new-odoo-ldap-insert**: `web-app-odoo/tasks/04_ldap.yml:54` (Create new LDAP configuration) fails with `Cannot execute SQL 'INSERT INTO res_company_ldap (sequence, company, ldap_server, …)'`. Regression introduced by `762586a03 fix: matrix docker-network label clash + odoo res.company.ldap column rename` — the column rename was not reflected in the INSERT. |
| ~~`web-app-mobilizon`~~ | 21 | — | — | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants (C3 hub fix verified). |
| `web-app-matrix` | 21 | C12 | — | ❌ | ⏳ | | CI run 25890032686 — **C12 recurrence**: `01_docker.yml:76` fails with `network matrix was found but has incorrect label com.docker.compose.network set to "" (expected: "default")`. Commit `c6affc96f` purge primitive was partially reverted by `602c05ed3 Reverted matrix fix`; re-applying the entity-keyed purge closes this row again. DM-scenario remains gated behind compose-up. |
| ~~`web-app-gitlab`~~ | 21 | — | — | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants (C3 hub fix verified). |
| ~~`web-app-espocrm`~~ | 21 | C9 | — | ✅ | ✅ | ✅ | Local FULL_CYCLE v0+v1+v2 ✅. Real root cause was a **duplicate `depends_on:` block** in `templates/compose.yml.j2` (websocket service rendered both the `dmbs_excl.yml.j2` include AND a manual `depends_on:`); commits `eaa51b39d` then `1f5689e44` consolidate daemon + websocket to the `dmbs_incl.yml.j2` include pattern. The earlier "C9 Keycloak admin login fatal" in the CI log was just the rescue-block noise — not the actual cluster. |
| `web-app-taiga` | 21 | C2b/C2c | C | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2b/C2c**: `expect(getByRole('link', { name: 'Explore Taiga' })).toBeVisible()` fails on dashboard tile + themed-routes tests; snapshot shows the upstream `taiga.io` discover-projects marketing page with placeholder `href="#"` links — dashboard nav injection and the role's own login route both absent. Looks deploy-side. |
| `web-app-mattermost` | 21 | C2b | C | ❌ | ⏳ | | CI run 25890032686 — **C2b (dashboard tile injection missing)**: `getByRole('link', { name: 'Explore Mattermost' })` 300 s timeout; the page loaded is Mattermost's `"View in Desktop App / View in Browser"` interstitial — dashboard navbar tile not rendered. |
| ~~`web-app-wordpress`~~ | 21 | — | — | ✅ | ✅ | | CI run 25797277810 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-joomla`~~ | 21 | — | — | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| `web-app-fider` | 21 | C2b | C | ❌ | ⏳ | | CI run 25890032686 — **C2b (deploy-side gap)**: biber `Sign in` link missing (`getByRole('link', name:/sign in/i)` 30 s timeout); `dashboard → fider` sees the autoindex `"Index of /"`. Both paths point at a deploy-side gap (Fider front-page + dashboard nav drift). |
| ~~`web-app-decidim`~~ | 21 | — | — | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| `web-app-baserow` | 21 | C2c | C | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2c (deploy-side gap)**: nginx upstream returns `heading 'connect ECONNREFUSED 127.0.0.1:8000'` — Baserow Django backend never came up; both baseline + guest tests fail `Expected: < 500 Received: 500`. Root cause is deploy-side, not Playwright. |
| ~~`web-app-akaunting`~~ | 21 | — | — | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| `web-app-fediwall` | 21 | C2b | C | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2b (per-spec missing submit step)**: single failing test `each wall surfaces biber's posts according to its servers list` — flow stuck on Keycloak login form with biber+password pre-filled but Sign In never clicked. Add the submit step to the wall-iteration loop in `roles/web-app-fediwall/files/playwright.spec.js`. |
| ~~`web-app-suitecrm`~~ | 20 | — | — | ✅ | ✅ | ✅ | CI run 25797277810 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-snipe-it`~~ | 20 | — | — | ✅ | ✅ | ✅ | CI run 25797277810 — deploy + Playwright PASS for all declared variants. |
| `web-app-openproject` | 20 | C9-OOM | — | ❌ | ⏳ | ⏳ | CI run 25890032686 — OOM still reproducing: `01_settings.yml:15` still exits `rc=137` after 30 retries despite `bdd59b9db` bumping `web.mem_limit` 4g → 6g. Consider a further bump to 8g and/or breaking the migration into smaller transactions. |
| `web-app-mediawiki` | 20 | C-new-mediawiki-mariadb-missing | — | ❌ | ⏳ | | CI run 25890032686 — **C-new-mediawiki-mariadb-missing**: `web-app-mediawiki/tasks/main.yml:25` (Wait until db container healthy) loops 60× because `docker container inspect mariadb` returns no entry — mariadb container literally absent. Sibling of C12 (compose-up failed earlier). |
| ~~`web-app-funkwhale`~~ | 20 | — | — | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| `web-app-pixelfed` | 20 | C2b/C2c | C | ❌ | ⏳ | | CI run 25890032686 — **C2b/C2c (dashboard tile never rendered)**: biber + administrator `dashboard → pixelfed oidc login` tests fail; snapshot shows nginx autoindex `heading 'Index of /'` — no SSO-injected nav. Deploy-side gap. |
| ~~`web-app-jenkins`~~ | 20 | — | — | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| `web-app-fusiondirectory` | 20 | C9 | — | ❌ | ❌ | ⏳ | CI run 25890032686 — **C9 Keycloak permanent admin login**: `web-app-keycloak/tasks/04_login.yml:17:7` (Try login with permanent admin) loops `HTTP Error 502: Bad Gateway` then fatals before the fusiondirectory consumer can run. Different signature from the prior v0 ✅ Local FULL_CYCLE; the earlier `b3d5bf466` LDAP pin still stands and the v1 oauth2-vhost gating issue is unchanged. |
| `web-app-peertube` | 20 | C6-schema | E | ❌ | ⏳ | | CI run 25890032686 — **C6 family**: `web-app-peertube/tasks/fix-application-schema.yml:25` (Wait for peertube to create the `application` table) retries 6× then FAILS — postgres reachable but table never appears. Same root family as the [Meta root cause](#meta-root-cause-for-bundles-b-c5-c6-and-most-c1); still pending. |
| `web-app-bluesky` | 20 | C2c | C | ❌ | ⏳ | ⏳ | CI run 25890032686 — **C2c (deploy-side routing gap)**: artifacts show the bluesky vhost serving `'Missing X-Forwarded-User from oauth2-proxy. Refusing handoff.'` and the mailu vhost serving the bare PDS ASCII banner; the guest probe times out at 60 s before any assertion. Root cause is oauth2-proxy hand-off / vhost wiring, not the spec. |
| ~~`web-app-opencloud`~~ | 20 | — | — | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants (cross-verified). |
| ~~`web-app-pgadmin`~~ | 19 | — | — | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-lam`~~ | 19 | — | — | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants. Commit `b3d5bf466` additionally pins `services.ldap.{enabled,shared}: true` and drops the variant overrides (LDAP IS the storage backend for LAM). |
| ~~`web-app-yourls`~~ | 19 | — | — | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-phpmyadmin`~~ | 18 | — | — | ✅ | ✅ | | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-postmarks`~~ | 18 | — | — | ✅ | ✅ | | CI run 25868178119 — deploy + Playwright PASS for all declared variants (prior C2 regression resolved by persona-helper fast-fail + `PERSONA_BIBER_BLOCKED`). |
| ~~`web-svc-xmpp`~~ | 16 | — | — | ✅ | ✅ | ✅ | CI run 25774452286 — deploy + Playwright PASS for all declared variants. |
| ~~`web-app-prometheus`~~ | 173 | C7 | G | ✅ | ✅ | | Local full-cycle re-deploy v0+v1: both rounds (PASS 1+2) `failed=0`, 0 Playwright failures. CI flake; not a real cluster-C7 issue. |
| ~~`web-app-mailu`~~ | 139 | C1+C2 | B+C | ✅ | ✅ | | Local full-cycle re-deploy v0+v1: all 4 PASS rounds `failed=0`; guest persona scenario passed in 531ms. CI 502 does NOT reproduce locally; environmental flake. |
| ~~`web-app-keycloak`~~ | 130 | C7 | G | ✅ | ✅ | | Local full-cycle re-deploy v0+v1: all 4 PASS rounds `failed=0`, 0 Playwright failures. CI flake. |
| ~~`web-app-nextcloud`~~ | 27 | environmental | n/a | ✅ | ✅ | ✅ | CI run 25890032686: v0+v1 deploys clean (`failed=0` in PLAY RECAPs); only v2 fail'd in `tasks/plugins/user_ldap.yml:69` with `versioncheck.php No such file or directory`. Source-code review confirms file exists in `stable33` and is `__DIR__`-loaded (cwd-independent), so missing-file means the entrypoint rsync did not stage `/var/www/html` for v2's round. Same env-flake family as the prior C14: re-struck pending recurrence in a future CI run. |
| ~~`web-app-bigbluebutton`~~ | 24 | n/a | n/a | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-discourse`~~ | 24 | C8 | n/a | ✅ | ✅ | | Local FULL_CYCLE v0+v1 ✅. Real root cause was variant 1's `postgres.shared: false` forcing the bundled launcher postgres (no pgvector); fixed by pinning `services.postgres.shared: true` since `svc-db-postgres` has pgvector compiled. OIDC tests now gated on `OIDC_SERVICE_ENABLED=true`. |
| ~~`web-app-mastodon`~~ | 23 | n/a | n/a | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants (C3 hub fix verified). |
| `web-app-friendica` | 23 | C2b+per-spec | C | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2b (dashboard tile) + per-spec (post-login surface)**: 4 failing tests. Admin dashboard click times out, side-by-side login never sees `#topbar-first`/`#navbar-apps-menu`/logout link, biber 403 at oauth2-proxy, admin `inAppLogout` finds no logout control. Mixed: dashboard auth chain failure + post-login surface regression (LDAP autocreate / session). |
| ~~`web-app-opentalk`~~ | 23 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-listmonk`~~ | 22 | n/a | n/a | ✅ | ✅ | | CI run 25890032686: deploy + Playwright PASS for all declared variants (v1 verified; C11 DB-upgrade race did not reproduce). |
| `web-app-gitea` | 22 | C12 | n/a | ❌ | ⏳ | ⏳ | CI run 25890032686: **C12 compose-up recurrence**: `sys-svc-compose/handlers/main.yml:76` (`compose up`) exits non-zero again after gitea image pull. Same orphan-default-network signature; the prior commit `c6affc96f` purge primitive was partially reverted by `602c05ed3 Reverted matrix fix`. Re-apply `scripts/container/purge/entity/network.sh` + global `docker network prune -f`. Playwright issues stay gated behind compose-up. |
| ~~`web-app-openwebui`~~ | 22 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25797277810: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-flowise`~~ | 22 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| `web-app-bookwyrm` | 22 | C2d | C | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2d (deploy regression masquerading as Playwright)**: container still on the `Instance Configuration / Continue` wizard; the persona helper times out on `getByRole('textbox', { name: /username|email/i })`. Root cause is deploy-side bootstrap, not a Playwright spec bug. |
| ~~`web-app-minio`~~ | 22 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25797277810: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-xwiki`~~ | 21 | n/a | n/a | ✅ | ✅ | | CI run 25797277810: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-shopware`~~ | 21 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25797277810: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-pretix`~~ | 21 | n/a | n/a | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants (C3 hub fix verified). |
| `web-app-odoo` | 21 | C-new-odoo-ldap-insert | n/a | ❌ | ⏳ | ⏳ | CI run 25890032686: **C-new-odoo-ldap-insert**: `web-app-odoo/tasks/04_ldap.yml:54` (Create new LDAP configuration) fails with `Cannot execute SQL 'INSERT INTO res_company_ldap (sequence, company, ldap_server, …)'`. Regression introduced by `762586a03 fix: matrix docker-network label clash + odoo res.company.ldap column rename`; the column rename was not reflected in the INSERT. |
| ~~`web-app-mobilizon`~~ | 21 | n/a | n/a | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants (C3 hub fix verified). |
| `web-app-matrix` | 21 | C12 | n/a | ❌ | ⏳ | | CI run 25890032686: **C12 recurrence**: `01_docker.yml:76` fails with `network matrix was found but has incorrect label com.docker.compose.network set to "" (expected: "default")`. Commit `c6affc96f` purge primitive was partially reverted by `602c05ed3 Reverted matrix fix`; re-applying the entity-keyed purge closes this row again. DM-scenario remains gated behind compose-up. |
| ~~`web-app-gitlab`~~ | 21 | n/a | n/a | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants (C3 hub fix verified). |
| ~~`web-app-espocrm`~~ | 21 | C9 | n/a | ✅ | ✅ | ✅ | Local FULL_CYCLE v0+v1+v2 ✅. Real root cause was a **duplicate `depends_on:` block** in `templates/compose.yml.j2` (websocket service rendered both the `dmbs_excl.yml.j2` include AND a manual `depends_on:`); commits `eaa51b39d` then `1f5689e44` consolidate daemon + websocket to the `dmbs_incl.yml.j2` include pattern. The earlier "C9 Keycloak admin login fatal" in the CI log was just the rescue-block noise, not the actual cluster. |
| `web-app-taiga` | 21 | C2b/C2c | C | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2b/C2c**: `expect(getByRole('link', { name: 'Explore Taiga' })).toBeVisible()` fails on dashboard tile + themed-routes tests; snapshot shows the upstream `taiga.io` discover-projects marketing page with placeholder `href="#"` links; dashboard nav injection and the role's own login route both absent. Looks deploy-side. |
| `web-app-mattermost` | 21 | C2b | C | ❌ | ⏳ | | CI run 25890032686: **C2b (dashboard tile injection missing)**: `getByRole('link', { name: 'Explore Mattermost' })` 300 s timeout; the page loaded is Mattermost's `"View in Desktop App / View in Browser"` interstitial; dashboard navbar tile not rendered. |
| ~~`web-app-wordpress`~~ | 21 | n/a | n/a | ✅ | ✅ | | CI run 25797277810: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-joomla`~~ | 21 | n/a | n/a | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| `web-app-fider` | 21 | C2b | C | ❌ | ⏳ | | CI run 25890032686: **C2b (deploy-side gap)**: biber `Sign in` link missing (`getByRole('link', name:/sign in/i)` 30 s timeout); `dashboard → fider` sees the autoindex `"Index of /"`. Both paths point at a deploy-side gap (Fider front-page + dashboard nav drift). |
| ~~`web-app-decidim`~~ | 21 | n/a | n/a | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| `web-app-baserow` | 21 | C2c | C | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2c (deploy-side gap)**: nginx upstream returns `heading 'connect ECONNREFUSED 127.0.0.1:8000'`; Baserow Django backend never came up; both baseline + guest tests fail `Expected: < 500 Received: 500`. Root cause is deploy-side, not Playwright. |
| ~~`web-app-akaunting`~~ | 21 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| `web-app-fediwall` | 21 | C2b | C | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2b (per-spec missing submit step)**: single failing test `each wall surfaces biber's posts according to its servers list`. Flow stuck on Keycloak login form with biber+password pre-filled but Sign In never clicked. Add the submit step to the wall-iteration loop in `roles/web-app-fediwall/files/playwright.spec.js`. |
| ~~`web-app-suitecrm`~~ | 20 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25797277810: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-snipe-it`~~ | 20 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25797277810: deploy + Playwright PASS for all declared variants. |
| `web-app-openproject` | 20 | C9-OOM | n/a | ❌ | ⏳ | ⏳ | CI run 25890032686: OOM still reproducing: `01_settings.yml:15` still exits `rc=137` after 30 retries despite `bdd59b9db` bumping `web.mem_limit` 4g → 6g. Consider a further bump to 8g and/or breaking the migration into smaller transactions. |
| `web-app-mediawiki` | 20 | C-new-mediawiki-mariadb-missing | n/a | ❌ | ⏳ | | CI run 25890032686: **C-new-mediawiki-mariadb-missing**: `web-app-mediawiki/tasks/main.yml:25` (Wait until db container healthy) loops 60× because `docker container inspect mariadb` returns no entry; mariadb container literally absent. Sibling of C12 (compose-up failed earlier). |
| ~~`web-app-funkwhale`~~ | 20 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| `web-app-pixelfed` | 20 | C2b/C2c | C | ❌ | ⏳ | | CI run 25890032686: **C2b/C2c (dashboard tile never rendered)**: biber + administrator `dashboard → pixelfed oidc login` tests fail; snapshot shows nginx autoindex `heading 'Index of /'`; no SSO-injected nav. Deploy-side gap. |
| ~~`web-app-jenkins`~~ | 20 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| `web-app-fusiondirectory` | 20 | C9 | n/a | ❌ | ❌ | ⏳ | CI run 25890032686: **C9 Keycloak permanent admin login**: `web-app-keycloak/tasks/04_login.yml:17:7` (Try login with permanent admin) loops `HTTP Error 502: Bad Gateway` then fatals before the fusiondirectory consumer can run. Different signature from the prior v0 ✅ Local FULL_CYCLE; the earlier `b3d5bf466` LDAP pin still stands and the v1 oauth2-vhost gating issue is unchanged. |
| `web-app-peertube` | 20 | C6-schema | E | ❌ | ⏳ | | CI run 25890032686: **C6 family**: `web-app-peertube/tasks/fix-application-schema.yml:25` (Wait for peertube to create the `application` table) retries 6× then FAILS. Postgres reachable but table never appears. Same root family as the [Meta root cause](#meta-root-cause-for-bundles-b-c5-c6-and-most-c1); still pending. |
| `web-app-bluesky` | 20 | C2c | C | ❌ | ⏳ | ⏳ | CI run 25890032686: **C2c (deploy-side routing gap)**: artifacts show the bluesky vhost serving `'Missing X-Forwarded-User from oauth2-proxy. Refusing handoff.'` and the mailu vhost serving the bare PDS ASCII banner; the guest probe times out at 60 s before any assertion. Root cause is oauth2-proxy hand-off / vhost wiring, not the spec. |
| ~~`web-app-opencloud`~~ | 20 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants (cross-verified). |
| ~~`web-app-pgadmin`~~ | 19 | n/a | n/a | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-lam`~~ | 19 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants. Commit `b3d5bf466` additionally pins `services.ldap.{enabled,shared}: true` and drops the variant overrides (LDAP IS the storage backend for LAM). |
| ~~`web-app-yourls`~~ | 19 | n/a | n/a | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-phpmyadmin`~~ | 18 | n/a | n/a | ✅ | ✅ | | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
| ~~`web-app-postmarks`~~ | 18 | n/a | n/a | ✅ | ✅ | | CI run 25868178119: deploy + Playwright PASS for all declared variants (prior C2 regression resolved by persona-helper fast-fail + `PERSONA_BIBER_BLOCKED`). |
| ~~`web-svc-xmpp`~~ | 16 | n/a | n/a | ✅ | ✅ | ✅ | CI run 25774452286: deploy + Playwright PASS for all declared variants. |
**Total failed roles in CI Run 25705903504 (deploy matrix only):** 49.
@@ -186,7 +186,7 @@ Re-deploy on the same branch `feature/web-app-kix` after [25705903504](https://g
| **C10** GHCR mailu manifest 502 | fusiondirectory | 1 | Likely transient; if it reproduces, wrap mailu pulls in a retry helper. |
| **C6** Peertube PG Connection refused | peertube | 1 | Same as [Meta root cause](#meta-root-cause-for-bundles-b-c5-c6-and-most-c1); waiting for that fix. |
**Pattern shift vs. CI 25705903504.** Bundle A (C3 `POSTGRES_ALLOWED_AVG_CONNECTIONS`) is now verified GREEN — every formerly-C3 role passed in this run (discourse is the lone exception, but its failure is C8, not C3). The dominant remaining failure mode has shifted from C1 (Mailu cascade) and C3 (postgres var) to **C2 Playwright regressions** and **C9 Keycloak admin login**. The Meta load-bearing fix for Bundles B/E (C6) is still pending; peertube reproduces. The "Suspected Mailu cascade" (C1?) labels from CI 25705903504 are now reclassified per the cluster summary above.
**Pattern shift vs. CI 25705903504.** Bundle A (C3 `POSTGRES_ALLOWED_AVG_CONNECTIONS`) is now verified GREEN. Every formerly-C3 role passed in this run (discourse is the lone exception, but its failure is C8, not C3). The dominant remaining failure mode has shifted from C1 (Mailu cascade) and C3 (postgres var) to **C2 Playwright regressions** and **C9 Keycloak admin login**. The Meta load-bearing fix for Bundles B/E (C6) is still pending; peertube reproduces. The "Suspected Mailu cascade" (C1?) labels from CI 25705903504 are now reclassified per the cluster summary above.
### Infrastructure improvements landed alongside this remediation loop
@@ -202,18 +202,18 @@ These commits are not per-role fixes but address the underlying environment so s
Re-deploy on `feature/web-app-kix` after [25774452286](https://github.com/kevinveenbirkenbach/infinito-nexus-core/actions/runs/25774452286). The `test-deploy-server` matrix completed 71 jobs: **54 passed**, **17 failed**.
**Newly green** (was open in the matrix above, now closed): `web-app-listmonk` — v1 verified, prior C11 DB-upgrade race did not reproduce.
**Newly green** (was open in the matrix above, now closed): `web-app-listmonk`; v1 verified, prior C11 DB-upgrade race did not reproduce.
**Newly red, then re-classified as environmental and re-struck:** `web-app-nextcloud` — v0+v1 clean, only v2 fail'd in `Warm LDAP cache` with `versioncheck.php` missing. Source-code analysis (Odoo-style verification against upstream `stable33`) confirmed the file exists and is `__DIR__`-loaded (cwd-irrelevant), so the failure mode is "rsync did not stage the source in v2's round" — same matrix-deploy round-isolation pattern as the prior C14 flake. Re-struck pending recurrence in a future CI run.
**Newly red, then re-classified as environmental and re-struck:** `web-app-nextcloud`; v0+v1 clean, only v2 fail'd in `Warm LDAP cache` with `versioncheck.php` missing. Source-code analysis (Odoo-style verification against upstream `stable33`) confirmed the file exists and is `__DIR__`-loaded (cwd-irrelevant), so the failure mode is "rsync did not stage the source in v2's round"; same matrix-deploy round-isolation pattern as the prior C14 flake. Re-struck pending recurrence in a future CI run.
### Cluster summary (CI 25890032686)
| Cluster | Roles | Count | Action |
| --- | --- | ---: | --- |
| **C2b** (dashboard tile / role front-page injection missing) | fider, mattermost, pixelfed, taiga, friendica (partial) | 5 | Deploy-side gap: dashboard nav injection drift + role docroot drift. Compare against a known-good role's vhost + dashboard manifest. Almost certainly NOT per-spec selector regressions. |
| **C2b (per-spec)** (missing submit / post-login selector regression) | fediwall, friendica (partial) | 2 | fediwall: missing Sign In click in the wall-iteration loop; friendica: post-login `#topbar-first`/`#navbar-apps-menu`/logout-link selectors no longer match — Friendica's authenticated surface drifted (LDAP autocreate / session change). |
| **C2b (per-spec)** (missing submit / post-login selector regression) | fediwall, friendica (partial) | 2 | fediwall: missing Sign In click in the wall-iteration loop; friendica: post-login `#topbar-first`/`#navbar-apps-menu`/logout-link selectors no longer match. Friendica's authenticated surface drifted (LDAP autocreate / session change). |
| **C2c** (deploy-side gap surfaced via guest probe) | baserow, bluesky | 2 | `docker logs <role>-app` to find why the upstream is not serving; check oauth2-proxy / nginx upstream wiring. |
| **C2d** (deploy regression masquerading as Playwright) | bookwyrm | 1 | Bookwyrm container still on first-run wizard — re-run the bootstrap task; the Playwright timeout is downstream. |
| **C2d** (deploy regression masquerading as Playwright) | bookwyrm | 1 | Bookwyrm container still on first-run wizard; re-run the bootstrap task; the Playwright timeout is downstream. |
| **C9** (Keycloak permanent admin login) | fusiondirectory | 1 | `04_login.yml:17:7` loops `HTTP Error 502` then fatals. Same suspected root cause as documented in C9 (admin password drift between rounds). |
| **C9-OOM** (rails db:migrate OOM) | openproject | 1 | Despite the `bdd59b9db` 4g → 6g mem_limit bump, `rc=137` still fires. Bump to 8g and/or chunk the migration. |
| **C12** (compose-up orphan-network) | gitea, matrix | 2 | `602c05ed3 Reverted matrix fix` partially rolled back `c6affc96f`'s purge primitive. Re-apply `scripts/container/purge/entity/network.sh` + global `docker network prune -f`. |
@@ -224,14 +224,14 @@ Re-deploy on `feature/web-app-kix` after [25774452286](https://github.com/kevinv
### Pattern shift vs. CI 25774452286
The Playwright failure shape has shifted further: most former C2 roles are now better classified as **C2b/C2c/C2d** — i.e. deploy-side gaps surfacing in Playwright rather than per-spec selector drift. The C12 orphan-network signature has recurred for **gitea + matrix** because the purge primitive was partially reverted. **openproject's OOM** persists even at 6 GB. **nextcloud** flipped from "env-flake / passing locally" to a real Nextcloud-image regression. C-new clusters have been added to the [Cluster catalogue](#cluster-catalogue) for odoo, mediawiki, and nextcloud.
The Playwright failure shape has shifted further: most former C2 roles are now better classified as **C2b/C2c/C2d**, i.e. deploy-side gaps surfacing in Playwright rather than per-spec selector drift. The C12 orphan-network signature has recurred for **gitea + matrix** because the purge primitive was partially reverted. **openproject's OOM** persists even at 6 GB. **nextcloud** flipped from "env-flake / passing locally" to a real Nextcloud-image regression. C-new clusters have been added to the [Cluster catalogue](#cluster-catalogue) for odoo, mediawiki, and nextcloud.
## Meta root cause for Bundles B, C5, C6 (and most C1?)
Two recent change-sets interact:
1. `820fb0a22 fix(purge): clear stale nginx vhosts between matrix-deploy rounds` — extends the inter-round purge with an nginx-vhost primitive. The purge is correct in intent.
2. `f102c229f refactor(inventory): split monolith into SRP submodules + variant-only mode` — the new variant-only resolver added in `cli/administration/deploy/development/inventory/variants.py` (removed again in commit `c7bacbab5`; see [inventory/](../../cli/administration/deploy/development/inventory/__init__.py) for the current package layout) built each round's include list strictly from the variant's pinned `services:` block. Roles whose variants pin `postgres: shared: false` (25 `variants.yml` files do this for variant 1) dropped their postgres dependency from the round.
1. `820fb0a22 fix(purge): clear stale nginx vhosts between matrix-deploy rounds`: extends the inter-round purge with an nginx-vhost primitive. The purge is correct in intent.
2. `f102c229f refactor(inventory): split monolith into SRP submodules + variant-only mode`: the new variant-only resolver added in `cli/administration/deploy/development/inventory/variants.py` (removed again in commit `c7bacbab5`; see [inventory/](../../cli/administration/deploy/development/inventory/__init__.py) for the current package layout) built each round's include list strictly from the variant's pinned `services:` block. Roles whose variants pin `postgres: shared: false` (25 `variants.yml` files do this for variant 1) dropped their postgres dependency from the round.
Effect in CI Run 25705903504, round 1 for any postgres consumer:
@@ -251,7 +251,7 @@ The inter-round purge tears down everything from round 0 (including svc-db-postg
Together with the fix, add a unit test under `tests/unit/cli/administration/deploy/development/inventory/` that builds a minimal matrix-deploy plan with one variant-changing app + one shared postgres consumer and asserts every round's include set contains `svc-db-postgres`.
Until this fix lands, every C1? row in the matrix stays at `❌` — a CI re-run of the same workflow will reproduce them.
Until this fix lands, every C1? row in the matrix stays at `❌`; a CI re-run of the same workflow will reproduce them.
## Verification
+1 -1
View File
@@ -15,7 +15,7 @@ In addition to the user security guidelines, administrators have additional resp
Administrators MUST enforce [2FA](https://en.wikipedia.org/wiki/Multi-factor_authentication) and SHOULD use a password manager with auto-generated passwords. We recommend [KeePass](https://en.wikipedia.org/wiki/KeePass). The KeePass database can be stored securely in your Nextcloud instance and synchronized between devices.
- **Avoid Root Logins & Plaintext Passwords**
Infinito.Nexus MUST NOT allow logging in via the root user or using simple passwords. Instead, an SSH key MUST be generated and transferred during system initialization. When executing commands as root, always use `sudo` (or, if necessary, `sudo su`—but only if you understand the risks). (More information on [SSH](https://en.wikipedia.org/wiki/Secure_Shell) and [sudo](https://en.wikipedia.org/wiki/Sudo) is available on Wikipedia.)
Infinito.Nexus MUST NOT allow logging in via the root user or using simple passwords. Instead, an SSH key MUST be generated and transferred during system initialization. When executing commands as root, always use `sudo` (or, if necessary, `sudo su`, but only if you understand the risks). (More information on [SSH](https://en.wikipedia.org/wiki/Secure_Shell) and [sudo](https://en.wikipedia.org/wiki/Sudo) is available on Wikipedia.)
- **Manage Inventories Securely**
Your inventories for running Infinito.Nexus MUST be managed in a separate repository and secured with tools such as [Ansible Vault](https://en.wikipedia.org/wiki/Encryption) 🔒. Sensitive credentials MUST NEVER be stored in plaintext; use a password file to secure these details.
+17 -17
View File
@@ -1,8 +1,8 @@
# 🧩 Ansible Plugins (`plugins/`)
# Ansible Plugins (`plugins/`) 🧩
This document is the central reference for plugin types and plugin placement in Infinito.Nexus.
## 🗂️ Current Project Layout
## Current Project Layout 🗂️
```text
plugins/
@@ -17,7 +17,7 @@ Configured plugin paths in this repository are defined in `ansible.cfg`:
- `filter_plugins = ./plugins/filter`
- `lookup_plugins = ./plugins/lookup`
## 📚 All Common Ansible Plugin Types
## All Common Ansible Plugin Types 📚
The table below summarizes common Ansible plugin types and their conventional directory names.
@@ -39,9 +39,9 @@ The table below summarizes common Ansible plugin types and their conventional di
| ✅ | `test` | `test_plugins/` | Add Jinja2 tests used in conditions and templates. |
| 📦 | `vars` | `vars_plugins/` | Load variables from custom sources. |
## 🧭 Which Plugin Type to Use
## Which Plugin Type to Use 🧭
### ⚙️ `action`
### `action` ⚙️
Use when task execution behavior itself must be changed or wrapped.
@@ -51,7 +51,7 @@ Examples:
- argument normalization before module dispatch,
- pre/post task orchestration.
### 🔍 `filter`
### `filter` 🔍
Use for pure data transformation in Jinja2 expressions.
@@ -61,7 +61,7 @@ Examples:
- deriving computed fields for templates,
- deterministic formatting helpers.
### 👉 `lookup`
### `lookup` 👉
Use when a value must be dynamically resolved at runtime.
@@ -71,39 +71,39 @@ Examples:
- resolving paths/version data,
- reading structured project data for templates/tasks.
### 📣 `callback`
### `callback` 📣
Use to hook into execution events and shape output/reporting.
### 🧭 `inventory`
### `inventory` 🧭
Use when host/group data should come from a dynamic source.
### 🧠 `strategy`
### `strategy` 🧠
Use to alter how tasks are scheduled/executed across hosts.
### 📦 `vars`
### `vars` 📦
Use to load variables from custom backends.
### 🔌🔐🐚 `connection`, `become`, `shell`
### `connection`, `become`, `shell` 🔌🔐🐚
Use to customize low-level execution and privilege mechanics.
### 💾 `cache`
### `cache` 💾
Use to persist facts/results across runs for performance and consistency.
### 🖧🌐🛰️ `cliconf`, `httpapi`, `netconf`
### `cliconf`, `httpapi`, `netconf` 🖧🌐🛰️
Use in network automation contexts requiring protocol-specific transport behavior.
### ✅ `test`
### `test` ✅
Use to add custom Jinja2 test operators for conditional logic.
## 🧪 Best Practices
## Best Practices 🧪
- Keep plugin interfaces explicit and stable.
- Keep plugin logic focused; move shared logic to `utils/`.
@@ -111,7 +111,7 @@ Use to add custom Jinja2 test operators for conditional logic.
- Prefer deterministic outputs and avoid hidden global state.
- Document any plugin-specific task arguments in the plugin docstring and README.
## 🔗 References
## References 🔗
- Ansible plugin development: <https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html>
- Ansible plugin index (all types): <https://docs.ansible.com/ansible/latest/plugins/plugins.html>
+1 -1
View File
@@ -6,7 +6,7 @@ This Ansible role ensures that [.profile](https://en.wikipedia.org/wiki/Bourne_s
## Overview
By standardizing `.profile` as the central source for environment configuration, this role ensures consistent shell behavior. It does **not** manage the contents of `.profile` itself — only guarantees that it is sourced by supported shells.
By standardizing `.profile` as the central source for environment configuration, this role ensures consistent shell behavior. It does **not** manage the contents of `.profile` itself; it only guarantees that it is sourced by supported shells.
## Purpose
+1 -1
View File
@@ -10,7 +10,7 @@ Designed for Arch Linux, this role validates configuration variables (`mount`, `
## Purpose
The purpose of this role is to provide a reliable, idempotent solution for local backups to a swappable USB drive. By automating the entire workflow—from variable checks and script deployment to service orchestration and snapshot management—it reduces manual intervention and integrates seamlessly with other Infinito.Nexus roles for comprehensive system automation.
The purpose of this role is to provide a reliable, idempotent solution for local backups to a swappable USB drive. By automating the entire workflow (from variable checks and script deployment to service orchestration and snapshot management) it reduces manual intervention and integrates seamlessly with other Infinito.Nexus roles for comprehensive system automation.
## Features
+1 -1
View File
@@ -42,4 +42,4 @@ To customize any of these values without editing role defaults:
database_collation: "latin1_swedish_ci"
```
3. Run your playbook—Ansible’s variable precedence ensures your overrides take effect.
3. Run your playbook. Ansible’s variable precedence ensures your overrides take effect.
+2 -2
View File
@@ -4,7 +4,7 @@ This guide explains how to safely upgrade a PostgreSQL Docker container from one
---
## ⚠️ Important
## Important ⚠️
PostgreSQL data directories are **not compatible across major versions**. You cannot just point a newer version to the old data volume. You must export and re-import your data.
@@ -16,7 +16,7 @@ First do a backup
Setup new Version and apply restore_postgres_databases.py.
## 🔗 References
## References 🔗
- [PostgreSQL Backup Documentation](https://www.postgresql.org/docs/current/backup-dump.html)
- [PostgreSQL Docker Image](https://hub.docker.com/_/postgres)
+1 -1
View File
@@ -13,7 +13,7 @@ It includes support for **dark mode**, **custom fonts**, and **extensive Bootstr
## Purpose
The goal of this role is to provide a **single source of truth for theming** across your infrastructure.
It makes all applications feel like part of the same ecosystem — visually and functionally.
It makes all applications feel like part of the same ecosystem, visually and functionally.
## Features
+5 -5
View File
@@ -21,11 +21,11 @@ Provide **one-stop, idempotent domain provisioning** for NGINX-based homelabs or
## Features
- **End-to-end TLS** — certificate retrieval and secure headers included.
- **Template-driven vHosts** — choose *basic* or *ws_generic* flavours (or your own).
- **Conditional OAuth2** — easily toggle authentication per application.
- **Handler-safe** — automatically triggers an NGINX reload when templates change.
- **Composable** — designed to be called repeatedly for many domains.
- **End-to-end TLS**: certificate retrieval and secure headers included.
- **Template-driven vHosts**: choose *basic* or *ws_generic* flavours (or your own).
- **Conditional OAuth2**: easily toggle authentication per application.
- **Handler-safe**: automatically triggers an NGINX reload when templates change.
- **Composable**: designed to be called repeatedly for many domains.
## Credits
@@ -7,7 +7,7 @@ Ad-hoc `community.docker.docker_network` calls or `docker network create` / `con
## When to use which 🧭
### `create.yml` — network only, no compose-up
### `create.yml`: network only, no compose-up
Use when the role still has work to do between the network creation and the compose-up.
Examples: copy `entrypoint.sh` / `healthcheck.sh` into the instance directory before the container starts, render additional configs, pre-attach other resources.
@@ -15,7 +15,7 @@ Also the right choice when pre-creating several foreign networks in a loop (e.g.
Required var:
- `network_role_id` — the role whose network to create. Name and subnet are derived from it.
- `network_role_id`: the role whose network to create. Name and subnet are derived from it.
```yaml
- include_tasks: "{{ [playbook_dir, 'roles/sys-svc-compose/tasks/utils/network/create.yml'] | path_join }}"
@@ -23,7 +23,7 @@ Required var:
network_role_id: "{{ application_id }}"
```
### `routine.yml` — wrapper with immediate compose-up
### `routine.yml`: wrapper with immediate compose-up
Use when the role is a self-contained single-container service whose only pre-condition for compose-up is its own network (e.g. `svc-db-mariadb`, `svc-db-postgres`, `svc-ai-ollama`).
The wrapper loads the docker-python deps, calls `create.yml` for the role's own network (via `application_id`), and then re-includes `sys-svc-compose` with `docker_compose_flush_handlers: true` so the compose-up fires right after.
+6 -6
View File
@@ -16,12 +16,12 @@ The goal of this role is to deliver a **hassle-free, production-ready reverse pr
## Features
- **Automatic TLS & HSTS** — integrates with the *sys-svc-webserver-https* role for certificate management.
- **Flexible vHost templates** — *basic* and *ws_generic* flavours cover standard HTTP and WebSocket applications.
- **Security headers** — sensible defaults plus optional X-Frame-Options / CSP based on application settings.
- **WebSocket & HTTP/2 aware** — upgrades, keep-alive tuning, and gzip already configured.
- **OAuth2 gating** — drop-in support when *web-app-oauth2-proxy* is present.
- **Modular includes** — headers, locations, and global snippets are factored for easy extension.
- **Automatic TLS & HSTS**: integrates with the *sys-svc-webserver-https* role for certificate management.
- **Flexible vHost templates**: *basic* and *ws_generic* flavours cover standard HTTP and WebSocket applications.
- **Security headers**: sensible defaults plus optional X-Frame-Options / CSP based on application settings.
- **WebSocket & HTTP/2 aware**: upgrades, keep-alive tuning, and gzip already configured.
- **OAuth2 gating**: drop-in support when *web-app-oauth2-proxy* is present.
- **Modular includes**: headers, locations, and global snippets are factored for easy extension.
## Credits
+1 -1
View File
@@ -11,7 +11,7 @@ This Ansible role configures the OpenSSH daemon (`sshd`) by deploying a template
- Automatically reloads and restarts the SSH service via a systemd handler
- Uses a `run_once_sys_svc_sshd` fact to ensure idempotent execution
Key variable: `SYS_SVC_SSHD_PASSWORD_AUTHENTICATION` (default: `false`) — enables password authentication when explicitly required by controlled environments such as local E2E test targets.
Key variable: `SYS_SVC_SSHD_PASSWORD_AUTHENTICATION` (default: `false`) enables password authentication when explicitly required by controlled environments such as local E2E test targets.
## Features
+1 -1
View File
@@ -21,7 +21,7 @@ When you apply **sys-svc-webserver-https**, it will:
3. **Deploy** the Let’s Encrypt challenge-and-redirect snippet from `sys-svc-letsencrypt`.
4. **Reload** NGINX automatically when any template changes.
All tasks are idempotent—once your certificates are in place and your configuration is set, Ansible will skip unchanged steps on subsequent runs.
All tasks are idempotent. Once your certificates are in place and your configuration is set, Ansible will skip unchanged steps on subsequent runs.
---
+1 -1
View File
@@ -1,4 +1,4 @@
# Agent Instructions — test-e2e-playwright
# Agent Instructions: test-e2e-playwright
This role is the **shared Playwright runner** for all end-to-end tests in the repository.
Changes here affect every application role that uses it.
+1 -1
View File
@@ -20,7 +20,7 @@ The purpose of tthis role is to simplify the deployment and management of a pers
## Features
- **Unified Navigation**: Central menu bar with dynamic categories for all registered applications.
- **Customizable Tiles**: Showcase applications with title, description, and icons—fully configurable via YAML.
- **Customizable Tiles**: Showcase applications with title, description, and icons, fully configurable via YAML.
- **Responsive Design**: Optimized for desktop, tablet & mobile, built on Bootstrap.
- **Interactive Icons**: Automatic integration of Simple Icons for popular brands and tools.
- **Seamless IFrame Embedding**: Launch apps directly within the UI or open in new tabs.
+1 -1
View File
@@ -6,7 +6,7 @@
## Overview
Fediwall is a static, single-page web application — no backend, no database, no user accounts. Configuration lives in a `wall-config.json` next to `index.html` and can be overridden per-visitor through URL parameters.
Fediwall is a static, single-page web application with no backend, no database, and no user accounts. Configuration lives in a `wall-config.json` next to `index.html` and can be overridden per-visitor through URL parameters.
This role bakes the upstream release artefact into a small `nginx:alpine` image and supports **multiple walls per deployment**: every entry under [`meta/services.yml.fediwall.walls`](meta/services.yml) materializes as its own path under `https://fediwall.<DOMAIN_PRIMARY>/<slug>/` with its own baked-in `wall-config.json`. The root `/` shows a link list of all configured walls.
+1 -1
View File
@@ -1,3 +1,3 @@
# Todos
- Implement multi-wall runtime upload behind OIDC — see [docs/requirements/016-fediwall-multi-wall-runtime-upload.md](../../docs/requirements/016-fediwall-multi-wall-runtime-upload.md)
- Implement multi-wall runtime upload behind OIDC. See [docs/requirements/016-fediwall-multi-wall-runtime-upload.md](../../docs/requirements/016-fediwall-multi-wall-runtime-upload.md)
+5 -5
View File
@@ -2,7 +2,7 @@
## Description
**Flowise** is a visual builder for AI workflows. Create, test, and publish chains that combine LLMs, your documents, tools, and vector search—without writing glue code.
**Flowise** is a visual builder for AI workflows. Create, test, and publish chains that combine LLMs, your documents, tools, and vector search, without writing glue code.
## Overview
@@ -18,10 +18,10 @@ Users design flows on a drag-and-drop canvas (LLM, RAG, tools, webhooks), test t
## Further Resources
* Flowise — [https://flowiseai.com](https://flowiseai.com)
* Qdrant — [https://qdrant.tech](https://qdrant.tech)
* LiteLLM — [https://www.litellm.ai](https://www.litellm.ai)
* Ollama — [https://ollama.com](https://ollama.com)
* Flowise: [flowiseai.com](https://flowiseai.com)
* Qdrant: [qdrant.tech](https://qdrant.tech)
* LiteLLM: [litellm.ai](https://www.litellm.ai)
* Ollama: [ollama.com](https://ollama.com)
## Credits
+1 -1
View File
@@ -6,7 +6,7 @@ Empower your decentralized social networking with Friendica, a platform designed
## Overview
This role deploys Friendica using Docker, managing the Friendica application container alongside a central MariaDB instance. It provides tools for full resets, manual and automatic database reinitialization, email and general configuration debugging, and autoinstall processes—all to ensure your Friendica installation remains reliable and easy to maintain.
This role deploys Friendica using Docker, managing the Friendica application container alongside a central MariaDB instance. It provides tools for full resets, manual and automatic database reinitialization, email and general configuration debugging, and autoinstall processes, all to ensure your Friendica installation remains reliable and easy to maintain.
For detailed administration procedures, please refer to the [Administration.md](./Administration.md) file.
+1 -1
View File
@@ -6,7 +6,7 @@ Dive into a world of rhythm and sound with [Funkwhale](https://www.funkwhale.aud
## Overview
This role deploys Funkwhale using Docker. It orchestrates multiple services—including the API, Frontend, Celery Worker, Celery Beat, and Typesense—integrating with centralized PostgreSQL and Redis services for a fully containerized music sharing experience.
This role deploys Funkwhale using Docker. It orchestrates multiple services (including the API, Frontend, Celery Worker, Celery Beat, and Typesense) integrating with centralized PostgreSQL and Redis services for a fully containerized music sharing experience.
## Features
+4 -4
View File
@@ -1,4 +1,4 @@
# Hugo — Administration
# Hugo: Administration
## Force a rebuild
@@ -41,9 +41,9 @@ make exec CMD="docker compose -f /opt/docker/web-app-hugo/docker-compose.yml bui
Common causes:
- **`hugo` reports unknown content / module errors** — the upstream repository moved a directory or a Hugo Module. Pin to a known-good tag in `source_version`.
- **`hugo` reports template execution errors** — the theme's templates are incompatible with the bundled Hugo binary. Bump `services.hugo.builder_version` (e.g. to a newer `exts-<version>` tag) or pin the source repo to a tag known to build with the current Hugo.
- **Module fetch fails (`go.mod` resolution)** — Hugo modules pull from `proxy.golang.org`. Network egress to that host must be allowed by the local firewall / proxy.
- **`hugo` reports unknown content / module errors**: the upstream repository moved a directory or a Hugo Module. Pin to a known-good tag in `source_version`.
- **`hugo` reports template execution errors**: the theme's templates are incompatible with the bundled Hugo binary. Bump `services.hugo.builder_version` (e.g. to a newer `exts-<version>` tag) or pin the source repo to a tag known to build with the current Hugo.
- **Module fetch fails (`go.mod` resolution)**: Hugo modules pull from `proxy.golang.org`. Network egress to that host must be allowed by the local firewall / proxy.
## Rotating the Hugo binary
+1 -1
View File
@@ -3,7 +3,7 @@
- Implement working logout for all applications
- Implement general logout button
## Req 019 rollout — deploy gate
## Req 019 rollout: deploy gate
- During the autonomous req 019 rollout, `make deploy-fresh-purged-apps APPS=web-app-keycloak FULL_CYCLE=true` failed with two unrelated assertions:
- `kcadm.sh config credentials` failed during the keycloak admin login phase (likely transient or env-scoped).
+1 -1
View File
@@ -2,7 +2,7 @@
## Description
Elevate your email marketing with Listmonk, a high-performance, self-hosted newsletter and mailing list manager featuring a modern, intuitive dashboard. Listmonk empowers you with advanced analytics, efficient subscriber segmentation, and streamlined campaign management—all configurable via a flexible TOML configuration file.
Elevate your email marketing with Listmonk, a high-performance, self-hosted newsletter and mailing list manager featuring a modern, intuitive dashboard. Listmonk empowers you with advanced analytics, efficient subscriber segmentation, and streamlined campaign management, all configurable via a flexible TOML configuration file.
## Overview
+6 -6
View File
@@ -13,12 +13,12 @@ The app runs as a single Docker container and requires no additional database or
## Features
- **Self-hosted LittleJS environment** — run LittleJS demos and games under your own domain.
- **Example browser integration** — direct access to the original LittleJS example browser.
- **Tile-based launcher UI** — dynamically renders a catalog from the `exampleList` definition.
- **Quick links for games** — navbar entries for selected games (e.g. platformer, pong, space shooter).
- **Bootstrap-styled interface** — clean, minimalistic, and responsive layout.
- **Docker-ready** — fully integrated into the Infinito.Nexus Docker stack.
- **Self-hosted LittleJS environment**: run LittleJS demos and games under your own domain.
- **Example browser integration**: direct access to the original LittleJS example browser.
- **Tile-based launcher UI**: dynamically renders a catalog from the `exampleList` definition.
- **Quick links for games**: navbar entries for selected games (e.g. platformer, pong, space shooter).
- **Bootstrap-styled interface**: clean, minimalistic, and responsive layout.
- **Docker-ready**: fully integrated into the Infinito.Nexus Docker stack.
## Further Resources
+4 -4
View File
@@ -1,4 +1,4 @@
# 📖 **How to Migrate Mailboxes to a New Domain in Mailu**
# **How to Migrate Mailboxes to a New Domain in Mailu** 📖
When changing the primary email domain (e.g., from `cymais.cloud` to `infinito.nexus`), it is **not enough** to simply rename mailbox directories on disk. Mailu manages domain and user records in its internal database, and Dovecot maintains index files inside each Maildir. A blind rename will lead to login failures, rejected mail, or corrupted mail indices.
@@ -6,7 +6,7 @@ This guide explains the **safe procedure** for migrating user mailboxes to a new
---
## 🔴 Why renaming folders directly does not work
## Why renaming folders directly does not work 🔴
* Mailu keeps **domains, users, and aliases** in the `admin_data` database. If you rename folders only, Mailu will not recognize the new accounts.
* Dovecot generates `.dovecot.index*` and `dovecot-uidlist` files in each mailbox. These must be rebuilt when moving mailboxes; otherwise, users may see missing or broken mail.
@@ -14,7 +14,7 @@ This guide explains the **safe procedure** for migrating user mailboxes to a new
---
## ✅ Correct migration procedure
## Correct migration procedure ✅
### 1. Add the new domain and users in Mailu
@@ -82,7 +82,7 @@ This maps all `@cymais.cloud` addresses to their equivalents under `@infinito.ne
---
## 💡 Summary
## Summary 💡
* Do **not** just rename mailbox folders.
* Always create the new domain and user in Mailu first.
+6 -6
View File
@@ -3,7 +3,7 @@
## Description
**Mini-QR** is a lightweight, self-hosted web application for generating QR codes instantly and privately.
It provides a minimal and elegant interface to convert any text, URL, or message into a QR code — directly in your browser, without external tracking or dependencies.
It provides a minimal and elegant interface to convert any text, URL, or message into a QR code directly in your browser, without external tracking or dependencies.
## Overview
@@ -13,11 +13,11 @@ The app runs as a single Docker container and requires no database or backend se
## Features
- **Instant QR code creation** — simply type or paste your content.
- **Privacy-friendly** — all generation happens client-side; no data leaves your server.
- **Open Source** — fully auditable and modifiable for custom integrations.
- **Responsive Design** — optimized for both desktop and mobile devices.
- **Docker-ready** — can be deployed in seconds using the official image.
- **Instant QR code creation**: simply type or paste your content.
- **Privacy-friendly**: all generation happens client-side; no data leaves your server.
- **Open Source**: fully auditable and modifiable for custom integrations.
- **Responsive Design**: optimized for both desktop and mobile devices.
- **Docker-ready**: can be deployed in seconds using the official image.
## Further Resources
+3 -3
View File
@@ -2,7 +2,7 @@
## Description
**MinIO** is an S3-compatible object storage service for files, media, backups, and AI artifacts—self-hosted for performance and control.
**MinIO** is an S3-compatible object storage service for files, media, backups, and AI artifacts, self-hosted for performance and control.
## Overview
@@ -18,8 +18,8 @@ Applications that speak “S3” (Pixelfed, Mastodon, Nextcloud, Flowise, etc.)
## Further Resources
* MinIO — [https://www.min.io](https://www.min.io)
* AWS S3 (API background) — [https://aws.amazon.com/s3](https://aws.amazon.com/s3)
* MinIO: [www.min.io](https://www.min.io)
* AWS S3 (API background): [aws.amazon.com/s3](https://aws.amazon.com/s3)
## Credits
+1 -1
View File
@@ -24,7 +24,7 @@ This role builds its own Moodle image from upstream Moodle source on top of the
## Further Resources
- [Moodle Official Website](https://moodle.org/)
- [Moodle Developer Documentation — Docker images](https://moodledev.io/general/app/development/setup/docker-images)
- [Moodle Developer Documentation: Docker images](https://moodledev.io/general/app/development/setup/docker-images)
- [moodlehq/moodle-docker](https://github.com/moodlehq/moodle-docker) (extension list reference)
## Credits
+1 -1
View File
@@ -2,7 +2,7 @@
## Description
Elevate your collaboration with Nextcloud, a vibrant self-hosted cloud solution designed for dynamic file sharing, seamless communication, and effortless teamwork. Nextcloud offers a full suite of integrated tools—including LDAP and OIDC authentication, Redis caching, and automated plugin management via OCC—to empower a secure, extensible, and production-ready cloud environment.
Elevate your collaboration with Nextcloud, a vibrant self-hosted cloud solution designed for dynamic file sharing, seamless communication, and effortless teamwork. Nextcloud offers a full suite of integrated tools (including LDAP and OIDC authentication, Redis caching, and automated plugin management via OCC) to empower a secure, extensible, and production-ready cloud environment.
## Overview
+1 -1
View File
@@ -4,7 +4,7 @@ IAM(Identity and Access Management) is setup via Keycloak and LDAP.
## OpenID Connect (OIDC) Support 🔐
OIDC is supported in this role—for example, via **Keycloak**. OIDC-specific tasks are included when enabled, allowing integration of external authentication providers seamlessly.
OIDC is supported in this role (for example, via **Keycloak**). OIDC-specific tasks are included when enabled, allowing integration of external authentication providers seamlessly.
### Verify OIDC Configuration
+3 -3
View File
@@ -1,6 +1,6 @@
# Add LDAP Users Manually for Immediate Sharing
In a default Nextcloud + LDAP setup, user accounts are only created in the internal Nextcloud database **after their first login**. This means that even if a user exists in LDAP, they **cannot receive shared files or folders** until they have logged in at least once—or are manually synchronized.
In a default Nextcloud + LDAP setup, user accounts are only created in the internal Nextcloud database **after their first login**. This means that even if a user exists in LDAP, they **cannot receive shared files or folders** until they have logged in at least once, or are manually synchronized.
To make LDAP users available for sharing **without requiring initial login**, follow these steps:
@@ -28,7 +28,7 @@ docker exec -u www-data nextcloud php occ ldap:check-user --update <username>
docker exec -u www-data nextcloud php occ ldap:check-user --update viktoriakaffanke
```
Once executed, the user becomes fully available in the system—for sharing, group membership, and permissions—even without logging in.
Once executed, the user becomes fully available in the system (for sharing, group membership, and permissions) even without logging in.
## 3. Synchronize All Known Users (Optional)
@@ -42,7 +42,7 @@ This step is especially useful after modifying LDAP attributes or group membersh
## Playwright / E2E: biber First-Login Caveat
When a Playwright scenario exercises the OIDC/LDAP path as `biber` (or any non-administrator persona) against a freshly provisioned Nextcloud stack, the very first login can fail or stall because the Nextcloud internal account has not yet been materialized from LDAP — exactly the situation described above. On subsequent logins the account exists and the flow behaves normally.
When a Playwright scenario exercises the OIDC/LDAP path as `biber` (or any non-administrator persona) against a freshly provisioned Nextcloud stack, the very first login can fail or stall because the Nextcloud internal account has not yet been materialized from LDAP; this matches the situation described above. On subsequent logins the account exists and the flow behaves normally.
If a brand-new deployment must pass the `biber` end-to-end scenario on its first run, either:
+2 -2
View File
@@ -18,8 +18,8 @@ End users access a web page, pick a model, and start chatting. Conversations rem
## Further Resources
* Open WebUI — [https://openwebui.com](https://openwebui.com)
* Ollama — [https://ollama.com](https://ollama.com)
* Open WebUI: [openwebui.com](https://openwebui.com)
* Ollama: [ollama.com](https://ollama.com)
## Credits
+1 -1
View File
@@ -21,7 +21,7 @@ With automated setup, update handling, variable management, and plugin-based aut
## Further Resources
* [Shopware Official Website](https://www.shopware.com/en/) <!-- nocheck: url — redirect loop on probe, site is alive when visited interactively -->
* [Shopware Official Website](https://www.shopware.com/en/) <!-- nocheck: url; redirect loop on probe, site is alive when visited interactively -->
* [Shopware Developer Documentation](https://developer.shopware.com/)
* [Shopware Store (Plugins)](https://store.shopware.com/en/)
+1 -1
View File
@@ -2,7 +2,7 @@
## Description
Snipe‑IT is an open‑source asset management system designed to streamline hardware and software inventory tracking. This deployment provides an automated, containerized solution using Docker Compose, centralized MariaDB database integration, and secure, configurable environment settings—including robust SMTP email support and pending SAML authentication enhancements.
Snipe‑IT is an open‑source asset management system designed to streamline hardware and software inventory tracking. This deployment provides an automated, containerized solution using Docker Compose, centralized MariaDB database integration, and secure, configurable environment settings, including robust SMTP email support and pending SAML authentication enhancements.
## Overview
+1 -1
View File
@@ -30,7 +30,7 @@ By automating the Sphinx build process and containerized deployment, this role m
Once the build is complete, the generated HTML is served through a lightweight HTTP server. Health checks are in place to ensure the service is running reliably.
- **Consistent Deployment Workflow:**
The entire process—from pulling the repository to serving the final output—is automated. This reduces human error and makes it easy to update or roll back documentation builds.
The entire process (from pulling the repository to serving the final output) is automated. This reduces human error and makes it easy to update or roll back documentation builds.
## Further Resources
+1 -1
View File
@@ -2,7 +2,7 @@
## Description
Empower your organization with **XWiki**, an open-source enterprise wiki and knowledge management platform. XWiki provides powerful collaboration features, structured content management, and extensibility through applications and plugins—all under your control.
Empower your organization with **XWiki**, an open-source enterprise wiki and knowledge management platform. XWiki provides powerful collaboration features, structured content management, and extensibility through applications and plugins, all under your control.
## Overview
+1 -1
View File
@@ -20,7 +20,7 @@ This containerized YOURLS solution is built on robust Docker Compose and Ansible
Seamlessly connect to a MariaDB instance for consistent, reliable data storage and management.
- **Configurable Environment:**
Easily customize your YOURLS instance through environment variables—set your site URL, admin credentials, and more.
Easily customize your YOURLS instance through environment variables: set your site URL, admin credentials, and more.
- **Secure and Scalable:**
Benefit from container isolation and reproducible deployments that ensure your service is both secure and scalable.
+4 -4
View File
@@ -43,10 +43,10 @@ Typical use cases:
## Further Resources
* Coturn Project — [https://github.com/coturn/coturn](https://github.com/coturn/coturn)
* Coturn Wiki — [https://github.com/coturn/coturn/wiki](https://github.com/coturn/coturn/wiki)
* TURN on Wikipedia — [https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT)
* STUN on Wikipedia — [https://en.wikipedia.org/wiki/STUN](https://en.wikipedia.org/wiki/STUN)
* Coturn Project: [github.com/coturn/coturn](https://github.com/coturn/coturn)
* Coturn Wiki: [github.com/coturn/coturn/wiki](https://github.com/coturn/coturn/wiki)
* TURN on Wikipedia: [en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT)
* STUN on Wikipedia: [en.wikipedia.org/wiki/STUN](https://en.wikipedia.org/wiki/STUN)
## Credits
+1 -1
View File
@@ -1,6 +1,6 @@
# Container Scripts
This is the SPOT for container-side helpers under `scripts/container/`.
This directory holds container-side helpers grouped by area.
Use the README in each child directory as the canonical entry point for that area.
For the canonical Make target index that invokes these helpers, see [make.md](../../docs/contributing/tools/make.md).
+1 -1
View File
@@ -1,5 +1,5 @@
# Git Workflow Helpers 🔐
This is the SPOT for Git workflow helper scripts under `scripts/git/`.
This directory holds the Git workflow helper scripts.
These helpers own the Git operations that the Claude sandbox denies, so they run under the operator's control with access to `~/.gnupg` and interactive `pinentry`.
For the canonical command index, see [make.md](../../docs/contributing/tools/make.md).
+1 -1
View File
@@ -5,7 +5,7 @@ This directory contains scripts that install and bootstrap host-side tooling use
- 📦 Install Python, Ansible, and lint dependencies for supported environments
- 🐧 Install ad-hoc APT packages for CI and workflow jobs via `apt.sh`
- 📦 Build and install distro package metadata via `package.sh` (Arch/Debian/Ubuntu/Fedora)
- 🏖️ Install OS-level Claude Code sandbox dependencies (`bubblewrap`, `socat`) via `sandbox.sh` — covers Debian, Ubuntu, Fedora, CentOS/RHEL/Rocky/Alma, and Arch; also exposed as `make agent-install`
- 🏖️ Install OS-level Claude Code sandbox dependencies (`bubblewrap`, `socat`) via `sandbox.sh`, covering Debian, Ubuntu, Fedora, CentOS/RHEL/Rocky/Alma, and Arch; also exposed as `make agent-install`
- 🐍 Bootstrap virtual environments and editable project dependencies
- 🛠️ Keep host preparation separate from lint, build, and test execution
+1 -1
View File
@@ -10,7 +10,7 @@ Examples in this folder and its subfolders:
- `repository/name.sh` resolves the concrete repository name once for callers
- `repository/owner.sh` resolves the concrete repository owner once for callers
- `distros.sh` is the SPOT for the canonical distro list used by workflows
- `distros.sh` holds the canonical distro list used by workflows
- See [pr/README.md](pr/README.md) for the Pull Request-specific resolvers.
The scope of this folder is value resolution.
+1 -1
View File
@@ -1,6 +1,6 @@
# System Scripts
This is the SPOT for system-facing helpers under `scripts/system/`.
This directory holds system-facing helpers grouped by area.
Use the README in each child directory as the canonical entry point for that area.
For the canonical Make target index that invokes these helpers, see [make.md](../../docs/contributing/tools/make.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# Network Administration
This is the SPOT for network helpers under `scripts/system/network/`.
This directory holds network helpers grouped by area.
Use the child README files for the DNS, Docker, and IPv6 details.
For the canonical Make target index that invokes these helpers, see [make.md](../../../docs/contributing/tools/make.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# DNS Helpers
This is the SPOT for DNS helpers under `scripts/system/network/dns/`.
This directory holds DNS helpers used during development and environment preparation.
The folder contains the local name-resolution setup and teardown logic used during development and environment preparation.
For the canonical Make target index that invokes these helpers, see [make.md](../../../../docs/contributing/tools/make.md).
+5 -5
View File
@@ -1,4 +1,4 @@
# 🌐 DNS Setup Scripts
# DNS Setup Scripts 🌐
This directory contains platform-specific scripts for setting up local DNS resolution for the `infinito.example` domain (and subdomains) on the development machine.
@@ -6,9 +6,9 @@ All scripts map `*.infinito.example` → `127.0.0.1` so that local deployments a
---
## 📄 Scripts
## Scripts 📄
### 🐧 `linux.sh`
### `linux.sh` 🐧
General-purpose DNS setup for Linux systems. Supports multiple strategies depending on the environment:
@@ -18,7 +18,7 @@ General-purpose DNS setup for Linux systems. Supports multiple strategies depend
Shared helper functions are sourced from [`../common.sh`](../common.sh).
### 🪟 `wsl.sh`
### `wsl.sh` 🪟
WSL2-specific DNS setup. Only executes when running inside a WSL2 environment (detected via `/proc/version`). Configures:
@@ -29,7 +29,7 @@ WSL2-specific DNS setup. Only executes when running inside a WSL2 environment (d
---
## 🔗 Related
## Related 🔗
- [Makefile Commands](../../../../../docs/contributing/tools/make.md). canonical Make target index for this area
- [`../remove.sh`](../remove.sh). teardown script that undoes the DNS configuration
+1 -1
View File
@@ -1,6 +1,6 @@
# Docker Network Helpers
This is the SPOT for Docker-related network helpers under `scripts/system/network/docker/`.
This directory holds Docker-related network helpers.
For the canonical Make target index that invokes these helpers, see [make.md](../../../../docs/contributing/tools/make.md).
## Entry Points
+1 -1
View File
@@ -1,6 +1,6 @@
# IPv6 Helpers
This is the SPOT for IPv6 helpers under `scripts/system/network/ipv6/`.
This directory holds IPv6 enable and restore helpers for local development.
For the canonical Make target index that invokes these helpers, see [make.md](../../../../docs/contributing/tools/make.md).
For the generic stack refresh helper used by `make refresh`, see [../docker/README.md](../docker/README.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# System Purge
This is the SPOT for host and system cleanup helpers under `scripts/system/purge/`.
This directory holds host and system cleanup helpers.
For container-local cleanup helpers, see [../../container/purge/README.md](../../container/purge/README.md).
For the canonical Make target index that invokes these helpers, see [make.md](../../../docs/contributing/tools/make.md).
+3 -3
View File
@@ -1,13 +1,13 @@
# ⚙️ systemd Administration Scripts
# systemd Administration Scripts ⚙️
This directory contains scripts for managing systemd across different platforms and environments used in Infinito Nexus development.
For the canonical Make target index that invokes these helpers, see [make.md](../../../docs/contributing/tools/make.md).
---
## 📁 Subdirectories
## Subdirectories 📁
### ✅ [`enable/`](enable/)
### [`enable/`](enable/) ✅
Platform-specific scripts for enabling systemd where it is not active by default.
+4 -4
View File
@@ -1,13 +1,13 @@
# ✅ systemd. Enable
# systemd. Enable ✅
This directory contains platform-specific scripts for enabling systemd in environments where it is not active by default.
For the canonical Make target index that invokes these helpers, see [make.md](../../../../docs/contributing/tools/make.md).
---
## 📄 Scripts
## Scripts 📄
### 🪟 `wsl2.sh`
### `wsl2.sh` 🪟
Enables systemd as the WSL2 init system by writing `systemd=true` into `/etc/wsl.conf`.
@@ -20,6 +20,6 @@ Enables systemd as the WSL2 init system by writing `systemd=true` into `/etc/wsl
---
## 🔗 Related
## Related 🔗
- [`../`](../). systemd administration scripts
+4 -4
View File
@@ -1,13 +1,13 @@
# 🔒 TLS Administration Scripts
# TLS Administration Scripts 🔒
This directory contains scripts for managing TLS/PKI concerns in the Infinito Nexus development environment.
For the canonical Make target index that invokes these helpers, see [make.md](../../../docs/contributing/tools/make.md).
---
## 📁 Subdirectories
## Subdirectories 📁
### 🔐 [`trust/`](trust/)
### [`trust/`](trust/) 🔐
Scripts for installing the Infinito self-signed Root CA into the trust stores of the developer's machine.
@@ -16,6 +16,6 @@ Scripts for installing the Infinito self-signed Root CA into the trust stores of
---
## 🔗 Related
## Related 🔗
- `roles/sys-ca-selfsigned/`. role that generates the self-signed Root CA inside the container
+5 -5
View File
@@ -1,4 +1,4 @@
# 🔐 CA Trust Scripts
# CA Trust Scripts 🔐
This directory contains platform-specific scripts for trusting the Infinito Root CA certificate on the developer's machine.
For the canonical Make target index that invokes these helpers, see [make.md](../../../../docs/contributing/tools/make.md).
@@ -7,9 +7,9 @@ All scripts extract the Root CA from a running Infinito container and install it
---
## 📄 Scripts
## Scripts 📄
### 🐧 `linux.sh`
### `linux.sh` 🐧
Trusts the Infinito Root CA on the **Linux host system**.
@@ -19,7 +19,7 @@ Trusts the Infinito Root CA on the **Linux host system**.
Requires `INFINITO_CONTAINER` to be set to the name of the running container.
### 🪟 `wsl2.sh`
### `wsl2.sh` 🪟
Trusts the Infinito Root CA on **Windows** from inside a WSL2 environment. Only executes when running on WSL2 (detected via `/proc/version`).
@@ -30,7 +30,7 @@ Trusts the Infinito Root CA on **Windows** from inside a WSL2 environment. Only
---
## 🔗 Related
## Related 🔗
- [`../`](../). TLS administration scripts
- `roles/sys-ca-selfsigned/`. generates the self-signed Root CA inside the container
+1 -1
View File
@@ -1,6 +1,6 @@
# Act Deploy Scripts
This is the SPOT for executable act deploy flows under `scripts/tests/deploy/act/`.
This directory holds the executable act deploy flows under `scripts/tests/deploy/act/`.
For local deploy flows, use [../local/README.md](../local/README.md).
For the canonical Make target index that invokes these helpers, see [make.md](../../../../docs/contributing/tools/make.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# CI Deploy Scripts
This is the SPOT for executable CI deploy flows under `scripts/tests/deploy/ci/`.
This directory holds the executable CI deploy flows under `scripts/tests/deploy/ci/`.
For local deploy flows, use [../local/README.md](../local/README.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# Local Deploy Test Scripts 🧪
This file is the SPOT for local helpers outside `deploy/`.
This file describes the local helpers outside `deploy/`.
For executable local deploy flows, use [deploy/README.md](deploy/README.md).
For inventory reset helpers, use [reset/README.md](reset/README.md).
For cleanup helpers, use [purge/README.md](purge/README.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# Local Deploy Scripts
This is the SPOT for executable local deploy flows under `scripts/tests/deploy/local/deploy/`.
This directory holds the executable local deploy flows under `scripts/tests/deploy/local/deploy/`.
For other local helpers, use [../README.md](../README.md).
For the canonical Make target index that invokes these helpers, see [make.md](../../../../../docs/contributing/tools/make.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# Local Exec Scripts
This is the SPOT for container execution helpers under `scripts/tests/deploy/local/exec/`.
This directory holds the container execution helpers under `scripts/tests/deploy/local/exec/`.
For other local helpers, use [../README.md](../README.md).
For the canonical Make target index that invokes these helpers, see [make.md](../../../../../docs/contributing/tools/make.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# Local Purge Scripts
This is the SPOT for cleanup helpers under `scripts/tests/deploy/local/purge/`.
This directory holds the cleanup helpers under `scripts/tests/deploy/local/purge/`.
For other local helpers, use [../README.md](../README.md).
For the canonical Make target index that invokes these helpers, see [make.md](../../../../../docs/contributing/tools/make.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# Local Inventory Reset
This is the SPOT for inventory refresh helpers under `scripts/tests/deploy/local/reset/`.
This directory holds the inventory refresh helpers under `scripts/tests/deploy/local/reset/`.
For other local helpers, use [../README.md](../README.md).
For the canonical Make target index that invokes these helpers, see [make.md](../../../../../docs/contributing/tools/make.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# Role Spot Tests 🎯
Integration spot-checks pinned to specific roles — narrow assertions that catch regressions in a single role's templates, vars, or task layout.
Integration spot-checks pinned to specific roles, with narrow assertions that catch regressions in a single role's templates, vars, or task layout.
Tests in this directory MUST only cover role-specific invariants that do not generalize across roles. Use the filename `test_<role>_<aspect>.py`. Rules that should hold across every role MUST live in the corresponding topical cluster (e.g., [`config/`](../config/), [`compose/`](../../infrastructure/compose/), [`handlers/`](../../ansible/handlers/)).
+1 -1
View File
@@ -7,7 +7,7 @@ Tests in this directory MUST only cover variable definition / consumption checks
- `test_role_and_group_vars_used.py` scans top-level keys of `roles/<role>/{vars,defaults}/main.yml` and `group_vars/**/*.yml` against every project `.yml` / `.yaml` / `.j2` file. The suppression rule key is `unused-var` (see [suppression.md](../../../../docs/contributing/actions/testing/suppression.md)).
- `test_vars_usage_in_yaml.py` scans task-level `vars:` blocks against the same consumer set. The two scanners never overlap because their declaration shapes differ.
- `test_no_jinja_default_on_spot_path.py` forbids `| default(...)` on Jinja expressions whose path resolves via `group_vars/all/*.yml`. Those keys are loaded unconditionally, so the default is dead code at best and silent SPOT decoupling at worst. Per-line suppression: `# nocheck: spot-default`.
- `test_dotted_path_keys_exist.py` validates that every Jinja dotted path (`FOO.BAR.BAZ`) whose head is in `group_vars/all/` or the consuming role's own `vars`/`defaults` resolves to an existing key in that dict tree — catches typos / renamed / removed sub-keys. Per-line suppression: `# nocheck: dotted-path`.
- `test_dotted_path_keys_exist.py` validates that every Jinja dotted path (`FOO.BAR.BAZ`) whose head is in `group_vars/all/` or the consuming role's own `vars`/`defaults` resolves to an existing key in that dict tree, catching typos / renamed / removed sub-keys. Per-line suppression: `# nocheck: dotted-path`.
Both tests route every read through the project caches (`utils.cache.files`, `utils.cache.yaml`); contributors adding a new variable scanner MUST do the same instead of calling `yaml.safe_load*` or `open()` directly.