feat(design): leave the design queue by switching the design service off

The design queue skipped a hand-kept UI_LESS_ROLES list in
cli/meta/roles/design. The decision now lives in each role: a web-app- or
web-svc- role without a UI to design sets design.enabled: false in its
meta/services.yml, and design_disabled() reads that flag. web-svc-design,
the provider, stays out by its entity name.

A new lint, test_design_off_reason.py, requires a design entry in every
web-app- and web-svc- role and, where the entry is switched off, a
"# nocheck: design-off  Reason: <text>" on the design: line or the comment
lines directly above it. The rule is in the suppression catalog, and
requirement 037 decision 13 describes it.

Twelve roles switch design off with a reason checked against their code:
asset and simpleicons were already off; file, onlyoffice, bridgy-fed and
mig were on and no longer receive the shared CSS and design script; cdn,
collabora, coturn, legal, mirror and xmpp had no entry. The four that were
on drop their bond and their design variant overrides, which the
disabled-bond and variants-static-override lints reject on a literal false.
The queue lists 77 candidates; HEAD's list also counted bridgy-fed, mig,
collabora and onlyoffice. Cosmos diagrams and the integration matrix are
regenerated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5.5 committed 2026-10-12 17:09:33 +02:00
1 parent fb12d81ca2
commit 25a1f08ab7
33 files changed
+227 -84

No files matched your search

