Commit Graph
1421 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 f51ab74697 perf(sys-dns-cloudflare-records): poll record jobs every second while collecting
The collect step polled each async record job every 5 s, so a job that
finished right after a poll idled up to 5 s before its result was read.
The interval is now cloudflare_collect_interval (1 s), and the poll cap
derives from it, so the attempt's collect deadline, the retry split and
ignore_errors behave exactly as before.

🎯 Fix: 92 %
💥 Break: 3 %
🌱 Efficiency: –
⚡ Speed: –
🛡️ Security: ~100 %
🏆 Score: 89
🚦 Gate: ✅ 90/5, efficiency and speed <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:21:37 +00:00
kevinveenbirkenbachandClaude Opus 5.5 59889bb5af perf(sys-dns-cloudflare-records): skip records the zone already holds as declared
Every deploy sent each declared record through cloudflare_dns, one async
job per record, although nearly all of them already stood unchanged; the
galaxyserver 2fd6c6d deploy spent about two minutes collecting them.

A new action plugin, cloudflare_records_converged, reads each zone once
(all pages) and splits the declared records into pending and converged.
It judges A, AAAA, CNAME, MX, TXT and SRV with the same lookup key and
the same defaults the community.general 14.0.0 module applies; every
other type, every solo or absent entry, and every record set whose
entries would undo each other stay pending, as do all records of a zone
that cannot be read (with a warning). Only pending records reach the
unchanged apply and retry loop, so failures stay per record and loud.

The test pins the plugin's defaults against the module's argument_spec
and the reviewed collection version, and covers the hazardous groups
with a mutation check.

🎯 Fix: 92 %
💥 Break: 3 %
🌱 Efficiency: –
⚡ Speed: –
🛡️ Security: ~100 %
🏆 Score: 89
🚦 Gate: ✅ 90/5, efficiency and speed <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:18:57 +00:00
kevinveenbirkenbach 025ebe9754 Merge remote-tracking branch 'origin/main' into feature/store-i18n 2026-11-04 23:28:17 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a63cbcb1f7 fix(apparmor): leave a read-only /proc/sys alone in teardown and restore
Fork run 41911075483 (feature/store-i18n 2fd6c6d310) lost both workspace
jobs, debian:13 and ubuntu:26.04, in make security-apparmor-teardown:

  common.sh: line 75: /proc/sys/kernel/apparmor_restrict_unprivileged_unconfined: Read-only file system

Inside the workspace container /proc/sys is mounted read-only, and the
write that #1371 added ran under set -e. A sysctl that cannot be written
now leaves the restriction as it is with a log line instead of failing
the teardown, and only a successful write records the previous value.
Restore keeps its record when it cannot write the sysctl back.

The tests call the helpers under set -euo pipefail like teardown.sh and
use /proc/sys/kernel/ostype as a sysctl nobody can write; both new tests
fail against the previous common.sh.

