Commit Graph
596 Commits
Author SHA1 Message Date
kevinveenbirkenbach fb125e163d Merge branch 'main' into feature/designer 2026-10-15 11:32:54 +02:00
kevinveenbirkenbach 91d2236803 Merge branch 'main' of github.com:infinito-nexus/core 2026-10-15 10:57:49 +02:00
kevinveenbirkenbach 2b43cd666f Merge remote-tracking branch 'origin/main' into feature/designer 2026-10-15 02:39:36 +02:00
kevinveenbirkenbach b637742f96 Merge remote-tracking branch 'origin/main' into fix/env/distro-derived-keys/794 2026-10-15 01:07:53 +02:00
kevinveenbirkenbach efb816d93b Merge branch 'main' of github.com:infinito-nexus/core 2026-10-14 13:35:17 +02:00
kevinveenbirkenbachandClaude Opus 5.5 8a29ba0bfd fix(images): leave eol roles out of image discovery
The scheduled mirror job has failed every day since 2026-09-25 on the same two
images: quay.io/minio/minio and quay.io/minio/mc answer `unauthorized` to
anonymous pulls ("2/197 failed", run 38352569503). Both belong to
web-app-minio, an eol role outside the lifecycle envelope that default.env
tests, so no CI deploy pulls them.

iter_role_images now skips every role that is_maintained() reports as
unmaintained, the check the version updaters already apply to the same list.
On the tree the discovery yields 187 images instead of 192; the five it drops
belong to the four eol roles web-app-confluence, web-app-jira, web-app-minio
and web-app-phpldapadmin. The mirror sync, resolver and wait read this list,
and their descriptions and mirror.md now say so.

The roles directory is resolved first: is_maintained reads a relative path as
a role name and would keep every role.