+26 -21
View File
@@ -1,8 +1,8 @@
"""Work queue of the corporate-design routine.
A role is a candidate when it is a ``web-app-`` or ``web-svc-`` role with a web
UI of its own and its lifecycle sits inside the tested envelope. Every
candidate carries exactly one state:
A role is a candidate when it is a ``web-app-`` or ``web-svc-`` role, its
lifecycle sits inside the tested envelope and its ``design`` service is not
switched off with ``enabled: false``. Every candidate carries exactly one state:
* ``review``: its design spec is untracked or modified and waits for approval.
* ``new``: HEAD carries no design spec for it.
@@ -45,19 +45,7 @@ BASE_PATHS: tuple[str, ...] = (
"utils/design/palette.py",
)
UI_PREFIXES: tuple[str, ...] = ("web-app-", "web-svc-")
UI_LESS_ROLES: frozenset[str] = frozenset(
{
"web-svc-asset",
"web-svc-cdn",
"web-svc-coturn",
"web-svc-design",
"web-svc-file",
"web-svc-legal",
"web-svc-mirror",
"web-svc-simpleicons",
"web-svc-xmpp",
}
)
DESIGN_ENTITY = "design"
STATE_REVIEW = "review"
STATE_NEW = "new"
@@ -324,9 +312,25 @@ def _primary_version(services: object, role: str) -> str | None:
return str(entry["version"]).strip() or None
def design_disabled(role_dir: Path) -> bool:
"""Whether a role switches its ``design`` service off.
Args:
role_dir: Directory of the role.
Returns:
``True`` when ``meta/services.yml`` sets ``design.enabled`` to the
literal ``false``; a templated or missing flag counts as on.
"""
entry = read_yaml_file(role_dir / ROLE_FILE_META_SERVICES).get(DESIGN_ENTITY)
return isinstance(entry, dict) and entry.get("enabled") is False
def _is_candidate(role_dir: Path, envelope: frozenset[str]) -> bool:
role = role_dir.name
if not role.startswith(UI_PREFIXES) or role in UI_LESS_ROLES:
if not role.startswith(UI_PREFIXES) or get_entity_name(role) == DESIGN_ENTITY:
return False
if design_disabled(role_dir):
return False
return get_role_lifecycle(role_dir, role_name=role) in envelope
@@ -338,10 +342,11 @@ def candidate_roles(roles_dir: Path) -> list[str]:
roles_dir: Directory holding the roles.
Returns:
Sorted names of the ``web-app-`` and ``web-svc-`` roles that have a web
UI of their own and carry a lifecycle inside the tested envelope. A
role without a ``design`` service entry is included: its design pass
adds the entry.
Sorted names of the ``web-app-`` and ``web-svc-`` roles that carry a
lifecycle inside the tested envelope and do not switch their ``design``
service off (see :func:`design_disabled`). ``web-svc-design``, the
provider, is no candidate. A role without a ``design`` service entry is
included: its design pass adds the entry.
"""
envelope = tested_lifecycles()
return [
@@ -75,6 +75,7 @@ The placement rule is per check. The catalog "Position" column uses these labels
| `raw-docker` | same or above; head (first 30 lines) | [test_no_raw_docker.py](../../../../tests/integration/infrastructure/docker/test_no_raw_docker.py) | Marks a single line or a whole file under `roles/` as legitimately calling `docker` / `docker compose` / `docker-compose` directly. |
| `hardcoded-dns-resolver` | same or above | [test_no_hardcoded_dns_resolvers.py](../../../../tests/lint/repository/network/test_no_hardcoded_dns_resolvers.py) | Marks a line that needs a literal IP from `NETWORK_PUBLIC_DNS_RESOLVERS` where the substitution point cannot consume the variable (CoreDNS non-Jinja templates, host-bootstrap shell, doc examples). |
| `dynamic-flag` | same line OR comment block above key | [test_dynamic_flags.py](../../../../tests/integration/roles/meta/services/test_dynamic_flags.py) | Marks a `roles/*/meta/services.yml` flag whose value stays literal. Per-flag (same line) for databases. Block-level (comment block above) when both flags stay literal. |
| `design-off` | line or comment block above `design:` | [test_design_off_reason.py](../../../../tests/integration/roles/meta/services/test_design_off_reason.py) | Required with `Reason: <text>` on a `design.enabled: false` in a `web-app-`/`web-svc-` role. Keeps the role out of the design queue and off the shared CSS. |
| `native-metrics` | comment block above | [test_native_metrics_contract.py](../../../../tests/integration/roles/meta/services/prometheus/test_native_metrics_contract.py) | Marks a role whose `services.prometheus.native_metrics.enabled` and `templates/prometheus.yml.j2` presence are intentionally inconsistent. Reserved for prepared-but-default-off scrape fragments (operator opt-in). |
| `prometheus-scrape` | same or above | [test_roles_with_service.py](../../../../tests/unit/python/plugins/lookup/test_roles_with_service.py) | Documents a `services.prometheus.scrape: false` opt-out in `roles/<role>/meta/services.yml`. The role keeps `services.prometheus.{enabled,shared}: true` (so the role-wiring contract stays satisfied) but `roles_with_service('prometheus')` drops it from `PROMETHEUS_TARGET_ROLES_JSON`. Reserved for 301-redirect-only vhosts that never serve a request through lua-resty-prometheus, so they emit no `app="<role>"` label and have no scrape target. The marker accompanies the `scrape:` data flag the lookup honors. |
| `matomo-track` | same or above | [test_roles_with_service.py](../../../../tests/unit/python/plugins/lookup/test_roles_with_service.py) | Documents a `services.matomo.track: false` opt-out in `roles/<role>/meta/services.yml`. The role keeps `services.matomo.{enabled,shared}: true` (so the role-wiring contract stays satisfied) but `roles_with_service('matomo')` drops it from `MATOMO_TARGET_ROLES_JSON` and the matomo tracker e2e contract. Reserved for static-file (autoindex) and 301-redirect vhosts that carry no user-facing HTML page worth a `_paq` tracker. The marker accompanies the `track:` data flag the lookup honors. |
+5 -5
View File
@@ -106,19 +106,19 @@ Notes:
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | design | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
| **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-zammad/meta/services.yml) | ❌ | **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](https://admin-docs.zammad.org/en/latest/settings/security/third-party/saml.html) | ❌ | ❌ | ❌ | **zammad** | [☑️](../../roles/web-app-zammad/meta/services.yml) | ❌ | ❌ | [✅](https://admin-docs.zammad.org/en/latest/channels/email/index.html) | ❌ | [☑️](../../roles/web-app-zammad/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-zammad/meta/services.yml) | **zammad** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **zammad** | — | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-app-zammad/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **zammad** | [☑️](../../roles/web-app-zammad/meta/services.yml) | ❌ | ❌ | ❌ |
| **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-asset/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-asset/meta/services.yml) | **asset** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | — | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-asset/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **asset** | ❌ | ❌ | ❌ | ❌ |
| **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-cdn/meta/services.yml) | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-cdn/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-cdn/meta/services.yml) | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ |
| **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-collabora/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-collabora/meta/services.yml) | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ |
| **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-coturn/meta/services.yml) | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ |
| **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-cdn/meta/services.yml) | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-cdn/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-cdn/meta/services.yml) | **cdn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | — | ❌ | ❌ | [☑️](../../roles/web-svc-cdn/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **cdn** | ❌ | ❌ | ❌ | ❌ |
| **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-collabora/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-collabora/meta/services.yml) | **collabora** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | — | ❌ | [☑️](../../roles/web-svc-collabora/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **collabora** | ❌ | ❌ | ❌ | ❌ |
| **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-coturn/meta/services.yml) | **coturn** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ | — | [☑️](../../roles/web-svc-coturn/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **coturn** | ❌ | ❌ | ❌ | ❌ |
| **design** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **design** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **design** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **design** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-design/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **design** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **design** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-design/meta/services.yml) | **design** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **design** | ❌ | ❌ | [☑️](../../roles/web-svc-design/meta/services.yml) | ❌ | ❌ | — | ❌ | ❌ | ❌ | ❌ | **design** | ❌ | ❌ | ❌ | ❌ |
| **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-file/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-file/meta/services.yml) | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-file/meta/services.yml) | — | ❌ | ❌ | ❌ | **file** | ❌ | ❌ | ❌ | ❌ |
| **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-html/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-html/meta/services.yml) | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-html/meta/services.yml) | ❌ | — | ❌ | ❌ | **html** | ❌ | ❌ | ❌ | ❌ |
| **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-legal/meta/services.yml) | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | — | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ |
| **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-legal/meta/services.yml) | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-legal/meta/services.yml) | ❌ | ❌ | — | ❌ | **legal** | ❌ | ❌ | ❌ | ❌ |
| **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-libretranslate/meta/services.yml) | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-libretranslate/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-libretranslate/meta/services.yml) | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **libretranslate** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-libretranslate/meta/services.yml) | ❌ | ❌ | ❌ | — | **libretranslate** | [☑️](../../roles/web-svc-libretranslate/meta/services.yml) | ❌ | ❌ | ❌ |
| ↓ row wires → col | akaunting | baserow | bigbluebutton | bluesky | bookwyrm | bridgy-fed | chess | confluence | dashboard | decidim | ↓ row wires → col | discourse | erpnext | espocrm | fediwall | fider | flowise | friendica | funkwhale | fusiondirectory | gitea | ↓ row wires → col | gitlab | hugo | jenkins | jira | jitsi | joomla | keycloak | kix | lam | listmonk | ↓ row wires → col | litellm | littlejs | magento | mailu | mastodon | matomo | matrix | mattermost | mediawiki | mig | ↓ row wires → col | minio | mini-qr | mobilizon | moodle | n8n | navigator | nextcloud | odoo | opencloud | openproject | ↓ row wires → col | opentalk | openwebui | peertube | pgadmin | phpldapadmin | phpmyadmin | pixelfed | postmarks | pretix | prometheus | ↓ row wires → col | roulette-wheel | shopware | snipe-it | socialhome | sphinx | suitecrm | taiga | wordpress | xwiki | yourls | ↓ row wires → col | zammad | asset | cdn | collabora | coturn | design | file | html | legal | libretranslate | ↓ row wires → col | logout | onlyoffice | simpleicons | xmpp |
| **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-logout/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-logout/meta/services.yml) | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **logout** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-logout/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **logout** | — | ❌ | ❌ | ❌ |
| **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-onlyoffice/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-onlyoffice/meta/services.yml) | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-onlyoffice/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **onlyoffice** | ❌ | — | ❌ | ❌ |
| **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-simpleicons/meta/services.yml) | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-simpleicons/meta/services.yml) | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-simpleicons/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-simpleicons/meta/services.yml) | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-simpleicons/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **simpleicons** | ❌ | ❌ | — | ❌ |
| **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-xmpp/meta/services.yml) | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-xmpp/meta/services.yml) | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | [☑️](../../roles/web-svc-xmpp/meta/services.yml) | ❌ | ❌ | — |
| **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-xmpp/meta/services.yml) | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-xmpp/meta/services.yml) | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **xmpp** | ❌ | ❌ | ❌ | ❌ | ❌ | [☑️](../../roles/web-svc-xmpp/meta/services.yml) | ❌ | ❌ | ❌ | ❌ | **xmpp** | [☑️](../../roles/web-svc-xmpp/meta/services.yml) | ❌ | ❌ | — |
## Maintenance
@@ -20,7 +20,7 @@ These choices are settled at requirement creation time and bound the implementat
10. **Verification.** Every change passes `make quality-high` and a minimal compose deploy against the running stack: `make compose-deploy apps=<role> variant=0 disable=<every disableable service except design>` (default `initialize` mode, no teardown, single pass, no `full_cycle`). CSS and branding iterations run without a redeploy through `make design-sync app=<role> variant=0` (re-renders shared CSS, role `style.css` and the generated logo assets). A changed `design.js`, task, template or `meta/` file runs through `make compose-role-sync role=<role> variant=0`, which re-runs only the app role against the running stack and renders the CSP hash in the vhost with it. A role pass runs `make test` and the lint targets of the file types it touched after its last edit, and closes with a role sync and the complete role spec; a pass that changed provisioning runs that role sync twice, and the second run reports no change for the tasks it added. `make quality-high` runs once per two or three role passes, at every merge of the main branch and before a role is committed. Each role is also asserted with one further base color, blue `#001f3f`, through `base=` on the sync targets. Swarm verification is out of scope.
11. **Review gallery.** Each role gets its own private Artifact with at least 20 views, each captured in light and dark mode and in a desktop (1440×900) and a mobile (390×844) viewport, so at least 80 screenshots of the designed state per role. The views favour detail-rich surfaces: settings, administration, forms, lists and detail pages, logged in where the app has accounts, with idempotently seeded showcase content where a list or detail page would otherwise be empty. `make design-gallery app=<role>` captures them in one spec run and copies them to `/tmp/design-gallery/<role>/`. A comparison with the app before the design is captured on request only: `before=true` adds every view with every injected snippet stripped from the document (the body filter wraps its injections in `<!--infinito-inj-->` markers). The operator approves or rejects by comment.
12. **Frontend and backend.** Every role is styled and tested on its frontend and, where the app has one, on its separate backend (admin surface such as `wp-admin`, the Shopware or Magento admin, the Joomla administrator). A role with a backend shows backend views in its gallery in addition to the frontend views, and its Playwright spec covers both surfaces.
13. **Scope.** In-scope roles are invokable, carry a lifecycle inside the tested envelope (`alpha` to `maintenance`), and expose a web UI.
13. **Scope.** In-scope roles are invokable, carry a lifecycle inside the tested envelope (`alpha` to `maintenance`), and expose a web UI. A `web-app-` or `web-svc-` role without a UI to design sets `design.enabled: false` in its `meta/services.yml` with `# nocheck: design-off Reason: <text>`, which a lint enforces, and `make design-queue` skips it: `web-app-bridgy-fed`, `web-app-mig` (operator decision), `web-svc-asset`, `web-svc-cdn`, `web-svc-collabora`, `web-svc-coturn`, `web-svc-file`, `web-svc-legal`, `web-svc-mirror`, `web-svc-onlyoffice`, `web-svc-simpleicons` and `web-svc-xmpp`.
## Acceptance Criteria
@@ -55,7 +55,6 @@ A role is checked when its frontend and, where present, its backend are styled,
- [ ] web-app-bigbluebutton
- [ ] web-app-bluesky
- [ ] web-app-bookwyrm
- [ ] web-app-bridgy-fed
- [ ] web-app-checkmk
- [ ] web-app-chess
- [ ] web-app-dashboard
@@ -90,7 +89,6 @@ A role is checked when its frontend and, where present, its backend are styled,
- [ ] web-app-matrix
- [ ] web-app-mattermost
- [ ] web-app-mediawiki
- [ ] web-app-mig
- [ ] web-app-mini-qr
- [ ] web-app-mobilizon
- [ ] web-app-moodle
@@ -126,12 +124,9 @@ A role is checked when its frontend and, where present, its backend are styled,
- [ ] web-app-xwiki
- [ ] web-app-yourls
- [ ] web-app-zammad
- [ ] web-svc-collabora
- [ ] web-svc-html
- [ ] web-svc-legal
- [ ] web-svc-libretranslate
- [ ] web-svc-logout
- [ ] web-svc-onlyoffice
## Cross-linking
+1 -3
View File
@@ -21,7 +21,6 @@ flowchart LR
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_app_stalwart["web-app-stalwart 🐳🐝"]
dep_web_svc_design["web-svc-design 💻"]
end
subgraph role [web-app-bridgy-fed 🐳🐝]
svc_logout["logout ❌"]
@@ -29,7 +28,7 @@ flowchart LR
svc_dashboard["dashboard"]
svc_email["email ❌"]
svc_matomo["matomo"]
svc_design["design"]
svc_design["design ❌"]
svc_prometheus["prometheus"]
svc_bridgy_fed["bridgy-fed"]
svc_tor["tor"]
@@ -40,7 +39,6 @@ flowchart LR
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_app_stalwart -- "0..0" --> svc_email
dep_web_svc_design -. "0..1" .-> svc_design
linkStyle 2,5 stroke:red;
```
+4 -3
View File
@@ -28,10 +28,11 @@ matomo:
# nocheck: matomo-track - root serves Werkzeug's default 404 without </head>/<body>, head injection cannot anchor
track: false
# nocheck: playwright-service-flag - design consumer, no Playwright surface (provider: web-svc-design)
# nocheck: dynamic-flag
# nocheck: design-off Reason: flask_app serves only federation endpoints; '/' answers Werkzeug's bare 404
design:
bond: 1
enabled: "{{ 'web-svc-design' in group_names }}"
shared: "{{ 'web-svc-design' in group_names }}"
enabled: false
shared: false
# nocheck: matomo-track - same 404-without-anchors page, shared CSS <link> cannot inject
track: false
prometheus:
@@ -14,9 +14,6 @@
prometheus:
enabled: true
shared: true
design:
enabled: true
shared: true
tor:
enabled: true
- services:
@@ -29,8 +26,5 @@
prometheus:
enabled: false
shared: false
design:
enabled: false
shared: false
tor:
enabled: true
+1 -3
View File
@@ -23,7 +23,6 @@ flowchart LR
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_svc_design["web-svc-design 💻"]
end
subgraph role [web-app-mig 🐳🐝]
svc_logout["logout ❌"]
@@ -32,7 +31,7 @@ flowchart LR
svc_matomo["matomo"]
svc_redis["redis"]
svc_mig["mig"]
svc_design["design"]
svc_design["design ❌"]
svc_prometheus["prometheus"]
svc_tor["tor"]
end
@@ -42,7 +41,6 @@ flowchart LR
dep_web_app_keycloak -- "0..0" --> svc_sso
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_svc_design -. "0..1" .-> svc_design
linkStyle 3 stroke:red;
```
+4 -3
View File
@@ -49,10 +49,11 @@ mig:
mem_limit: 256m
pids_limit: 256
# nocheck: playwright-service-flag — design consumer, no Playwright surface (provider: web-svc-design)
# nocheck: dynamic-flag
# nocheck: design-off Reason: out of the design scope by operator decision; the pinned image's graph never renders (TODO.md)
design:
bond: 1
enabled: "{{ 'web-svc-design' in group_names }}"
shared: "{{ 'web-svc-design' in group_names }}"
enabled: false
shared: false
prometheus:
bond: 1
enabled: "{{ 'web-app-prometheus' in group_names }}"
-6
View File
@@ -16,9 +16,6 @@
prometheus:
enabled: true
shared: true
design:
enabled: true
shared: true
tor:
enabled: true
- services:
@@ -33,8 +30,5 @@
prometheus:
enabled: false
shared: false
design:
enabled: false
shared: false
tor:
enabled: true
+1
View File
@@ -28,6 +28,7 @@ matomo:
# nocheck: matomo-track
track: false
# nocheck: dynamic-flag
# nocheck: design-off Reason: the asset domain redirects onto web-svc-file, which serves the copied assets; no page of its own
design:
enabled: false
shared: false
+1
View File
@@ -27,6 +27,7 @@ flowchart LR
svc_build["build"]
svc_matomo["matomo"]
svc_javascript["javascript"]
svc_design["design ❌"]
svc_prometheus["prometheus"]
svc_tor["tor"]
end
+6
View File
@@ -48,6 +48,12 @@ javascript:
enabled: false
shared: true
canonical: cdn
# nocheck: playwright-service-flag — design consumer, no Playwright surface (provider: web-svc-design)
# nocheck: dynamic-flag
# nocheck: design-off Reason: serves the shared stylesheets and the npm-mirrored libraries the other roles load; its only HTML is nginx's autoindex listing (templates/proxy.conf.j2)
design:
enabled: false
shared: false
# nocheck: playwright-service-flag — /metrics endpoint covered by web-app-prometheus's own spec
prometheus:
bond: 1
+1
View File
@@ -27,6 +27,7 @@ flowchart LR
svc_matomo["matomo"]
svc_redis["redis"]
svc_collabora["collabora"]
svc_design["design ❌"]
svc_prometheus["prometheus"]
svc_tor["tor"]
end
@@ -40,6 +40,11 @@ collabora:
proxy:
service_key: collabora
lifecycle: beta
# nocheck: dynamic-flag
# nocheck: design-off Reason: '/' answers a plain OK, the admin console is off and the editor opens only as a POST from the WOPI host (Nextcloud)
design:
enabled: false
shared: false
prometheus:
bond: 1
enabled: "{{ 'web-app-prometheus' in group_names }}"
+1
View File
@@ -48,6 +48,7 @@ flowchart LR
svc_coturn["coturn"]
svc_gateway["gateway"]
svc_redis["redis"]
svc_design["design ❌"]
svc_prometheus["prometheus"]
svc_tor["tor ❌"]
svc_container_backup["container_backup"]
+5
View File
@@ -42,6 +42,11 @@ redis:
disabled: true
enabled: false
shared: "{{ 'svc-db-redis' in group_names }}"
# nocheck: dynamic-flag
# nocheck: design-off Reason: TURN/STUN relay; its only HTTP is a gateway answering a plain-text OK (templates/compose.yml.j2)
design:
enabled: false
shared: false
# nocheck: variants-coverage — the tor conjunct is the clearnet-variant-tor invariant below, so no variant may pin the true polarity on an onion node
# nocheck: bond-baseline — same reason: variant 0 must not pin the scrape consumer on
prometheus:
+2 -2
View File
@@ -47,13 +47,13 @@ flowchart LR
dpt_web_app_bigbluebutton["web-app-bigbluebutton 🐳🐝"]
dpt_web_app_bluesky["web-app-bluesky 🐳🐝"]
dpt_web_app_bookwyrm["web-app-bookwyrm 🐳🐝"]
dpt_web_app_bridgy_fed["web-app-bridgy-fed 🐳🐝"]
dpt_web_app_checkmk["web-app-checkmk 🐳🐝"]
dpt_web_app_chess["web-app-chess 🐳🐝"]
dpt_web_app_confluence["web-app-confluence 🐳🐝"]
dpt_web_app_dashboard["web-app-dashboard 🐳🐝"]
dpt_web_app_decidim["web-app-decidim 🐳🐝"]
dpt_web_app_discourse["web-app-discourse 🐳🐝"]
dpt_web_app_erpnext["web-app-erpnext 🐳🐝"]
dpt_more["..."]
end
dep_svc_net_tor -. "0..1" .-> svc_tor
@@ -65,13 +65,13 @@ flowchart LR
svc_design -. "0..1" .-> dpt_web_app_bigbluebutton
svc_design -. "0..1" .-> dpt_web_app_bluesky
svc_design -. "0..1" .-> dpt_web_app_bookwyrm
svc_design -. "0..1" .-> dpt_web_app_bridgy_fed
svc_design -. "0..1" .-> dpt_web_app_checkmk
svc_design -. "0..1" .-> dpt_web_app_chess
svc_design -. "0..1" .-> dpt_web_app_confluence
svc_design -. "0..1" .-> dpt_web_app_dashboard
svc_design -. "0..1" .-> dpt_web_app_decidim
svc_design -. "0..1" .-> dpt_web_app_discourse
svc_design -. "0..1" .-> dpt_web_app_erpnext
```
Solid `1:1` edges are fixed relationships; dashed `0..1` edges are conditional (enabled only in matching deployments); red `0..0` edges are turned off in this role. Node markers show the role's deploy modes (💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly turned off, and ⚙️ an Ansible role dependency declared in `meta/main.yml`.
+1 -3
View File
@@ -18,12 +18,11 @@ flowchart LR
dep_svc_net_tor["svc-net-tor 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_svc_design["web-svc-design 💻"]
end
subgraph role [web-svc-file 💻]
svc_file["file"]
svc_matomo["matomo"]
svc_design["design"]
svc_design["design ❌"]
svc_prometheus["prometheus"]
svc_tor["tor"]
end
@@ -33,7 +32,6 @@ flowchart LR
dep_svc_net_tor -. "0..1" .-> svc_tor
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_svc_design -. "0..1" .-> svc_design
svc_file -- "1:1" --> dpt_web_svc_asset
```
+4 -3
View File
@@ -18,10 +18,11 @@ matomo:
shared: "{{ 'web-app-matomo' in group_names }}"
# nocheck: matomo-track
track: false
# nocheck: dynamic-flag
# nocheck: design-off Reason: plain file server; its only HTML is nginx's autoindex listing (templates/proxy.conf.j2)
design:
bond: 1
enabled: "{{ 'web-svc-design' in group_names }}"
shared: "{{ 'web-svc-design' in group_names }}"
enabled: false
shared: false
prometheus:
bond: 1
enabled: "{{ 'web-app-prometheus' in group_names }}"
-6
View File
@@ -12,9 +12,6 @@
prometheus:
enabled: true
shared: true
design:
enabled: true
shared: true
tor:
enabled: true
- services:
@@ -24,8 +21,5 @@
prometheus:
enabled: false
shared: false
design:
enabled: false
shared: false
tor:
enabled: true
+1
View File
@@ -22,6 +22,7 @@ flowchart LR
subgraph role [web-svc-legal]
svc_legal["legal ❌"]
svc_html["html"]
svc_design["design ❌"]
svc_prometheus["prometheus"]
svc_tor["tor"]
end
+5
View File
@@ -17,6 +17,11 @@ html:
bond: 1
enabled: true
shared: true
# nocheck: dynamic-flag
# nocheck: design-off Reason: renders the imprint into web-svc-html's web root, where web-svc-html's design applies and its design spec covers the page
design:
enabled: false
shared: false
prometheus:
bond: 1
enabled: "{{ 'web-app-prometheus' in group_names }}"
+1
View File
@@ -24,6 +24,7 @@ flowchart LR
subgraph role [web-svc-mirror 💻]
svc_dashboard["dashboard"]
svc_mirror["mirror"]
svc_design["design ❌"]
svc_prometheus["prometheus"]
svc_tor["tor"]
end
+6
View File
@@ -17,6 +17,12 @@ mirror:
pids_limit: 64
min_storage: "1GB"
lifecycle: alpha
# nocheck: playwright-service-flag — design consumer, no Playwright surface (provider: web-svc-design)
# nocheck: dynamic-flag
# nocheck: design-off Reason: proxies whitelisted third-party assets under '/<origin host>/'; '/' answers 404 (templates/proxy.conf.j2)
design:
enabled: false
shared: false
# nocheck: playwright-service-flag — /metrics endpoint covered by web-app-prometheus's own spec
prometheus:
bond: 1
+1 -3
View File
@@ -25,14 +25,13 @@ flowchart LR
dep_svc_net_tor["svc-net-tor 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_svc_design["web-svc-design 💻"]
end
subgraph role [web-svc-onlyoffice 🐳🐝]
svc_matomo["matomo"]
svc_redis["redis"]
svc_postgres["postgres"]
svc_onlyoffice["onlyoffice"]
svc_design["design"]
svc_design["design ❌"]
svc_prometheus["prometheus"]
svc_tor["tor"]
svc_container_backup["container_backup"]
@@ -46,7 +45,6 @@ flowchart LR
dep_svc_net_tor -. "0..1" .-> svc_tor
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_svc_design -. "0..1" .-> svc_design
svc_matomo -. "0..1" .-> dpt_web_app_nextcloud
```
+4 -3
View File
@@ -50,10 +50,11 @@ onlyoffice:
proxy:
service_key: onlyoffice
lifecycle: beta
# nocheck: dynamic-flag
# nocheck: design-off Reason: document server; standalone it serves only static notice pages, the editors render only inside a host app that signs their configuration with the JWT secret
design:
bond: 1
enabled: "{{ 'web-svc-design' in group_names }}"
shared: "{{ 'web-svc-design' in group_names }}"
enabled: false
shared: false
prometheus:
bond: 1
enabled: "{{ 'web-app-prometheus' in group_names }}"
@@ -18,9 +18,6 @@
prometheus:
enabled: true
shared: true
design:
enabled: true
shared: true
tor:
enabled: true
- services:
@@ -37,8 +34,5 @@
prometheus:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
design:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
tor:
enabled: true
@@ -22,6 +22,7 @@ redis:
shared: "{{ 'svc-db-redis' in group_names }}"
# nocheck: dynamic-flag
# nocheck: playwright-service-flag — design consumer, no Playwright surface (provider: web-svc-design)
# nocheck: design-off Reason: icon API; '/' redirects to the docs or answers plain text, every other route returns an SVG or PNG
design:
enabled: false
shared: false
+1
View File
@@ -26,6 +26,7 @@ flowchart LR
svc_prometheus["prometheus"]
svc_sso["sso"]
svc_ldap["ldap"]
svc_design["design ❌"]
svc_xmpp["xmpp"]
svc_tor["tor"]
svc_container_backup["container_backup"]
+6
View File
@@ -17,6 +17,12 @@ ldap:
bond: 1
enabled: "{{ 'svc-db-openldap' in group_names }}"
shared: "{{ 'svc-db-openldap' in group_names }}"
# nocheck: playwright-service-flag — design consumer, no Playwright surface (provider: web-svc-design)
# nocheck: dynamic-flag
# nocheck: design-off Reason: XMPP clients connect over 5222/5269; the only HTTP page is ejabberd's built-in web admin on /admin behind HTTP Basic (templates/configuration.yml.j2)
design:
enabled: false
shared: false
xmpp:
healthcheck:
test: ["CMD", "ejabberdctl", "status"]
@@ -0,0 +1,118 @@
"""Every ``web-app-`` and ``web-svc-`` role declares a ``design`` entry in
``meta/services.yml``, and a role that switches it off says why.
``design.enabled: false`` keeps a role out of the design queue
(``make design-queue``) and off the shared CSS and the design script. The
``design:`` line or the contiguous comment lines directly above it carry the
marker and the reason on one comment line:
# nocheck: design-off Reason: <why the role has no UI to design>
design:
enabled: false
shared: false
``web-svc-design``, the provider, is exempt.
"""
from __future__ import annotations
import re
import unittest
from cli.meta.roles.design import DESIGN_ENTITY, UI_PREFIXES, design_disabled
from utils.annotations.suppress import line_has_rule
from utils.cache.files import read_text
from utils.roles.applications.services.registry import read_yaml_file
from utils.roles.entity.name import get_entity_name
from utils.roles.mapping import ROLE_FILE_META_SERVICES
from . import PROJECT_ROOT
_RULE = "design-off"
_REASON = re.compile(r"#.*\breason\b\s*:\s*\S", re.IGNORECASE)
_DESIGN_KEY = re.compile(rf"^{DESIGN_ENTITY}\s*:")
def justified(lines: list[str]) -> bool:
"""Whether the top-level ``design:`` key carries the marker and a reason.
Args:
lines: Lines of a ``meta/services.yml``.
Returns:
``True`` when the key line or the contiguous comment lines directly
above it hold ``# nocheck: design-off`` and ``Reason: <text>``.
"""
index = next((i for i, line in enumerate(lines) if _DESIGN_KEY.match(line)), None)
if index is None:
return False
block = [lines[index]]
above = index - 1
while above >= 0 and lines[above].lstrip().startswith("#"):
block.append(lines[above])
above -= 1
has_rule = any(line_has_rule(line, _RULE) for line in block)
return has_rule and any(_REASON.search(line) for line in block)
class TestDesignOffReason(unittest.TestCase):
def test_every_ui_role_declares_design_and_justifies_switching_it_off(
self,
) -> None:
offenders: list[str] = []
roles_dir = PROJECT_ROOT / "roles"
for role_dir in sorted(p for p in roles_dir.iterdir() if p.is_dir()):
role = role_dir.name
services = role_dir / ROLE_FILE_META_SERVICES
if not role.startswith(UI_PREFIXES) or not services.is_file():
continue
if get_entity_name(role) == DESIGN_ENTITY:
continue
if DESIGN_ENTITY not in read_yaml_file(services):
offenders.append(f"{role}: declares no '{DESIGN_ENTITY}' entry")
continue
if design_disabled(role_dir) and not justified(
read_text(str(services)).splitlines()
):
offenders.append(
f"{role}: 'design.enabled: false' without "
f"'# nocheck: {_RULE} Reason: ...'"
)
if offenders:
self.fail(
"Every web-app-/web-svc- role declares a 'design' entry in "
"meta/services.yml. A role that sets 'enabled: false' leaves the "
"design queue and the shared CSS, so it MUST say why: put "
f"'# nocheck: {_RULE} Reason: <why there is no UI to design>' on "
"the 'design:' line or the comment lines directly above it.\n\n"
"Offenders:\n" + "\n".join(f"- {line}" for line in offenders)
)
def test_reason_detection(self) -> None:
cases = {
"marker and reason above": (
[f"# nocheck: dynamic-flag, {_RULE} Reason: no page", "design:"],
True,
),
"marker without reason": ([f"# nocheck: {_RULE}", "design:"], False),
"reason without marker": (
["# nocheck: dynamic-flag Reason: x", "design:"],
False,
),
"blank line breaks the block": (
[f"# nocheck: {_RULE} Reason: no page", "", "design:"],
False,
),
"marker and reason on the key line": (
[f"design: # nocheck: {_RULE} Reason: no page"],
True,
),
}
for name, (lines, expected) in cases.items():
with self.subTest(case=name):
self.assertEqual(justified(lines), expected)
if __name__ == "__main__": # pragma: no cover
unittest.main()
@@ -263,12 +263,23 @@ class TestCandidateRoles(unittest.TestCase):
self._role("web-app-demo", {"demo": {}, "design": self.CONSUMER})
self.assertEqual(candidate_roles(self.roles_dir), [])
def test_ui_less_provider_is_skipped(self) -> None:
def test_role_that_switches_design_off_is_skipped(self) -> None:
self._role(
"web-svc-cdn", {"cdn": {"lifecycle": "beta"}, "design": self.CONSUMER}
"web-svc-cdn", {"cdn": {"lifecycle": "beta"}, "design": {"enabled": False}}
)
self.assertEqual(candidate_roles(self.roles_dir), [])
def test_templated_design_flag_keeps_the_role(self) -> None:
flag = "{{ 'web-svc-design' in group_names }}"
self._role(
"web-app-demo", {"demo": {"lifecycle": "beta"}, "design": {"enabled": flag}}
)
self.assertEqual(candidate_roles(self.roles_dir), ["web-app-demo"])
def test_design_provider_is_skipped(self) -> None:
self._role("web-svc-design", {"design": {"lifecycle": "beta"}})
self.assertEqual(candidate_roles(self.roles_dir), [])
def test_roles_outside_the_ui_categories_are_skipped(self) -> None:
for name in ("svc-db-demo", "drv-demo", "web-opt-rdr-demo", "update"):
self._role(name, {"demo": {"lifecycle": "beta"}, "design": self.CONSUMER})