| 🎯 Fix | 💥 Break | 🌱 Efficiency | ⚡ Speed | 🛡️ Security | 🏆 Score | Gate |
| --- | --- | --- | --- | --- | --- | --- |
| 95 % | 3 % | – | – | ~100 % | 92 | ✅ 90/5, efficiency and speed <= 100, security 100 % |

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:25:38 +01:00
kevinveenbirkenbachandClaude Opus 5.5 4596f81e01 test(svc-runner): read the role files through the ROLE_FILE constants
test_file_spot rejected the hand-built vars/meta paths in the new
compose project test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:39:08 +01:00
kevinveenbirkenbach 2fdf419d7a Merge branch 'fix/svc-runner/network-project-preflight' into feature/store-i18n 2026-11-04 21:21:21 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a57e9b944f fix(svc-runner): run the runner stack as its entity's compose project
svc-runner started its stack with 'compose --chdir /opt/github-runner up'
and no --project, so the wrapper named the project after the directory,
github-runner, while every other role uses its entity name. Pass 2 of the
swarm drill in fork run 41744154601 (svc-runner#0) then stopped in the
pre-flight: network 'runner' belongs to compose project 'github-runner'.
The deploy now passes --project from entity_name, and a unit test renders
the real task, runs the real wrapper and checks the pre-flight accepts the
network it creates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:17:13 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c0dd930d29 fix(merge): run the #1212 rehash before compose pull and every compose up
The main merge placed main's rehash handler behind store-i18n's compose pull
and build handlers, so pull ran against stale hashed config names, and the
store-i18n-only lone recreate path (compose-service-up) never rehashed at all.
The handler now sits right after the instance-dir guard, as on main, and
listens on compose-service-up too. The test covers every handler that runs
compose up instead of assuming there is only one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:01:08 +01:00
kevinveenbirkenbach fb004b88a0 Merge remote-tracking branch 'origin/main' into feature/store-i18n
# Conflicts:
#	roles/web-app-mattermost/meta/services.yml
2026-11-04 19:43:38 +01:00
kevinveenbirkenbach 70f3716fbe Merge pull request #1370 from infinito-nexus/fix/update/follow-warn-on-unreadable-digest
fix(update): say when follow cannot read Docker Hub, and retry a 429
2026-11-04 19:16:52 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c068e44d1b fix(update): treat an incomplete Docker Hub response as unreadable
followed_tags caught OSError, ValueError, KeyError and RuntimeError, but
http.client.IncompleteRead, BadStatusLine and LineTooLong derive from
http.client.HTTPException only. One truncated digest response therefore
escaped to collect_updates (docker.py:344, no try) and aborted the whole
Docker update run instead of printing the follow warning for that image.
Every other fetch site of the updater already catches HTTPException
(docker.py, pip.py).

Reported by CodeRabbit on #1370.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:16:16 +01:00
kevinveenbirkenbach 4367272f8f Merge pull request #1348 from infinito-nexus/fix/sys-svc-compose/config-hash-lag/1212
fix(sys-svc-compose): recompute config and secret hashes before compose up
2026-11-04 19:08:59 +01:00
kevinveenbirkenbachandClaude Opus 5.5 248c9c5748 refactor(web-app-funkwhale): drop the same_origin prefix from the module sources
test_prefix_grouped_files flagged roles/web-app-funkwhale/files/python/same_origin/, whose three files all started with same_origin_; they were untracked while the lint ran in the objstore worktree. The sources are now settings.py, storage.py and urls.py, volumes.yml mounts them under the unchanged targets /app/same_origin_settings.py, /app/same_origin_storage.py and /app/same_origin_urls.py, so module names and DJANGO_SETTINGS_MODULE stay the same; the mirrored tests and the network_modes.md link follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:28:29 +01:00
kevinveenbirkenbachandClaude Opus 5.5 83923b27ad Merge branch 'fix/objstore/app-proxy-delivery' into feature/store-i18n
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:20:42 +01:00
kevinveenbirkenbachandClaude Opus 5.5 80fc8487a8 Merge branch 'fix/web-app-gitea/ldap-source-converge' into feature/store-i18n
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:10:26 +01:00
kevinveenbirkenbachandClaude Opus 5.5 6f491381be fix(web-app-gitea): converge duplicate LDAP auth sources and keep the bind password off the host argv
Setup ran add-ldap on every deploy under the name LDAP ({{ DOMAIN_PRIMARY }}); the name column compares case-sensitively, so a casing change created a second source (echoserver had LDAP (Infinito.Nexus) ID 1 with 331 users and LDAP (infinito.nexus) ID 3 with 12), and setup updated only the new one. Cleanup looked the source up with an awk regex in which the parentheses grouped, so it never matched. Setup and cleanup now find LDAP sources by their type (LDAP (via BindDN)), the source is named LDAP, and gitea_ldap_source_plan decides from the per-source user and external-login counts (files/sql/auth-source-users.sql through database_query, only when more than one source exists): keep the source users are linked to, else the lowest id, and delete the others; users on more than one source fail the deploy loudly and delete nothing. The bind password now travels on stdin, is read on the host, forwarded with container exec -e and expanded only by the shell inside the container, with no_log; it left GITEA_LDAP_AUTH_ARGS. On echoserver the 12 users of ID 3 were moved to ID 1 by hand first, so the next deploy keeps ID 1 and deletes ID 3.

Residual: the Gitea CLI takes the password only as --bind-password, so it sits in the argv of the in-container gitea process while add-ldap or update-ldap runs. The IAM line's 5f152d9f28 passes it through task environment: and keeps the name lookup; at the IAM merge take this side for the LDAP tasks and the GITEA_LDAP_* vars.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:10:09 +01:00
kevinveenbirkenbach f40964991a Merge branch 'fix/update/pacman-sig-404' into feature/store-i18n
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	roles/update/vars/main.yml
2026-11-04 17:46:11 +01:00
kevinveenbirkenbachandClaude Opus 5.5 1c561a1e7d test(install): keep role-file paths out of the stage fixtures
tests/lint/ansible/roles/test_file_spot.py rejects a hard-coded
"roles/a/tasks/main.yml" in test_stage.py and asks for the
ROLE_FILE_TASKS_MAIN constant. The fixture only needs some nested file,
not a role, so it uses pkg/a/data.yml and pkg/a/.git instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:35:09 +01:00
kevinveenbirkenbachandClaude Opus 5.5 330b85f9e7 fix(apparmor): lift the unprivileged-unconfined restriction in teardown
On runner image 20261101 kernel.apparmor_restrict_unprivileged_unconfined
is on, so the kernel turns runc's change_onexec into the stack
docker-default//&unconfined. docker-default grants no signal to an
unconfined peer, and every in-container kill() is denied; run 41743807934
lost four package builds that way (journal: apparmor="DENIED"
operation="signal" comm="rsync" signal=usr2).

teardown.sh now writes 0 to the sysctl and records the value it found
under /run/infinito; restore.sh writes that value back and drops the
record. A missing sysctl or one already at 0 is left alone, and a second
teardown keeps the first record. CI runs teardown before every deploy,
workspace and dns job; dev hosts through make environment-bootstrap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:35:08 +01:00
kevinveenbirkenbachandClaude Opus 5.5 fd05f85d44 fix(install): stage the package build workspace with tar, not rsync
Run 41743807934 lost Matrix#2, Workspace (debian:13), Gitea#0 and Wazuh#0
to the full job budget right after "Preparing build workspace". On runner
image 20261101 the kernel stacks every container onto
docker-default//&unconfined, which refuses every in-container kill(); the
host journal logs apparmor="DENIED" operation="signal" comm="rsync"
signal=usr2. rsync's local copy waits for exactly that SIGUSR2 and sleeps
until the job is killed.

scripts/install/stage.sh copies the tree through a tar pipe, which sends
no signal; --format=posix keeps nanosecond mtimes. package.sh uses it on
the arch and debian paths and installs tar instead of rsync.

Copying this repository both ways gives identical content, modes, types,
link targets and mtimes on all 11306 entries. test_stage.py runs stage.sh
under a seccomp filter that fails every signal syscall with EACCES; the
old rsync copy hangs past its timeout under the same filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:35:08 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c62b3d2e53 fix(web-app-gitea): apply ROOT_URL on every start so the SSO callback matches Keycloak
On a long-lived instance the role's DOMAIN, ROOT_URL and SSH_DOMAIN variables never reach app.ini: the image's setup script writes them only when app.ini does not exist yet and applies only GITEA__section__KEY variables on later starts. echoserver's Gitea therefore still runs with ROOT_URL https://code.cymais.cloud/, sends that host as the OAuth2 callback, and Keycloak answers 'Invalid parameter: redirect_uri' (its log shows exactly https://code.cymais.cloud/user/oauth2/SSO%20Login%20%28INFINITO.NEXUS%29/callback). env.j2 now sets GITEA__server__DOMAIN, GITEA__server__ROOT_URL and GITEA__server__SSH_DOMAIN. The OAuth2 source is named 'SSO Login', because external_login_user.provider holds 25 characters and the 26-character domain-suffixed name would break every account link; setup and cleanup find the source by its type instead of by name, so the rename happens in place and no second button appears. OpenID 2.0 sign-in and sign-up are switched off. CI missed it because every CI run installs fresh and the Gitea specs never click the SSO button.

The IAM line's 144871ada1 carries the same rename and lookup plus a patched Gitea build for onion sign-in, but keeps the plain ROOT_URL variable; at the IAM merge keep these GITEA__server__ lines together with that patch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:23:36 +01:00
kevinveenbirkenbachandClaude Opus 5.5 ac384eb872 fix(objstore): serve Decidim and Funkwhale object-store files from the app's own origin
Decidim#0 and Funkwhale#0 of fork run 41744154601 failed on the tor rows because both apps handed the browser presigned or public URLs on the object-store host, which publishes no onion vhost. Decidim now sets STORAGE_CDN_HOST to its own origin and switches Active Storage to rails_storage_proxy; an initializer routes direct uploads through the disk-service token route (Rails writes them to S3 server-side), points the redirect and legacy blob routes and the rich-text BlobRenderer at the proxy, safelists storage reads in Rack::Attack, and refuses to boot when the patched activestorage 7.2.2.2 / decidim 0.31.6 signatures change. Funkwhale runs a settings module with a storage whose public names (attachments, sized images, dynamic preferences) resolve to MEDIA_URL and whose exports get a signed one-hour token link; a /media view checks every name against clean_name and rejects backslashes and percent signs, signs per request method and answers with X-Accel-Redirect; the front nginx routes those paths to the API, sends relative protected covers back through it and no longer forwards X-Forwarded-Host, which broke the presigned signature. Both apps reach the store over its internal URL; the public-host CSP entry, extra_hosts entry and CA bundle are gone. The Playwright specs assert the direct upload goes to the app and accept no object-store origin.

Verified by compose deploys of both roles, clearnet update and tor-only reinstall, failed=0 with green Playwright and no object-store host in the rendered pages or APIs; swarm and Decidim variant 1 (disk storage) are not deployed yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:17:29 +01:00
kevinveenbirkenbach dedecefcbd Merge branch 'feature/web-app-wordpress/options-simplify' into feature/store-i18n
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	roles/web-app-wordpress/vars/main.yml
2026-11-04 12:58:32 +01:00
kevinveenbirkenbachandClaude Opus 5.5 268b2a46f9 feat(web-app-wordpress): set admin_email, blogname and the FreedomTranslate options through the options map
WORDPRESS_OPTIONS now merges the role's own contributions (admin_email from the platform administrator, blogname from the design title while the design service is on, the freedomtranslate-wp config.options while that addon is on) under services.wordpress.options, so the inventory still wins and wordpress_options_checked guards the merged map. design.php no longer writes blogname, the addon task no longer writes its options, and WORDPRESS_OPTIONS_OWNED_ELSEWHERE drops the three. Every deploy now converges admin_email, and blogname while the design is on; a write WP-CLI reports as unchanged no longer counts as a change, and the FreedomTranslate readback runs after the options write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:53:11 +01:00
kevinveenbirkenbachandClaude Opus 5.5 50ee46ed22 refactor(provision): group the services disabler modules in a services package
test_prefix_grouped_files flagged cli/administration/inventory/provision/services_disabler.py and services_disabled_conflicts.py, which share the 'services' prefix since the conflict check moved into its own module. Both now live in cli/administration/inventory/provision/services/ as disabler.py and disabled_conflicts.py; every importer, the purge script, the BLE001 per-file ignore and the requirement 037 link follow the new path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:23:07 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c3e2114e91 Merge branch 'fix/web-app-wordpress/permalink-options' into feature/store-i18n
Conflicts resolved: the inventory validator keeps store-i18n's custom-role
declaration skip and gains the open-subtree check; the suppression catalogue
keeps both new rows; WORDPRESS_PERMALINK_STRUCTURE gives way to the options
map. WORDPRESS_OPTIONS_OWNED_ELSEWHERE also lists the store-i18n writers the
merge brought along: the design logos (WORDPRESS_DESIGN_LOGOS keys) and the
freedomtranslate options; two custom-role validator tests pass open_paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:35:18 +01:00
kevinveenbirkenbachandClaude Opus 5.5 46e0bd1367 feat(web-app-wordpress): manage WordPress options from services.yml and pin the old permalink structure
Deploys since 197d74416d rewrote the blog's permalink structure to
/%postname%/, so links of the form /blog/YYYY/MM/DD/<slug>/ answer 404
(docs/security/user.md:24 fails the URL check).

services.wordpress.options maps WordPress option names to values; the role
reads each option, writes only the ones that drift and flushes the rewrite
rules after a write. permalink_structure is pinned to
/blog/%year%/%monthnum%/%day%/%postname%/, the value WordPress itself sets
for a subdirectory network. A guard refuses options the role writes
elsewhere, secret-named keys and values WordPress would not read back
unchanged.

The inventory validator accepts any child key under a services.yml key that
carries '# nocheck: inventory-open-subtree <reason>'; services.wordpress.options
carries it, everything else stays strict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:30:31 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e9779c4c7b refactor(provision): move the disable/inventory conflict check into its own module
services_disabler.py grew past the 500-line limit with the dependents step
(gate pass 16, test_python_file_size). The conflict check moves unchanged to
services_disabled_conflicts.py; the YAML helper it shares becomes public
(load_yaml_mapping_tolerant) and the two importers follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:24:38 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c5a69ee951 test(web-app-discourse): build the vars path from ROLE_FILE_VARS_MAIN
test_file_spot flagged the hand-written vars/main.yml path in the chat
integration test (gate pass 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:20:58 +01:00
kevinveenbirkenbachandClaude Opus 5.5 b5543b6d62 fix(update): say when follow cannot read Docker Hub, and retry a 429
Update run 41722395224 proposed no sys-os bump although fedora:latest
points at 45 and a local dry run proposes 44 -> 45. followed_tags turned
every failed digest lookup into "no candidate" without a word, so the run
cannot tell a rate limit from a pin that is current.

tag_digest now retries an HTTP 429 twice with a short backoff, as the tag
listing does, and followed_tags reports on stderr what it compared and
which tags it kept, or the error that made it propose nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:20:00 +01:00
kevinveenbirkenbach 3d7c9da23e Merge branch 'fix/front-inj-design/css-per-network' into feature/store-i18n 2026-11-04 10:53:49 +01:00
kevinveenbirkenbach 28bbf6dc21 Merge branch 'fix/web-app-discourse/chat-integration-enable' into feature/store-i18n 2026-11-04 10:53:49 +01:00
kevinveenbirkenbachandClaude Opus 5.5 01e27bfddb fix(sys-front-inj-design): store and link the role stylesheet per network
The design inject renders a role's style.css once per vhost but copied every
render to one shared CDN path, so the last render won. xwiki's stylesheet
embeds its own origin; on a multi-network node the onion render landed last
and every clearnet page loaded onion URLs, failing network-leak.spec.js
whenever the pass picked clearnet.

The first network keeps style.css, every further network gets
<label>-style.css (onion-style.css), keyed by the same rule the tls lookup
uses for the render (serving_network), and each vhost links its own file.
Single-network roles keep the exact old path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:58 +01:00
kevinveenbirkenbachandClaude Opus 5.5 09e855ae1b fix(web-app-discourse): enable the chat integration and the bridged partners' providers
The chat-integration plugin ships with core and defaults to
chat_integration_enabled: false, while the addon task was empty, so the
spec's first assertion could never pass and topic notifications never left
Discourse for Matrix or Mattermost.

The addon now turns on chat_integration_enabled plus the provider of every
partner the round deploys, idempotently, reads the settings back and fails
when one does not stick. The spec asks the plugin's real admin route
(/admin/plugins/discourse-chat-integration/providers.json; the old path is a
404 on v2026.10.0) and requires an enabled partner provider, since the
catalogue now lists disabled providers too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:44:19 +01:00
kevinveenbirkenbachandClaude Opus 5.5 1ed3fd11da fix(provision): disable the services gated on a provider the row disables
A tor-only CI row disables web-svc-coturn, which no onion can serve, but the
disabler switched off only service entries keyed coturn. Nextcloud's talk
entry is gated on 'web-svc-coturn' in group_names and variant 0 pins it true,
so Talk stayed on without a TURN server and 01_fullstack.yml's assert stopped
every Nextcloud#0 tor row.

When disable resolves to a provider role P, every entry whose base enabled is
exactly the presence test of P is written false as well, and the reconcile
releases those entries once P is no longer disabled. Negated, compound and
variable gates are left alone. Across all 755 planner row states this changes
one value an app sees: talk on Nextcloud#0 tor rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:41:09 +01:00
kevinveenbirkenbachandClaude Opus 5.5 be4c1b2394 fix(update): stop retrying the 404 of pacman's optional database signature
pacman's DatabaseOptional fetches <repo>.db.sig after every database; Arch
publishes none, so every mirror answers 404. The XferCommand ran curl with
--retry-all-errors, which retried that permanent 404 three times, 2 s apart,
for about 18 s per refresh on every Arch host (deploy log 17.4-18.3 s).

pacman-xfer keeps retrying transient failures (transport errors, 5xx, every
4xx but 404/410), honours Retry-After on 429/503 up to 30 s and drops a failed
attempt's partial bytes like curl's own retry did; 404 and 410 fail at once.
The shell-download-retry lint accepts a download its script retries in a
bounded counter loop that leaves on success and sleeps between attempts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 09:06:45 +01:00
kevinveenbirkenbach 107bc77cc7 Merge remote-tracking branch 'origin/main' into fix/sys-svc-compose/config-hash-lag/1212 2026-11-03 23:03:40 +01:00
kevinveenbirkenbach 9f4be23164 Merge remote-tracking branch 'origin/main' into feature/store-i18n 2026-11-03 22:56:04 +01:00
kevinveenbirkenbach 78b1948f56 Merge remote-tracking branch 'origin/main' into fix/sys-svc-compose/config-hash-lag/1212 2026-11-03 22:49:47 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c3c0e9db77 fix(swarm): test service membership without a pipe into grep -q
_context.sh runs under set -euo pipefail and tested membership with
`printf '%s\n' "${_names}" | grep -qxF <name>`. grep -q exits on the first
match; when printf still has lines to write it dies of SIGPIPE (141), and
pipefail turns the matched test into false. resolve_db_service then fell
through to the central <dep>_<dep> name or printed nothing, and
has_swarm_service reported a deployed service as missing, so
skip_if_no_swarm_service let the converge and chaos gates exit 0 untested.
The race grows with the stack's service count and with CPU load (store-i18n
gate pass 11: 'mariadb_mariadb' != 'magento_database').

The three tests read the listing from a here-string, which is no pipeline.
Two regression tests put the match first in a 10000-name listing and fail
on every run against the old helper. Root cause found by i18n 2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 22:49:39 +01:00
kevinveenbirkenbach 8690590e40 Merge remote-tracking branch 'origin/main' into feature/store-i18n
# Conflicts:
#	cli/build/docs/integration_matrix/__main__.py
#	cli/build/docs/integration_matrix/data.py
#	docs/requirements/027-integration-matrix.md
2026-11-03 22:42:56 +01:00
kevinveenbirkenbach de98d94fae Merge remote-tracking branch 'origin/main' into fix/sys-svc-compose/config-hash-lag/1212 2026-11-03 22:07:58 +01:00
kevinveenbirkenbach 715820d6c1 Merge remote-tracking branch 'origin/main' into fix/ci/pin-dev-runtime-images 2026-11-03 22:07:52 +01:00
kevinveenbirkenbachandClaude Opus 5.5 7ac795c0cb test(web-app-gitlab): build the runner-stdin environment with select_autoescape
ruff S701 flagged the jinja2 Environment without autoescape in the single-boot rails test; select_autoescape(default_for_string=False) keeps the rendered Ruby stdin unescaped, the repository's pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:56:06 +01:00
kevinveenbirkenbachandClaude Opus 5.5 81ef94e111 style(dev-python): fold the env filter onto one line after the rename
DEV_PYTHON_REQUIRES is shorter than the old name, so ruff format joins the
dict comprehension in test_install.py onto one line, and the CI format
check would report the drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:47:46 +01:00
kevinveenbirkenbach a70802eb1b Merge branch 'perf/web-app-gitlab/one-rails-boot' into feature/store-i18n 2026-11-03 21:43:54 +01:00
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 6e2166d825 perf(web-app-gitlab): converge root, platform admin and appearance in one Rails boot
Three separate 'rails runner -' calls each paid a full Rails boot (47 s, 42 s and 39 s on the echoserver production deploy). platform_admin.yml now runs one runner whose stdin is a small step driver (run_steps.rb) followed by the unchanged root-password, platform-admin and appearance scripts, each wrapped in its own InfinitoSteps.run block: every step keeps its own [CHANGED]/[FAIL] verdict prefixed with its name, exit(0) ends only that step, and the run fails naming every failed step. Rails 7.2.3.1 evals runner stdin at TOPLEVEL_BINDING inside an executor that passes SystemExit through; puts writes to the swapped $stdout while the logger writes to files. The design render and slurp move to tasks/utils/design.yml ahead of it, keeping the numbered includes ascending, and the file layout leaves feature/iam-profile-rbac's hunks on platform_admin.yml and 00_core.yml mergeable without conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:33:37 +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