Commit Graph
1371 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 a6d15a5ed4 test(web-app-moodle): mark the tempdir walk of the auth_oidc sync test
test_no_raw_project_walk flagged tree.rglob in files_in; the walk scans the test's own temporary directory, the exception the lint documents, so it carries the project-walk nocheck instead of the cached project helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:43:54 +01:00
kevinveenbirkenbachandClaude Opus 5.5 f7923b9f94 test(web-app-moodle): build the sync-body environment with select_autoescape
ruff S701 flagged the jinja2 Environment without autoescape in the auth_oidc sync test; select_autoescape(default_for_string=False) keeps the from_string shell body unescaped, as other tests do, and autoformat wrapped one long filecmp call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:31:24 +01:00
kevinveenbirkenbach 089ca505b6 Merge branch 'fix/web-app-moodle/oidc-plugin-missing' into feature/store-i18n 2026-11-03 21:16:59 +01:00
kevinveenbirkenbachandClaude Opus 5.5 9985fb6d08 style(dev-python): apply ruff format to the install test after the DEV_PYTHON_REQUIRES rename
The shorter variable name from 0bdb14cc1d (#1358) lets the dict comprehension fit on one line; make autoformat in gate pass 6 collapsed it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:16:47 +01:00
kevinveenbirkenbachandClaude Opus 5.5 279a449566 fix(web-app-moodle): sync the auth_oidc plugin from the image into the code volume
The entrypoint refills the shared code volume only when the release changes, so a volume bootstrapped at 5.3.0 by an image without auth_oidc never received the plugin from a later same-release image; galaxyserver's production deploy failed at the presence check although the image ships it (probe: /opt/moodle-source/public/auth/oidc exists). 04_oidc.yml now asserts the image ships the plugin, naming the image when it does not, and syncs it into the volume as the runtime user through a staging directory, only when diff -rq finds a difference, before the existing presence check. The OIDC upgrade.php run no longer hides a failed plugin install behind failed_when: false, matching the MCP and objectfs plugin tasks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:42:17 +01:00
kevinveenbirkenbach 42315cb065 Merge commit '0bdb14cc1d' (fix/dev-python/python-requires-env-name, #1358) into feature/store-i18n 2026-11-03 20:25:21 +01:00
kevinveenbirkenbach 9642ff13e4 Merge remote-tracking branch 'origin/main' into feature/store-i18n 2026-11-03 20:22:44 +01:00
kevinveenbirkenbach b73af793aa Merge branch 'perf/web-app-stalwart/jmap-batch' into feature/store-i18n 2026-11-03 20:22:33 +01:00
kevinveenbirkenbachandClaude Opus 5.5 be60486fc4 perf(stack_host_copy): settle trees with empty leaf directories in the one-call check
Development worktrees carry empty .claude/.cc-writes directories in role source trees; the one-call unchanged check gave up on any directory without files, so the docs tooling copy fell back to per-file stat and copy round trips (about 56 s on the echoserver production deploy). file_checksums now also reports directories (isdir, mode), and the check asks for every leaf directory in the same remote call: it settles only when each leaf exists as a real directory and, when directory_mode is set, carries exactly that mode. With directory_mode unset the stock copy would not touch an existing directory either (file module with mode None). A missing directory, a mode mismatch, a file or symlink in its place, or a symbolic directory_mode with leaves still runs the regular copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:04:23 +01:00
kevinveenbirkenbachandClaude Opus 5.5 0bdb14cc1d fix(dev-python): name the requires-python hand-over outside the .env key space
#1347 passed the supported Python range to the remote install.sh as
INFINITO_PYTHON_REQUIRES. Every INFINITO_* name is a key of the generated
.env, so test_every_referenced_infinito_key_is_registered fails on main
for this one, which no registry entry can carry: its value is derived
from pyproject.toml's requires-python, the single source, and a default
in default.env would be a second copy of the range. The task environment
and the script now call it DEV_PYTHON_REQUIRES; behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:55:27 +01:00
kevinveenbirkenbachandClaude Opus 5.5 7ee231201d perf(web-app-stalwart): batch independent management JMAP reads into one request each
Every jmap_call.yml call pays about 1.5 s of fixed cost (container exec, curl, include, parse and assert tasks) before Stalwart does any work; the echoserver production deploy spent 98 s in 33 calls. Independent reads now share one request: Security, AllowedIp and Http in security.yml, Domain and MailingList in 15_aliases.yml, Application and OAuthClient in 02_webadmin.yml, Directory and Role in 06_oidc.yml. The rendered method calls and arguments are identical to the old requests, no JMAP call runs between a batched read and the facts it feeds, and the User-role assert still follows the directory writes. Writes stay in their own requests, because JMAP keeps processing later calls after a refused set. A refused read is now named by its object instead of c0. Projected saving: about 19 s per echoserver deploy, about 10 s on galaxyserver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:54:58 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e3ffa80c11 perf(stack_host_copy): leave Python bytecode caches out of directory copies
Source trees in a development worktree carry __pycache__ next to their sources (web-app-docs python/: 76 .pyc beside 42 .py). They defeated the one-call unchanged check and were copied file by file over SSH; on the echoserver production deploy the docs tooling staging took 155.9 s at about 1.27 s per file. The plugin now walks the tree without __pycache__ for both the unchanged check and the stock copy, which reads _walk_dirs as a module global; the swap is restored in finally and each task runs in its own forked worker. Sourceless .pyc outside a cache directory stay in the copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:04:11 +01:00
kevinveenbirkenbachandClaude Opus 5.5 0c54388951 fix(constructor): accept the legacy owners of networks and volumes in the pre-flight
Production hosts carry objects from before the compose project was named after the entity: galaxyserver's mariadb, openldap and postgres networks have no project label, and echoserver's openldap_data volume is labelled svc-db-openldap, the role id older deploys passed as -p. The pre-flight stopped both deploys although compose reuses such networks and volumes with a warning. A network named after its entity now passes without a label or with a declaring role id, still checked against its subnet; a volume passes with a declaring role id as well. A container does not, because compose cannot see a container of another project and fails on its name. A label naming another project still stops the deploy, and the volume message now says compose would hand the role another project's data instead of claiming it refuses the volume.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:07:15 +01:00
kevinveenbirkenbachandClaude Opus 5.5 77cab6e40b perf(svc-swarm-node): read the ingress ports and the node addresses in one shell
The ingress guard read ran two remote shells per swarm deploy, each asking container info again (WordPress#0 run 41303726270: 66 s + 61 s over 64 calls each). One shell now prints 'port <json>' and 'node <address>' lines into swarm_ingress_read; the new swarm_ingress_sections filter splits them by tag and raises on any untagged line, and SWARM_INGRESS_GUARDED and SWARM_INGRESS_PEERS read their section, so the guarded set, the peers, the firewall digest and the ruleset stay the same. A test ties the --format tags in read.yml to the sections the filter knows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:38:05 +01:00
kevinveenbirkenbachandClaude Opus 5.5 eb36a54c44 perf(sys-svc-compose-ca): stop probing images on the swarm no-wrapper path
render_override gathered the metadata of every image (docker image inspect, a pull when missing, a docker run probe) before it checked wrap, and the swarm handler's --no-wrapper path threw the result away, because image_meta is read only behind 'if not wrap: continue'. The gather now runs only when wrap is true, so the swarm override is unchanged byte for byte. It pulled again the externals that registry_sync had just removed (WordPress#0 run 41303726270: 275 s over 64 calls).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:14:01 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a3cb439a68 fix(constructor): stop the disk pre-flight only when a full filesystem also lacks absolute room
The pre-flight stopped every deploy on a filesystem above SIZE_PERCENT_MAXIMUM_DISK_USAGE, which blocked a 1.8 TB workstation at 99 % with 30 GB free while a 145 GB runner was fine. A filesystem now fails only when it is above the percentage AND offers less than SIZE_GB_MINIMUM_DISK_FREE (20 GB): disk_usage_errors reads df's available column, the preflight_disk_usage filter passes the floor, and disk.yml runs df -P -k so the block unit stays KiB even under POSIXLY_CORRECT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:10:34 +01:00
kevinveenbirkenbachandClaude Opus 5.5 81e2be2204 fix(dev-python): hand requires-python to the remote script with a valid backreference
76adaeb9e8 wrote the regex_search group reference as '\1' inside the folded
task scalar. Jinja unescapes string literals, so the filter received \x01,
rejected it as an unknown argument and failed the dev-python task on every
remote host. The literal is now '\\1', and the unit test renders the task's
environment expression the way Ansible does instead of only checking that
the variable is named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:55:22 +01:00
kevinveenbirkenbachandClaude Opus 5.5 76adaeb9e8 fix(dev-python): take the supported Python range from requires-python
bf9c5be173 capped the bootstrap at 3.14 with constants inside install.sh, a
second copy of the range pyproject.toml already declares. requires-python is
now the single source (>=3.11,<3.15): install.sh derives its floor and cap
from it, in the repository or, on a remote host where the script runs alone,
from INFINITO_PYTHON_REQUIRES that the dev-python role reads from the same
file; any other form fails loudly. The Dockerfile copies pyproject.toml
before the bootstrap, and pip now refuses 3.15 for the package as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:42:34 +01:00
kevinveenbirkenbachandClaude Opus 5.5 bf9c5be173 fix(dev-python): cap the bootstrap Python at the newest minor ansible-core supports
Fedora 45 defaults to Python 3.15. ansible-core 2.21, which the unpinned
`ansible` extra still resolves to (ansible 14.5.0 pins ~=2.21.5), calls
dataclasses._is_type, which 3.15 removed, so the Workspace (fedora:latest)
job died before the first playbook (run 41491308729).

install.sh now picks and installs the highest Python between
PYTHON_MIN_MINOR and PYTHON_MAX_MINOR (3.11 to 3.14) on apt, rpm and
pick_python_bin, and fails loudly when only newer interpreters exist. On
Fedora 45 it installs python3.14. Raise PYTHON_MAX_MINOR once ansible ships
ansible-core 2.22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:06:41 +01:00
kevinveenbirkenbachandClaude Opus 5.5 1ec96181b1 Merge remote-tracking branch 'origin/main' into feature/store-i18n
Conflicts resolved by combining both sides: utils/tests/swarm/run.py keeps store-i18n's re-export of run_watched from watchdog.py, and the #1330 storage roots (lowest_free_mb over / and /var/lib/containerd, the fullest root in the abort banner and the sample line, df over every root) move into watchdog.py; test_run_storage_roots.py now targets watchdog and expects the process-group kill. The seaweedfs 4.52 bump lands in roles/web-svc-seaweedfs, where store-i18n moved the engine; roles/web-app-seaweedfs keeps only the console.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:53:44 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a67dd9b372 feat(constructor): stop a deploy in a pre-flight when the host cannot carry it
The constructor now runs tasks/utils/constructor/preflight.yml before the package install and fails with a named remedy when: the running kernel has no module index on disk or the clock is not synchronised (chrony leap status, else timedatectl, outside containers); / or the docker root is fuller than SIZE_PERCENT_MAXIMUM_DISK_USAGE (90); a docker network contradicts the entity that declares it (name, compose project label, subnet outside its block, overlap with another declared subnet); a declared container lacks or a declared volume carries a foreign entity label; a docker default address pool overlaps the mesh, Tor or a declared subnet; a host process outside docker holds a public port of the deploy; or, in assert mode, a clearnet domain of the deploy does not resolve.

A volume without any label passes, since compose adopts it and installs before this change created the Synapse volume with docker run. web-app-discourse declares its launcher-started containers through networks.local.launcher, and web-app-matrix labels a new Synapse volume with its compose project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:38:15 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e66c635fb6 perf(sys-svc-container): roll a single-replica swarm service without a stagger delay
swarmkit sleeps update_config.delay after the last slot too and then monitors for delay + 1s, so a one-replica service paid about 21 s per rollout for nothing to stagger (database stacks converged in 32 s instead of 8 s in WordPress#0's update pass of run 41303726270, about 400 s per row). A service whose replicas line is 'replicas: 1' now rolls with delay 0s; several replicas keep service_update_delay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:45:15 +01:00
kevinveenbirkenbachandClaude Opus 5.5 d6151aa559 fix(web-app-bigbluebutton): publish every container port once, on the proxy's upstream host
BBB's upstream compose template publishes nginx's 48081/48082/48087 on 127.0.0.1 and on 10.7.7.1. On Arch, Docker's nftables backend with the userland proxy off writes one identical 'MASQ TO OWN PORT' rule per binding and the second fails with 'rule exists' (moby 60109b06fb, PR #54031, first released in 29.10.0), so compose up failed in Moodle#3 of run 41303726270. compose_mods keeps only bindings on services.bigbluebutton.upstream_host or without a host address; nothing in the platform uses the loopback ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:34:22 +01:00
kevinveenbirkenbachandClaude Opus 5.5 486d85c29f perf(web-app-docs): claim a build only for a free slot and skip current translations
The builder handed every queued marker straight to its worker pool, whose own FIFO let a background latest build run ahead of the deployed:de build a visitor waited on (40-81 min in docs#1 of run 41303726270). A marker is now claimed only while a slot is free. build_language dequeues a translation that is already current instead of rebuilding it for 2-3 h on a duplicate request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:31:03 +01:00
kevinveenbirkenbachandClaude Opus 5.5 4f20a57a7e fix(web-app-docs): look at the shared volume again before answering 'Building'
On the swarm NFS volume a docs replica keeps a failed lookup until the parent's cached attributes expire, so a site another replica just renamed into place stayed invisible to it and it served the 202 'Building' page (lang en) after a sibling had answered 200 (docs#1 of run 41303726270). recheck_servable() lists the parent to force close-to-open revalidation and is called only on the path that would answer 202; ordinary page requests pay nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:27:05 +01:00
kevinveenbirkenbachandClaude Opus 5.5 cfc8cbb8fe fix(web-app-wazuh): give the manager room for the vulnerability feed and name failing API calls
At 2g the vulnerability-detection feed update thrashed the manager cgroup until the kernel OOM-killed wazuh-modulesd; Wazuh restarts no daemon, so the API answered every call with HTTP 500 (error 1017) and the API administrator grant failed in the update pass of run 41303726270 (Wazuh#1, swarm). The manager gets mem_limit 4g, and api_administrators.py raises '<METHOD> <path> answered HTTP <code>: <body>' instead of a bare 500 so a dead daemon shows up by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:23:53 +01:00
kevinveenbirkenbachandClaude Opus 5.5 23c3da94cd Merge the ERPNext standard price lists into staging/alphaserver
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:28:07 +01:00
kevinveenbirkenbachandClaude Opus 5.5 b23574fe78 Merge the stack_host_copy batched checksum into staging/alphaserver
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:00:20 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a394d4bf6b fix(svc-net-wireguard): dial every endpoint at the IPv4 address it resolves to
The mesh is IPv4 only (60-byte header, IPv4 pools), yet wg.conf handed
wg the endpoint name, and wg dials the first getaddrinfo answer, an AAAA
on a dual-stack name. A host with broken IPv6 then never handshook
(alphaserver, 2026-11-01). Resolve every dialled endpoint with getent
ahostsv4 on the dialling host, render that literal, measure the path MTU
against it, and fail before rendering when a name has no IPv4 address.
Also set the tunnel MTU on a live interface, since wg syncconf drops it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:53:01 +01:00
kevinveenbirkenbachandClaude Opus 5.5 ac4a8f45e2 fix(web-app-erpnext): seed the standard price lists the skipped setup wizard creates
The deploy bypasses ERPNext's setup wizard and with it install_defaults, so a site without the store's GUI fixtures had no Standard Selling or Standard Buying price list and every Item Price insert failed with "Could not find Price List: Standard Selling". company.py now creates both after the Company, insert-if-missing, with the wizard's selling and buying flags and the Company's own currency; Price List.on_update fills the Selling and Buying Settings defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:45:53 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e91117e421 perf(stack_host_copy): settle an unchanged directory copy in one remote call
Ansible's copy action runs one stat and one file module per file of a
directory source even when nothing changed, about 0.6 s each over SSH.
The echoserver deploy paid it on 31 branding renders and the docs
tooling tree (~770 s and 169.5 s, deploy -9). Before falling through to
the stock copy, hash the tree locally and compare it with one
file_checksums call on the target; when every file matches in type,
content and mode, return the stock copy's unchanged result. Any
difference, symlink, empty directory, owner or symbolic mode falls
through to the stock copy unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:35:01 +01:00
kevinveenbirkenbachandClaude Opus 5.5 7189721c38 Merge the open alphaserver fixes into staging/alphaserver
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:18:06 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e810319d00 fix(svc-net-wireguard): require a handshake from the last 180 s in the mesh gate
The gate counted any stamp above zero as handshaken. wg syncconf keeps
the old stamp while it points the endpoint at a dead path, so a deploy
passed the gate over a broken tunnel (alphaserver, 2026-11-01: the
endpoint resolved to an AAAA the host could not reach). Read the host's
epoch in the same shell call as latest-handshakes and accept a stamp
only within WireGuard's REJECT_AFTER_TIME of 180 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:58:26 +01:00
kevinveenbirkenbachandClaude Opus 5.5 0aa5d1386b fix(web-app-erpnext): seed the tree roots the skipped setup wizard creates
The deploy bypasses ERPNext's setup wizard and with it install_fixtures, so a site without the store's GUI fixtures had no root Item Group and every consumer creating one failed with "Could not find Parent Item Group: All Item Groups". company.py now seeds All Item Groups, All Customer Groups and All Supplier Groups as root groups, insert-if-missing like All Territories.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:51:07 +01:00
kevinveenbirkenbach 8e3aac5cae Merge branch 'fix/web-app-erpnext/api-bot-extra-roles' into feature/store-i18n 2026-11-01 12:34:41 +01:00
kevinveenbirkenbach 39f935cf22 Merge branch 'fix/deploy/primary-apps-json-separators' into feature/store-i18n 2026-11-01 12:34:32 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c8625c4592 fix(deploy): pass list extra vars as compact JSON
ansible-playbook splits each -e value through parse_kv on whitespace, so json.dumps' default ', ' separator broke PRIMARY_APPS=["a", "b"] into a raw parameter and the list never reached the play. Dump extra vars with separators=(',', ':') in the development deploy and the swarm matrix deploy, and test that a list extra var survives parse_kv.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:33:12 +01:00
kevinveenbirkenbachandClaude Opus 5.5 4afa1183c1 feat(web-app-erpnext): grant consumer-declared roles to the API bot
Consumers that build on the ERPNext site from their own repositories (web-app-cst-store, web-app-cst-security-check) now declare the roles they need on erpnext-api-bot in services.erpnext.api_bot.extra_roles instead of granting them themselves. Every deploy grants them beside System Manager and the Server Scripts role, creating an empty Role first when the site does not hold it yet, so this role stays the only writer of the bot's roles and keys. Port of the api-bot part of 067 from feature/iam-profile-rbac (3e5acbd104).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:32:42 +01:00
kevinveenbirkenbachandClaude Opus 5.5 2d7bf740b0 fix(ci): put containerd, Docker and swap on the runner's empty spare disk
The small GitHub runner shape (AWS, 72G root) ran out of disk in swarm and compose rows (#1329: WordPress#2 of run 41261531965, Nextcloud#6 and #7 of run 41262286962) while an empty instance-store disk nvme1n1 sat unused. mount_spare_disk.sh formats and mounts it at /mnt, but only on a GitHub-hosted runner outside act, and only a whole unpartitioned disk without any signature, mount or swap use of at least 75G; every decline leaves a notice. relocate_docker_dataroot.sh then bind-mounts /var/lib/containerd from the spare disk and moves the Docker data-root to /mnt/docker-host (not /mnt/docker, which is the nested daemon's volume that dedicated.sh wipes). It refuses before stopping anything when images, volumes, build cache, foreign containerd namespaces or shims exist or daemon.json is unreadable, and rolls back the bind mount and daemon.json on any later failure or cancel. Swap goes on / once containerd lives on /mnt, dump_state.sh logs disk size, model and free space per storage root, and the swarm watchdog samples / and /var/lib/containerd every 10 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:31:02 +01:00
kevinveenbirkenbachandClaude Opus 5.5 4f03e7f9f8 fix(web-app-stalwart): create accounts without credentials while SSO is on
Under SSO the OIDC directory is the authentication directory, and Stalwart refuses any password credential for its accounts ("Cannot set credentials for accounts in an external directory"). Every create carried one, so an account added after SSO was switched on never came into existence: the echoserver deploy failed on the keycloakbootstrap bot right after the 0.16 to 1.x migration, and a new person would have been skipped silently. Under SSO the create now carries no credentials and the password update is skipped; existing accounts come back as primaryKeyViolation with their id, as without SSO. Any remaining refusal fails the deploy. Closes the accounts entry of the role's TODO.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:11:03 +01:00
kevinveenbirkenbachandClaude Opus 5.5 ad86cb7a3a fix: satisfy the gate-32 lints in the Cloudflare retry and email provider-view changes
The Cloudflare request timeout, attempt deadline, collect deadline and retry delay now scale through lookup('timeout', ...) together, so a raised TIMEOUT_FACTOR keeps them consistent; the deadline task name fits 80 characters; test_async_retry_split reads the role defaults through PROJECT_ROOT, ROLE_FILE_DEFAULTS_MAIN and load_yaml. The provider-view helpers move from plugins/lookup/email.py (531 lines) to utils/mail/provider_view.py; provider_view_carrier stays in the lookup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:36:38 +01:00
kevinveenbirkenbach 134b22a30f Merge branch 'fix/sys-dns-cloudflare-records/async-hang' into feature/store-i18n 2026-11-01 10:17:37 +01:00
kevinveenbirkenbach e5141cc544 Merge branch 'perf/email/provider-render-reuse' into feature/store-i18n 2026-11-01 10:17:27 +01:00
kevinveenbirkenbachandClaude Opus 5.5 99de0c15e9 fix(sys-dns-cloudflare-records): bound each record job and retry only transient failures
A filesystem stall killed a Cloudflare record job before async_wrapper saw its start notice (Broken pipe), and the shared reap masked it. Each record job now has a request timeout and an attempt deadline; judge.yml reaps every job of an attempt and async_retry_split sorts the failures: hangs, timeouts, connection errors, 429 and 5xx get up to cloudflare_attempts tries, anything else fails at once and names its record. An answer that says the record already stands counts as done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:13:49 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a29483244d perf(email): park the provider-view render once per play on non-provider hosts
On swarm workers and the nfs-server, every full lookup('email') rendered the whole applications tree again (about 20 s per task, 8 tasks per CI run, Nextcloud#7 of run 41261531965), because the provider host's carrier is keyed on the full applications inventory and each mesh member carries its own wireguard interface there. The stage-wide applications_carrier park now also parks the provider view (this host's variables under the provider host's group_names and NETWORK_MODE) on hosts that are not the onion-serving provider host, keyed like the applications carrier plus [provider host, NETWORK_MODE]; lookup('email') serves it only on an exact match and renders fresh otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:11:30 +01:00
kevinveenbirkenbach ca722a00b7 Merge branch 'fix/web-app-dashboard/build-once' into feature/store-i18n 2026-11-01 10:05:32 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c1d1d377c6 fix(web-app-dashboard): build the dashboard image once for all network instances
Every network instance (dashboard, dashboard-onion) carried the full build block, so compose built the same dashboard_custom tag twice in parallel. In CI run 41261531965 (odoo#0) dockerd 29.9.2 died with SIGSEGV inside BuildKit during that double build and stopped the stack. Now only the first instance builds; its siblings run the same tag with pull_policy never in compose, the pattern bookwyrm, espocrm and openproject use. Swarm builds, labels and pushes the image once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:58:58 +01:00
kevinveenbirkenbach 7e3c290d51 Merge branch 'feature/stalwart/forward-platform-admin-mail' into feature/store-i18n 2026-11-01 09:42:13 +01:00
kevinveenbirkenbachandClaude Opus 5.5 629fe2375d style(tests): apply ruff format to test_migrate_v016
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:42:06 +01:00
kevinveenbirkenbach d2443e3451 Merge branch 'fix/web-app-stalwart/migrate-verify' into feature/store-i18n
# Conflicts:
#	roles/web-app-stalwart/files/shell/migrate_v016.sh
2026-11-01 09:35:07 +01:00