475ab9b386 on feature/store-i18n takes the same approach with a lifecycle
reader of its own; main already has is_maintained for it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 01:19:28 +02:00
kevinveenbirkenbachandClaude Opus 5.5 3fff830b9c feat(skills): add i8-cleanup on top of the portable cleanup skill
Routes disk and /tmp cleanup through the cleanup skill and adds the repository rules: compose-project attribution, the shared cache stack that is never a candidate, act and swarm keep rules, teardown targets with what each destroys, and the targets that must never run as a cleanup step. Listed in the agent cheatsheet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:57:20 +02:00
kevinveenbirkenbachandClaude Opus 5.5 1079f48feb fix(env): derive the image and container names from the distro under test
A compose deploy job labelled centos or arch built its stack image from
pkgmgr-debian and ran it as infinito_nexus_debian (#794). distros.sh
exports INFINITO_DISTRO per iteration and sources load.sh again, but the
loader returned early because INFINITO_ENV_LOADED and the .env mtime were
unchanged. A full reload would not have helped either: the debian values
were already exported, so the restore step kept them as caller intent.

load.sh now records the distro it loaded for. Another INFINITO_DISTRO
defeats the early return and drops INFINITO_CONTAINER,
INFINITO_PARENT_IMAGE and INFINITO_IMAGE, the three keys the env handlers
derive from the distro. When the requested distro also differs from the
one .env was generated for, the loader sources a set that
`python -m cli.meta.env --output` generates for it in a private temp
directory and leaves .env untouched, so a local multi-distro run does not
switch the checkout's distro. A caller value set for the loaded distro
still wins.

Verified: tests/unit/python/scripts/meta/env/test_load.py and the new
distros.sh case pass. Against main's loader both fail with the symptom of
the issue: INFINITO_DISTRO=centos next to infinito_nexus_debian and
pkgmgr-debian:stable, and distros.sh handing debian, arch and centos the
same debian parent. shellcheck is clean.

Consequence: CI jobs labelled arch, ubuntu, fedora or centos now build
and deploy that distro, so a failure that shows up there was masked
before.

feature/store-i18n carries 3f973eb137, which resolves the parent image
for scripts/image/push.sh on its own, and 05e4dbafcb, which inserts the
INFINITO_INSTANCE slot check into the block this commit restructures. The
merge keeps the slot check ahead of the distro check.

Closes #794

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:30:48 +02:00
kevinveenbirkenbachandClaude Opus 5.5 25a1f08ab7 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>
2026-10-12 17:09:33 +02:00
kevinveenbirkenbachandClaude Opus 5.5 a43f15b539 Merge branch 'main' into feature/designer
Conflicts:
- roles/web-app-pihole/meta/services.yml: main dropped the unused redis
  entry, this branch changed the javascript entry directly below it. The
  redis entry is gone and the javascript entry keeps the flag bound to
  the SSO flag of the role and its nocheck reasons.
- README.md: the generated role table keeps the renamed web-svc-design
  role and lists Pi-hole without Redis and without the CDN.

The role diagram of Pi-hole is regenerated, because both sides removed
an edge and the index of the red link moved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 17:09:52 +02:00
kevinveenbirkenbachandClaude Opus 5.5 c35f2409a4 fix(web-app-odoo): make the OAuth addon work on Odoo 20
Nobody could sign in to Odoo through Keycloak. Odoo sent an http redirect
URI, and Keycloak refused it as "Invalid parameter: redirect_uri".

The addon auth_oauth_https builds that URI from web.base.url. Two things
kept it from doing so after the image moved to Odoo 20.

Its manifest declared version 19.0.1.8.0, and Odoo marks a module whose
version names another series as not installable. It only warns about that,
so the install task stayed green and the addon was missing. The manifest
now declares 1.8.0, which Odoo prefixes with its own series.

Once installed, the addon read the parameter with get_param, which Odoo 20
replaced by typed getters. The call raised AttributeError, a broad except
logged it and fell back to the URL of the request, which is http behind
the proxy. The addon reads the parameter with get_str now and no longer
catches the error.

A deploy now fails when the addon is not installed afterwards. The check
reads ir_module_module, because the output of "odoo -i" does not tell an
installed module from a skipped one.

The spec logged out by navigating to /web/session/logout. Odoo 20 accepts
POST only on that route and answered 405, so both SSO tests failed after
a successful login. The spec logs out through the user menu now.

A host that runs the suite with several workers still failed it. Odoo
computes the session token of an account from its OAuth access token, so
every new sign-in ends the other sessions of that account, and the spec
files share the administrator. The role caps its suite at one worker
with PLAYWRIGHT_MAX_WORKERS, which the central config now honours, and
the spec asserts the cap.

Closes #822

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 11:37:41 +02:00
kevinveenbirkenbachandClaude Opus 5.5 89b95d751c fix(update): stop bumping pins of eol roles
The update bot opened a version PR for web-app-jira (#821) although the role is end of life: none of the updaters looked at the lifecycle. utils.update.base.is_maintained now answers that question once, and the collectors for image versions, declared version sources, pip pins, repository refs and the addon check skip a role whose lifecycle is eol.

The lint that requires every semver pin to be watched shares that scope, otherwise it would report the pins of the eol roles as unwatched. Every other stage, deprecated included, stays in the update scope.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 01:09:10 +02:00
kevinveenbirkenbach 396b8230eb Merge branch 'main' into feature/designer 2026-10-06 00:13:54 +02:00
kevinveenbirkenbachandClaude Opus 5.5 30c9008b6c fix(web-svc-design): keep links apart from body text for extreme bases
For a very dark base color the light palette used the base itself as
link color, which is as dark as body text: #001f3f gave a link at
contrast 1.02 against the text, so links in tables read as plain text.
The dark palette did the same for a very light base (#FFFF00: 1.11).

The lightness the link search starts from is clamped to 0.45..0.55 in
light and 0.70..0.80 in dark mode. #001f3f now yields #37577c in light
mode (2.27 against the text, AA on every surface). Bases inside the
band, #FFA500 among them, produce the same palette as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 23:14:02 +02:00
kevinveenbirkenbachandClaude Opus 5.5 681699a2c5 feat(design): declare the design layer first, make Bootstrap opt-in
The injector knows a third snippet kind, top, and places it right after
the opening head tag. The design role uses it to link layer.css, which
only declares the cascade layer infinito-design. Declared before the
stylesheets of the app, that layer loses to every rule of the app,
layered or not.

The shared Bootstrap component mapping is linked only for a role whose
design entry sets bootstrap: true.

A role stylesheet or design script that renders empty counts as absent,
and a deploy removes the stylesheet a role no longer ships from the CDN.

The two style.css guides describe the layer, the order of in-house
options before the role stylesheet, and the frame and focus tokens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 21:41:15 +02:00
kevinveenbirkenbachandClaude Opus 5.5 739c78bad1 feat(update): bump addon pins through their declared source
The version-source updater only read meta/services.yml. An addon pinned in
meta/addons/<id>.yml stayed where it was: the ONLYOFFICE connector for
Nextcloud sat on v10.1.2, which does not install on Nextcloud 35, until
someone bumped it by hand.

The updater now reads addon files too. An addon declares its source in the
`update:` block it already has, beside monitored, catalog and upstream_id.
A monitored addon of the github-releases catalog needs nothing more: its
upstream_id names the repository whose tags are its versions. The
update-version-sources job opens the PRs, one per role.

Every pinned addon is wired:

- nextcloud onlyoffice carries its own version pin now. Its archive URL
  stays a literal that carries the pin, because
  test_pinned_addon_archives_available reads it as written
- wordpress mcp-adapter, moodle auth_oidc and jellyfin sso-authentication
  follow the tags of their repository, moodle within its v4.5 series
- the three xwiki extensions read their Maven metadata
- pretix-oidc reads the PyPI simple index. The JSON API answers 404 behind
  the package cache the test stack routes pypi.org through
- joomla's plugin is built here and jellyfin's ldap-authentication is bound
  to the md5 of its archive; both are opted out with that reason

A bump rewrites the pin and the config.archive line together, and an
archive that does not carry the pin fails test_version_keys, so the two
cannot drift apart again. test_version_pins_are_watched covers addon pins
as well: a new one without a source or a reasoned opt-out fails the lint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 02:50:48 +02:00
kevinveenbirkenbachandClaude Opus 5 81ab6e4ffc feat(update): let a version pin declare its own upstream source
The Docker updater watches image plus version, the repository updater
repository plus ref. A dozen pins fell outside both - an app_version, a
release, a tag in a registry neither speaks - so nothing ever told anyone
they had gone stale.

Such a pin now names its upstream next to itself in an `update:` block:
git_tags, registry_tags, npm, http_regex or a role-local script, with
`match` selecting a tag flavour and `strip` removing a prefix the pin does
not carry. utils/update/source.py resolves them beside the two existing
updaters, `python -m cli.contributing.update.source` bumps them, the
update-version-sources job opens one PR per role, and
tests/external/update/source warns while a bump is pending.

Two lints hold the rule: every semver pin is watched by one of the three
updaters or opted out with a reason, and a semver only ever sits behind
`version`, `<what>_version` or `ref` - which renames moodle's release to
app_version and simpleicons' local_version to local_tag, the latter being
the tag of a locally built image rather than an upstream version. A
declaration naming a key the entity does not carry, or a type the resolver
does not know, fails the lint instead of resolving nothing in silence.

Picked from feature/store-i18n (94d607fd0a) onto current main. Two things
differ from the original: svc-ai-lmstudio no longer carries an app_version
pin, so its declaration is dropped, and web-app-stalwart's webui_version,
added since, declares the git tags of the webui repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 02:50:48 +02:00
kevinveenbirkenbachandClaude Opus 5.5 5b002678df docs(mcp): remove the generated audit report
The report aggregated each role's meta/mcp.yml into one table, and its
freshness test failed on every image-version bump of an MCP-serving role,
because the updater edits services.yml and nothing regenerates the committed
report. The table added nothing the sources lack: scope and credential subject
were identical for every role, so it restated its inputs.

The generator, the report, its freshness test, make mcp-audit and the i8mcau
alias go, and make docs no longer calls the generator. The metadata stays in
each role's meta/mcp.yml, where test_mcp_schema.py enforces its shape, and
requirement 035 now says so. The classification and completeness lints stay,
because they check the lists in 035 against the roles, not the report.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 14:36:52 +02:00
kevinveenbirkenbach 377a2faec9 Merge pull request #761 from infinito-nexus/update/docker-image-versions-web-app-mattermost-20261002-85cee58
update(images): update Docker image versions (web-app-mattermost)
2026-10-04 07:35:38 +02:00
kevinveenbirkenbachandClaude Opus 5.5 b65164ffaf Merge branch 'main' into feature/designer
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 06:29:52 +02:00
kevinveenbirkenbachandClaude Opus 5.5 86cefeff26 feat(web-svc-design): derive one corporate design from a single base color
web-svc-css becomes web-svc-design and owns the configuration that lived in group_vars/all/13_design.yml. Consumers switch their css service key to design, and sys-front-inj-css becomes sys-front-inj-design. javascript stays an independent key; a role's optional design.js.j2 is injected together with the design and CSP-hashed like the functional script.

An in-repo OKLCH lookup replaces colorscheme_generator. From design.colors.base alone it derives surfaces, text, borders, the primary and status fills with their on-colors, hover and active states and a dedicated dark palette; a unit test proves WCAG AA, and AAA for body text, across six base colors in both modes. Semantic --design-* tokens replace --color-NN-SS in every role stylesheet. The shared element styles sit in the cascade layer infinito-design, so an app's own design system always wins and takes the palette through a variable mapping in its role stylesheet or through its own theming mechanism.

design.logo and design.title resolve globally from web-svc-design or per role from its design entry, and false or 0 disables the replacement. The design_assets action renders the logo lockup per slot as SVG, PNG and ICO, and the legacy title keys move into the design entries.

Three roles carry the design end to end. The dashboard takes logo, favicon and title from the generator and replaces the hard-coded port-ui colors. Gitea maps its theme variables onto the tokens and mounts the generated logo and a branded start page. Nextcloud receives primary color, logo, name and slogan through occ theming and ships no stylesheet.

make design-sync re-renders the static design files of a deployed app, make design-gallery captures before and after screenshots in light, dark, desktop and mobile, and make design-queue orders the roles that are due for a design pass. The i8-designer skill and docs/agents/action/design.md describe that loop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 06:04:14 +02:00
kevinveenbirkenbach 84e65b97ef Merge branch 'main' into update/docker-image-versions-web-app-baserow-20261002-4836a00 2026-10-04 06:01:17 +02:00
kevinveenbirkenbach f4dd4258be Merge pull request #764 from infinito-nexus/update/docker-image-versions-web-app-pretix-20261002-8a50f8b
update(images): update Docker image versions (web-app-pretix)
2026-10-04 05:57:03 +02:00
kevinveenbirkenbach 5bcf193720 Merge pull request #759 from infinito-nexus/update/docker-image-versions-web-app-gitea-20261002-8fa3a54
update(images): update Docker image versions (web-app-gitea)
2026-10-04 05:56:19 +02:00
kevinveenbirkenbachandClaude Opus 5.5 9ee4b5c60a docs(mcp): regenerate the audit report for the pretix 2026.8.0 bump
test_the_committed_report_is_freshly_generated compares the committed
table against a fresh render. The image updater edits only
meta/services.yml, so the pretix row still read 2026.7.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 03:14:26 +02:00
kevinveenbirkenbachandClaude Opus 5.5 6e216029d8 docs(mcp): regenerate the audit report for the mattermost 11.11.1 bump
test_the_committed_report_is_freshly_generated compares the committed
table against a fresh render. The image updater edits only
meta/services.yml, so the mattermost row still read 11.11.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 03:14:07 +02:00
kevinveenbirkenbachandClaude Opus 5.5 e42d6d0fef docs(mcp): regenerate the audit report for the gitea 28.0.0 bump
test_the_committed_report_is_freshly_generated compares the committed
table against a fresh render. The image updater edits only
meta/services.yml, so the gitea row still read 1.27.3.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 03:13:51 +02:00
kevinveenbirkenbachandClaude Opus 5.5 bca71ae8b1 docs(mcp): regenerate the audit report for the checkmk 2.4.0p37 bump
test_the_committed_report_is_freshly_generated compares the committed
table against a fresh render. The image updater edits only
meta/services.yml, so the checkmk row still read 2.4.0p32.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 03:13:45 +02:00
kevinveenbirkenbachandClaude Opus 5.5 827e63ba1f docs(mcp): regenerate the audit report for the baserow 2.4.0 bump
test_the_committed_report_is_freshly_generated compares the committed
table against a fresh render. The image updater edits only
meta/services.yml, so the baserow row still read 2.3.4.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 03:11:57 +02:00
kevinveenbirkenbachandClaude Opus 5.5 42bdf4321f docs(roles): stop testing deprecated roles
lifecycle.md said a deprecated role is still covered by the test suite while the tested envelope already left it out. A deprecated role is now untested: the matrix-deploy MUST NOT exercise it and a failure in it MUST NOT block a release, while planned, pre-alpha and eol stay open to best-effort runs. The complexity legend says untested too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 21:11:51 +02:00
kevinveenbirkenbachandClaude Opus 5.5 80082553c7 docs(roles): point the allowed lifecycle values at lifecycle.md
layout.md listed six of the nine lifecycle stages the layout lint accepts and left out rc, maintenance and eol. It now defers to the stages in lifecycle.md, which already define every value, deprecated included, with its criteria.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 21:11:50 +02:00
kevinveenbirkenbachandClaude Opus 5.5 b1311c8f87 test(ci): pin every release tag to the subject the push workflow recognises
entry-push-latest.yml lets a push to main cancel the running pipeline only when its head commit starts with the release subject pkgmgr writes. The new integration test reads that prefix from the workflow and the tag schema from scripts/github/resolve/release/tag.sh, then requires every release tag from v13.0.0 on to sit on "Release version X.Y.Z". Six tags between v10.1.0 and v11.6.0 carry "Release X.Y.Z", so the check starts at the first tag of the uniform run.

call-test.yml now checks out with full history and tags, the pair detect-release already relies on; a depth-1 checkout carries no tags and would skip the test. Forks and tagless clones skip it on purpose.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 19:57:30 +02:00
kevinveenbirkenbachandClaude Opus 5.5 9281a17195 ci(push): let a release push on main supersede the running run and deploy every chunk
A push to main whose head commit starts with "Release version " (the subject pkgmgr writes for every release) now cancels the main run in progress, so the release starts at once instead of queueing behind it. Every other push to main still leaves the running pipeline alone.

When the pushed main commit carries a vX.Y.Z tag, the orchestrator runs with chunk_gate false: every chunk deploys and reports, and a failed chunk still turns the run red, so release-guard and release-version stay skipped.

entry-cancel-superseded.yml mirrors the new rule behind its push guard, parenthesised so && does not bind the release clause; the mirror test composes the expected rule the same way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 19:57:30 +02:00
kevinveenbirkenbach aa55c8c8bd Merge branch 'main' into feature/role-stalwart-mail-swarm 2026-10-02 17:40:09 +02:00
kevinveenbirkenbachandClaude Opus 5.5 5e0bcee6f5 docs(agents): point the Assumption 10 link at its current heading
7ed60e1fc2 renamed the Assumption 10 heading in security.md, and the
link in sandbox.md still used the short #assumption-10 fragment, which
no heading slug matches. It now carries the full slug of the new
heading, the form 4efa892706 on feature/store-i18n uses for the old
one, so that branch's merge resolves the line by keeping main's side.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:57:31 +02:00
kevinveenbirkenbachandClaude Opus 5.5 4e1c8e2de1 fix(agents): deny rm -rf and the host credential files in the sandbox
An external report flagged the Bash(*) allow in the committed
.claude/settings.json. Bash(*) stays: every command still runs inside
the OS sandbox, and deny and ask take precedence over it. Reviewing the
file against its own documentation turned up two real gaps instead.

security.md and settings.md cite an rm -rf* deny rule as the guard
against `rm -rf .` wiping the worktree and .git/, but the rule was lost
in ee30f6d446 when the deny list was rewritten. It is back.

denyRead covered ~/.aws, ~/.config/gcloud, ~/.gnupg and ~/.kube only.
~/.docker/config.json holds registry logins inline, ~/.git-credentials
and ~/.netrc hold plaintext credentials, and the default ~/.ssh/id_*
keys were readable because Assumption 10 kept ~/.ssh open for an
ask-gated git push that has since moved to deny. All nine paths are
denied now. The rest of ~/.ssh stays readable, so SSH fetches keep
known_hosts and authenticate through the agent socket. Sandboxed docker
calls pull anonymously and print a config warning on stderr.

Thanks to Hanno Horrmeyer for the report.

Reported-by: Hanno Horrmeyer
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:57:30 +02:00
Alejandro Roman 04a657649c Merge remote-tracking branch 'upstream/main' into feature/role-stalwart-mail-swarm 2026-09-23 20:41:12 +04:00
kevinveenbirkenbachandClaude Opus 5.5 f47ca6e7c0 chore(agents): deny git commit --amend
An agent cannot see whether the operator committed on top of its last
commit in the meantime. An amend then rewrites a commit it never
authored: it happened to a signed, tagged release commit, which only a
soft reset onto the original recovered. Follow-up changes land as a new
commit instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:49:02 +02:00
Alejandro Roman 9df3287adf Merge remote-tracking branch 'upstream/main' into feature/role-stalwart-mail-swarm 2026-09-22 12:16:44 +04:00
kevinveenbirkenbachandClaude Opus 5 f40d627a1b docs(agents): merge the tool-specific files into AGENTS.md
CLAUDE.md and GEMINI.md existed as per-runtime extensions of AGENTS.md, and
only one of them carried anything at all: GEMINI.md stated that it had no
additions, while CLAUDE.md held rules that bind every runtime anyway — the
permission announcement, the make-over-raw preference, the loop, chain and
heredoc bans, the stream-the-log-to-/tmp rule, the CI-evidence rule, the push
ban and the comment rule. A rule that binds every agent does not belong in one
agent's file. AGENTS.md absorbs them and now states that no tool-specific
extension file exists; agents whose runtime auto-loads a different filename
reach it through their own configuration instead.

The filenames were machinery as much as prose. The PR templates, the branch
and pull-request scope tables, the settings and security docs that justified
the loop deny by "the textual CLAUDE.md rule", six requirement documents whose
push instruction linked ../../CLAUDE.md, and a test docstring citing the
shell-loop ban all named them, and now name AGENTS.md.
scripts/meta/resolve/pr/scope.sh deliberately keeps matching CLAUDE.md and
GEMINI.md: this change deletes them, and without those patterns the change
itself would not classify as agent-scope.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 22:51:04 +02:00
kevinveenbirkenbachandClaude Opus 5 5b8465260f feat(ci): deploy only the variants a change reaches
An image bump in a provider redeployed every variant of every consumer,
including the variants that switch that provider off and therefore never
deploy the bumped image at all. The diff-derived whitelist now narrows each
affected role to the variants whose own round closure still reaches a seed,
emitted as `role#0,2` selection tokens. That closure is walked on the same
variant-merged services maps the matrix planner builds its rounds from, so the
narrowing follows a parent's variant choice across every generation of the
chain. It covers every auto-derived whitelist: the weekly update PRs,
Dependabot, and any other diff-driven run.

The narrowing never shrinks the matrix on partial information. A seed role
keeps every variant, because its own files changed. A role reached only
through a run_after edge keeps every variant too, because the round closure
does not follow that edge and a role whose hits are empty is therefore
unnarrowed rather than dropped.

A pinned token naming a role the discovery query does not return now warns
instead of aborting the run. The diff-derived whitelist legitimately names
roles the lifecycle envelope filters out, and once it pins variants such a
token would abort every chunk; role names are still checked against roles/
before the run starts, so a typo is caught one layer earlier.

The round's variant merge moves out of the deploy inventory package into
utils/roles/applications/variants.py. The CI selection has to build the same
maps, and importing the deploy package for them pulls in a module that reads
INFINITO_SRC_DIR at import time, which the runner host does not set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 22:51:03 +02:00
kevinveenbirkenbachandClaude Opus 5 f88aea5473 docs(mcp): regenerate the audit report for the baserow bump
test_the_committed_report_is_freshly_generated compares the committed table
against a fresh render, and baserow had moved to 2.3.4 in its services.yml
while the table still read 2.3.3.

The bump arrived from the image-version updater, which edits services.yml and
nothing else. The report is derived from those files but is committed, so every
version bump for an MCP-serving role leaves it stale until someone runs
`make mcp-audit`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 18:25:12 +02:00
Alejandro Roman ace4c3ff2c fix(docs): regenerate MCP audit report after image bumps 2026-09-21 12:32:35 +04:00
Alejandro Roman 4b9a834546 Merge remote-tracking branch 'upstream/main' into feature/role-stalwart-mail-swarm 2026-09-21 03:25:20 +04:00
kevinveenbirkenbachandClaude Opus 5 55b5a3dd93 fix(fider): run the build that reads the private-network opt-in
0d1ba1777f set ALLOW_PRIVATE_NETWORK_TARGETS=true in July with the right
diagnosis: Fider's SSRF guard refuses the OAuth token exchange when the
Keycloak host resolves to an internal address, so login 500s and never
completes. The image pin stayed at v0.36.0, which does not read that variable,
so the fix has been inert ever since.

Read out of the binaries rather than the release notes: `stable` contains the
string ALLOW_PRIVATE_NETWORK_TARGETS, v0.36.0 contains it zero times. Upstream
added the opt-in on 2026-06-24 and the check now returns before the hostname is
resolved, which is what puts the OAuth token path under it as well.

CI showed this as three red specs on Fider#0, admin sso login, biber sso login
and the seaweedfs tenant-logo upload, all with a missing .c-menu-user. The
artifact says why the selector is missing: the page is Fider's own 500 screen,
and fider.log carries the cause four times over, `Token URL is not allowed:
This URL is not allowed because it targets a private or internal network
address (app/services/oauth/oauth.go:320)`. The selector was right; there was
no session to render.

The tag is `stable` and not a version because upstream published v0.36.1 on
GitHub on 2026-07-03 and never pushed the matching image. Docker Hub's newest
release tag remains v0.36.0, and `stable` was rebuilt three days after that
release, carrying the opt-in. The marker on the line records both that and the
condition for removing it.

This is a moving tag in a repository that has just finished pinning fifteen of
them, and the mirror copies by tag, so an existing copy of `stable` can go
quietly stale. That is the accepted cost of the only build upstream ships with
the fix, and the TODO is what closes it.

audit.md moves with it: the report is generated from the pins and the lint
fails when the committed copy differs from a fresh generation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 10:52:02 +02:00
kevinveenbirkenbachandClaude Opus 5 c31c62efaf fix(images): install the curl the slim base dropped under the healthcheck
10c5b35e88 moved simpleicons and roulette-wheel from node:26-bookworm to
node:26-slim. Both declare healthcheck flavor curl, which renders
CMD curl -f <url>, and neither Dockerfile installed anything. Measured with
docker create and docker cp: node:26-bookworm carries /usr/bin/curl at 280800
bytes, node:26-slim carries neither curl nor wget. The probe therefore fails
from the first beat and the container never reports healthy.

sys-svc-compose waits on the database container alone in compose and on every
stack task in swarm, so the consequence splits by mode. Under swarm the task
dies with non-zero exit (137): dockerexec: unhealthy container, is rescheduled,
dies again, and the converge wait fails after 600 attempts. Five jobs across
the two runs on a534d5d46 died that way, and none of them on the role they are
named after: ERPNext#0, Decidim#0, Hugo#0, Nextcloud#9 and Nextcloud#12 all
carry web-app-dashboard in their closure, dashboard enables simpleicons, and
the failure reads 'not converged: simpleicons_simpleicons'.

All five are swarm and tor, which reads like the tor axis until XWiki#1 is put
beside them: swarm, tor, green, and simpleicons is not in its closure. The
discriminator is the service, not the axis.

Of the seven roles 10c5b35e88 rebased, bridgy-fed and postmarks probe over tcp,
and flowise and bookwyrm install curl themselves. These two were the remainder.
roulette-wheel had not been deployed yet when this was written, so it is the
same defect caught before it was observed. The install goes into the runtime
stage of the multi-stage build; the builder stage would have left the runtime
image unchanged and nothing would have gone red.

The lint reads the binary out of the probe the declaration renders rather than
restating a flavor-to-binary table, so a flavor that changes its command moves
the check with it, and it locates the flavor line per service so one marker
cannot silence a sibling. Its scope stops at slim: nginx:1.31.6-alpine was
measured too and ships curl as a real 272 KB binary plus busybox wget, so the
four alpine roles that declare those flavors are served by their base, and
widening the rule to them needs that measurement per image rather than an
assumption from the word alpine.

Not built. The image with the new layer has not been produced, and whether a
second fault waits behind the healthcheck is decided by the next deploy. The
apt path itself is not blind: BookWyrm#1 installs curl the same way on
python:3.11-slim and is green in this run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 03:18:37 +02:00
kevinveenbirkenbachandClaude Opus 5 c693f4f619 style(quality): apply what the gate wrote back into the tree
Nothing here was typed by hand. The docs generator rebuilt the MCP audit
table against pins that had already landed, so fider reads v0.36.0 instead
of stable, jenkins 2.568.3-lts instead of lts, pretix 2026.7.0 instead of
stable, and flowise 24-slim instead of 24-bookworm-slim. That is the table
catching up with da665ab69 and 10c5b35e8, reading the same declaration the
updater reads.

Autoformat wrapped three test files from the same series: one condition
pulled onto a single line, three assertEqual calls broken across lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 19:35:05 +02:00
kevinveenbirkenbachandClaude Opus 5 66be8f6a0a test(lint): refuse a Debian codename in an image tag
A codename carries no ordering, so 'bookworm' and 'trixie' are two opaque
strings and no updater can tell that one supersedes the other. A pin that
names its Debian release that way is frozen on that release for as long as
it stands, and the freeze is invisible because the tag keeps resolving.
Numeric tags are comparable and stay inside the freshness pipeline, so the
rule is worth holding for the whole repository rather than fixing case by
case.

The check reads two shapes, because a codename reaches the tree in two
spellings. A 'name:tag' reference is recognised by the absence of a space
after the colon, which is what separates 'debian:bookworm' from the YAML
'image: python' and covers Dockerfile FROM lines and heredocs without a
second parser. A 'version:' value is read as a key, because '3.11-bookworm'
carries no colon at all.

False positives are kept out by two independent gates: a word-boundary
prefilter, so 'residual' and 'inside' are not read as 'sid', and a split of
the tag on '-', '.' and '_' that requires a whole component to be a
codename. That is what lets 'cache buster', '.stretched-link',
'fa-bullseye' and a tags list containing 'stretch' through. Every codename
occurrence in the repository was checked against the matcher by hand, and
the check was negative-controlled in both shapes, including an untracked
file.

Four references had to move for the rule to hold. meta/distros.yml takes
'debian:13' rather than '13.6': at depth 1 the updater can raise it across
a major, while depth 2 pins it inside the 13.x line forever. The whole
'make install' path was run in a debian:13 container to back that: all
twenty-four Depends and Recommends resolve on trixie, the docker repository
bootstrap derives the right suite and installs docker-ce-cli, and the
package builds and installs.

The workspace DiD probe moves to 'debian:13-slim'. An earlier version of
this change carried a suppression claiming the probe had to follow the
suite the apt proxy mirrors. That was wrong twice over: it cited a function
that has nothing to do with the probe, and the coupling does not exist,
because the cache frontend rewrites by path prefix and the suite never
enters a routing decision. Replaying the probe verbatim against
debian:13-slim fetches all three trixie suites through the
bookworm-configured proxies, so the suppression is gone rather than
reworded.

The rule ships with an escape hatch and no uses of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 17:03:53 +02:00
kevinveenbirkenbachandClaude Opus 5 aa6bbe3b20 feat(lint): render the software identity from its declared variables
SOFTWARE_NAME, SOFTWARE_DOMAIN, SOFTWARE_URL and ORGANIZATION are declared once in group_vars/all/00_general.yml and derive from each other. Eleven templates wrote the value instead, so a rename would reach the four declarations and every consumer that reads them, and silently miss the literal.

The lint reads the name from the declaration rather than carrying it, and searches case-insensitively: the domain is the name lowered, the URL is https:// plus the domain and the organization is the name again, so one literal finds the value of all four. A hit is reported with the variable it should have been, derived from the scheme in front of it and its case. The escape is nocheck: software-identity, same line or the one above.

Every migrated template renders the same bytes as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 21:38:45 +02:00
kevinveenbirkenbachandClaude Opus 5 f19d5e3477 test(lint): refuse a loop_var that shadows a declared variable
Ansible warns that the name is already in use and carries on. Carrying on is the problem: inside the loop the original value is gone, so every lookup, template and nested include reading that name sees the loop item instead, and the task still reports success. The damage lands in a log nobody greps.

The lint collects every key declared in group_vars and in each role's vars or defaults, then reports any loop_var matching one. A denylist of known-bad names would have found application_id and missed domain, which is the same class in four more places.

All seven existing sites are marked, each with the reason read at the source rather than inferred. The three application_id loops pass the role id as the item, and the include_vars that follows loads that role's own application_id with the same value, so both meanings coincide by construction. The four domain loops hand the vhost to sys-stk-front-proxy, whose tasks read front_proxy_domain from exactly that name, so the shadowing is the interface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 21:23:17 +02:00