The validation job asserts that main is fully merged into the PR head,
so the branch has to carry the current main before CI can pass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The drill hands the deploy every group-conditional service in disable=,
and this branch made the mesh one of them. Its row is planned at index 0 of
sweep 0 with the axis on auto, which is the half of the rotation that
carries the mesh, so the lab brought svc-net-wireguard up while the same
run asked for the wireguard service to be switched off. The deploy refuses
that combination, and every workspace job that drills a swarm cluster died
on it:
service 'wireguard' is disabled, but provider role 'svc-net-wireguard'
is still active
The axis is now pinned off for the drill, which is the state those jobs ran
in before the mesh existed: they exercise the workspace tooling on each
distro, and the deploy matrix is what proves the mesh. An operator who
wants the other arm passes vpn=enforced.
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 jenkins row still read 2.568.3-lts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The validation job asserts that main is fully merged into the PR head,
so the branch has to carry the current main before CI can pass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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 hermes row still read v2026.9.7.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The validation job asserts that main is fully merged into the PR head,
so the branch has to carry the current main before CI can pass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
One conflict, in the generated role table of README.md. Both sides changed
the same rows: upstream swapped WordPress's Mailu dependency for Stalwart,
while this branch replaced the three svc-net-wireguard-{core,plain,
firewalled} rows with the single svc-net-wireguard it deploys.
Resolved by regenerating rather than by hand, since make readme-index owns
that table and gates it with --check. The regenerated table carries both
sides and the check reports it up to date.
README.md was the only file both sides touched, and nothing in the tree
still names the three replaced roles.
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 prometheus row still read v3.14.0.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The validation job asserts that main is fully merged into the PR head,
so the branch has to carry the current main before CI can pass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The carriage returns a pty adds are invisible until a role parses its own
output, so the absence of the tty is asserted where the transport is
written rather than left to the next role that reads stdout.
Ansible appends -tt to any task it cannot pipeline when the task escalates,
and ansible.builtin.script is one of those, so the deploy over the mesh ran
those tasks on a pty. A pty ends every line with a carriage return, while
the container connection this transport replaces returned bare newlines. A
role that reads its own stdout therefore started failing for no reason other
than the deploy having moved onto the mesh: the Matomo bootstrap parsed a
perfectly good token as empty, because its regex anchors each line and a
trailing carriage return defeats that anchor.
The host the tool switches now asks for no tty, so the mesh returns the same
bytes the connection it replaced did, and no role has to learn about the
transport underneath it.
Taken from the connection plugin rather than guessed: the tty is added when
the task cannot pipeline and escalates, and the option carrying it is
exposed per host as ansible_ssh_use_tty.
Deprecated roles are no longer tested, but Stalwart variant 1 still switched its mailu service key on so CI co-deployed Mailu for the migration e2e. The override and the static mailu key, whose only purpose was that pull, are gone, and the generated cosmos drops the mailu node.
The migration gate keeps only the check that services.stalwart.migration.import_mailu stays declared and off by default. The operator-side import (12_import_mailu.yml, migrate_from_mailu.py) still runs when web-app-mailu is in the inventory; the e2e harness under files/test/ and templates/test.env.j2 now skips itself and is left for removal.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
Stalwart is the default mail provider, so Mailu moves from maintenance to deprecated. Its README carries the Deprecated banner lifecycle.md requires, pointing at web-app-stalwart and its "Migration from Mailu" section; MAIL_PROVIDER: web-app-mailu keeps routing mail to Mailu until an install has moved.
deprecated sits outside the tested lifecycle envelope, so Mailu leaves the CI deploy discovery and the generated role table of the root README.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
Formatter output only: the parsed YAML and the Python AST of every touched file equal their previous versions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MAIL_PROVIDER moved to web-app-stalwart without a make docs run, so every generated README still drew Mailu as the email dependency. This is the generator output for the current tree: the root role table and each role cosmos now name Stalwart, Mailu loses its dependents, and Stalwart gains its swarm marker, the Tor dependencies and the 0..0 edges.
The hand-written cosmos of svc-net-tor-smtp gives way to the generated one, which no longer shows the SOCKS5 hop through svc-net-tor because the role does not declare that dependency.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
Takes the agent policy from the feature/store-i18n worktree over unchanged:
- rm -rf* leaves the deny list.
- Edit and Write on any .claude/*.json now ask for confirmation.
- The sandbox network allowlist trades the single 127.0.0.6:8097 entry for 127.0.0.1 to 127.0.0.12 and localhost on every port, so sandboxed commands reach local services and published compose ports.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sshd starts bash in a way that sources ~/.bashrc even for a single
remote command, and this file printed its banner unconditionally. Every
command Ansible runs over SSH therefore returned the banner on stdout, and a
task that parses its own output read that instead: the Matomo bootstrap
asserted on stdout='unknown': unknown terminal type., which is [H[2J[3J
failing when there is no terminal to clear.
The deploy never saw it while the roles were installed over the container
connection, where no login shell is involved. Now that the applications are
deployed over the mesh, every such task reads the banner.
Reproduced and fixed against the rendered template: sourcing it
non-interactively with TERM=unknown printed the terminal error and the whole
banner before, and prints nothing now, while an interactive shell is
unchanged.
The narration comment above the colour block goes with it: the guard moved it
off the file header, where the comment lint allows it, into mid-code, where
it is what that lint calls narration.
The bootstrap is now the first thing to touch a node, and it installed
the WireGuard tooling on an index nothing had fetched: every meshed row died
in ten minutes with 'No package matching wireguard-tools is available' on
all four hosts.
The deploy never hit this because user-root pulls in sys-svc-ssh, which
refreshes the index before the run's first install. The bootstrap skipped
that role, so the ordering the deploy relies on was missing. It now runs the
same prefix the constructor does -- root, administrator, then the mesh --
rather than repeating the refresh somewhere else.
One conflict, in the MCP exclusion list of requirement 035. Both sides
edited the same line: main added svc-net-tor-smtp, this branch replaced the
three svc-net-wireguard-{core,plain,firewalled} entries with the single
svc-net-wireguard it deploys. Resolved to the union of both — 24 entries —
which is what the merged tree actually holds.
Everything else merged clean and was checked for overlap rather than
assumed: no reference anywhere still names the three replaced roles;
matrix.py kept main's PRIMARY_APPS alongside the reordered round, and the
bootstrap pass does not need it, since only one template reads it and
group_vars defaults it empty; and main's ansible.cfg addition sits in
[defaults], so the ssh_args the mesh tool derives its own connection
settings from are untouched.
Why the first pass stops at the mesh, and what the meshed row therefore
stops covering: the second deploy itself, so no re-run proving a redeploy
changes nothing, and no async update pass. Every direct-transport row still
deploys twice, so that coverage moves across the axis rather than
disappearing.
The round replaces its first deploy with the mesh bootstrap and keeps the
deploy after the seam as the only one that installs the applications: the
same roles at 2.98s a task instead of 0.74s, some five thousand tasks of it,
which is most of why a meshed variant 0 ran past the 20400s budget.
The backup node and the DR drill move behind that pass, because there is
nothing to back up before it; they are held together in one step so the two
call sites do not drift. Credentials still reset before the deploy that has
to carry them.
A direct-transport round is untouched and still deploys twice.
A meshed round deployed every role twice, once over the container
connection and once again over the tunnel, which left the most expensive
transport carrying nothing but idempotence checks. The first pass now brings
up the mesh and nothing else, so the pass after the seam is the only one
that deploys the applications.
The converge drops the backup inventory with it. That node is deployed after
the rotation now, so it renders the current mesh on its first run and has
nothing to converge from.
The pass that moves the deploy onto the mesh logs in as administrator, so
whichever pass brings the mesh up has to create that account. It is guarded
rather than unconditional: the converge reaches nodes that already have it,
and the controller play would otherwise grow an account it never uses.