The shared `container pull` of the Playwright image had no timeout. A
wedged registry-proxy tunnel kept a local deploy on that task for 18
minutes with no traffic until the pull was killed by hand; the task's
`retries: 3` never got a chance because the first attempt never
returned.
Run the pull under `timeout -k 60 <lookup('timeout', 1800)>`, the same
shape the compose build handlers use. A stuck pull now exits with 124
and the existing retry loop starts a fresh connection.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The previous commit packed the personas into one archive but still transferred them per role. orchestrate.yml runs 01_setup.yml once and then loops 02_run_one.yml over every app plus a retry pass, which is where the 22 transfers came from. Moving the staging into the setup phase leaves exactly one.
Each container now mounts the shared tree read-only at /e2e/tests/personas, the same pattern node_modules already uses. Both import directions were checked: specs require ./personas from /e2e/tests and land in the mount, while personas/utils/env.js requires ../../timeouts and reaches two levels up into the writable per-app staging dir, where the shared helpers are still placed. The nested mount therefore does not cut the relative imports.
Measured saving is 4.1 of the 4.2 minutes the copy cost in odoo 0, since only one transfer remains.
Not verified: the nested bind mount was not executed. Docker orders mounts by destination depth so /e2e lands before /e2e/tests/personas, but if that ordering did not hold the spec would fail loudly on require, not silently.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Playwright image was pulled once per role, and the shared helpers
and persona modules were staged file by file. The pull now happens once
for the whole run, and the personas tree is copied in a single task.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Introduce plugins/lookup/path_absolute.py as the single source for the
repo-root anchor: lookup('path_absolute', 'roles/...') returns an absolute
path under playbook_dir, joining every term. Replace all hand-built
playbook_dir path constructions across .yml/.yaml and .j2 templates with it:
[playbook_dir, '<rel>'] | path_join -> lookup('path_absolute', '<rel>')
playbook_dir ~ '/<rel>' -> lookup('path_absolute', '<rel>')
{{ playbook_dir }}/<rel> -> lookup('path_absolute', '<rel>')
Unrelated repo_path symbols in cli/meta/roles/applications/resolution and the
single bare discover_playwright_roles(playbook_dir) call are left untouched.
Add tests/lint/ansible/test_playbook_dir_via_lookup.py: fails the build if any
of the three construction forms reappears in .yml/.yaml/.j2, pointing callers
at the lookup. Unit test covers the plugin.
Touching these files pulled their pre-existing narration comments into
test_comments_valid scope; clean them (delete restatement/banners, keep real
trip-wires as Workaround/Upstream/Invariant markers). Drop caution, important
and pitfall from the comment-lint marker vocabulary and remove the comments
that used them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tests/lint/repository/test_name_emoji_and_length.py: every Ansible task
name and GitHub Actions workflow/job/step name must start with an emoji (first
char not 0-9/A-Za-z) and be <= 80 chars.
- emit emoji names at the source: cli/build/include generator now writes
"🔧 setup <app>" / "🚿 flush handlers after <app>" so generated
tasks/groups/*-roles.yml satisfy the convention too
- convert existing task/step/job/workflow names to emoji-prefixed, choosing a
task-fitting icon per action (📦 install, ✅ ensure, 🔍 probe, 🥾 bootstrap,
🚀 deploy, 🧩 service-module load, ⬇️ checkout, 📂 include-routines, ...)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds two structural ansible lints under tests/lint/ansible/structure/:
- ansible-block-oversized: forbid `- block:` constructs with more than
3 direct child tasks. Big block bodies hide intent, accumulate
unrelated guards on the wrapper, and signal that the body wants to
be its own include_tasks sub-file. Extraction keeps the parent
scannable; the sub-file MAY carry the `# include-gated:
when: …` header marker so swarm-compat lints exempt it.
- ansible-repeated-when-run: forbid 3+ consecutive sibling tasks
carrying the SAME `when:` expression. Copy-pasted guards drift,
silently disable each other over time, and read worse than a single
`- name: …` + `when: <expr>` + `block:` wrapper. Per-line opt-out
for runs where wrapping would alter semantics (cross-task register
dependencies).
Both lints use the line-based scanner pattern from existing
tests/lint/ansible/jinja/* + `is_suppressed_at` from
utils.annotations.suppress for the per-line nocheck markers.
Refactors five roles into compliance:
- svc-prx-openresty/tasks/02_assert.yml: heavy rescue body extracted
to 02a_assert_rescue_debug.yml.
- test-e2e-playwright/tasks/01_setup.yml: 3 install-needed siblings
wrapped in a single when-block.
- web-app-erpnext/tasks/02_bench_bootstrap.yml: 3 site-probe-failed
siblings wrapped likewise.
- web-app-odoo/tasks/02_install_modules.yml: 3 OIDC-enabled siblings
wrapped. tasks/03_oidc.yml: dropped redundant single-task wrapper
block (the original wrapper carried name only and would have
required extraction for size — inlining the 23 tasks at file root
keeps the file purpose-narrow and removes one layer of indirection).
- web-app-xwiki/tasks/04_extensions.yml: large block body + rescue
body both extracted to 04a_extensions_rescue.yml +
04b_extensions_execute.yml.
Mixed-scope offenders (web-app-keycloak/tasks/{02_manager_ops,05a,
assert/05_ldap,update/05_ldap_shared,02a} and
web-app-matomo/tasks/{03_manager_ops,05_bootstrap,05a}) deliberately
stay unstaged; they ship with their respective swarm-aware
restructure commits where they belong by topic, not bundled with the
structural-lint cleanup.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per-role Playwright runs no longer call `npm install` against
registry.npmjs.org on every invocation. A new `01_setup.yml` runs once
per host/version, populates `_shared/node_modules/` inside the Playwright
container, and stamps the installed version in `.installed-version`.
Each `02_run_one.yml` mounts that tree read-only at `/e2e/node_modules`
and the container command becomes `npx --no-install playwright test`.
This eliminates the npmjs.org TLS-handshake flake that took out the
keycloak playwright in a recent variant-2 update, saves ~4 s per role,
and gives version bumps a single rebuild point.
Three retry layers cover transient registry failures: Ansible (3× with
5 s delay), npm fetch (5× with 10-60 s backoff), and image pull (3×).
Stage-base / shared-dir / stamp / node_modules paths live as SPOT
constants in `vars/main.yml` so call sites never reconstruct them.
Renames `tasks/run_one.yml` → `tasks/02_run_one.yml` (and adds
`01_setup.yml`) to make the include order visible by filename; updates
cross-references in `AGENTS.md`, the two `docs/contributing/...` pages,
`dump_disk_diagnostics.sh`, and the lint test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>