Renames the mesh-only first pass to the prologue it always was, since
PASS 1 and PASS 2 now both name application deploys, and states why the
credential reset cannot move to the direct arm's slot.
The meshed round was its own shape: a mesh-only first pass, one
application deploy, and a second only when a drill needed one. Comparing it
with the direct arm meant adjusting for a missing pass and a stripped browser
suite, and every timing answer carried a caveat.
Both arms now run deploy, converge, drill, redeploy, converge, verify. What
the mesh needs moves into a prologue that installs no role: tunnel up on every
node, keys rotated, controller joined, transport switched. Both application
passes then run over the tunnel and both print their PASS banner, so a meshed
row and a direct row differ only in how they were reached.
The credential reset stays in the prologue rather than moving to the direct
arm's slot between the passes. Its mirror copies the manager's host_vars over
every other host, keeping only applications.svc-net-wireguard, so after the
switch every worker would inherit the manager's mesh address and three
connections would land on one node.
The drill probe now gates both arms. A backup host deployed for an application
with no NFS-flagged volume is several minutes spent on a drill that skips
itself, and that was true on the direct arm before this branch existed.
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>
Narrowing the meshed drill to its backup half bought budget by giving
up the recovery legs over the mesh. The budget overrun is worth measuring
before it is designed around, so the pre-drill pass and the full teardown and
recover come back. Reverts a27b093c4.
The unit test of the alarm script built its Jinja environment with
autoescape switched off, which code scanning reports as a possible XSS
sink and fails the PR on. The template is a shell script, so
select_autoescape() leaves it unescaped exactly as before, and the finding
no longer applies.
Refs #790
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The pre-drill pass cost a whole deploy so the drill would have a stack
to tear down and a later pass to bring it back. n8n variant 0 spent 11970s in
that pass alone and hit the 20400s budget before the real deploy started.
The meshed round now deploys once, as the rest of the branch does, and runs
the backup half of the drill after it: backup host plus DRILL_STAGE=backup,
around 550s against roughly 12000s. Teardown, the recover CLI and the bring-up
onto the recovered export are transport-agnostic and the direct arm proves
them on every application the axis rotates there. What the meshed half adds is
the backup host joining the data mesh and running its units over the tunnel.
Step 3b, which caught the Nextcloud volume-repo failure, is inside the half
that is kept.
DRILL_STAGE=backup returns once the marker has travelled from the live
export through the deployed units to the LUKS device, before step 6 tears the
stack down. The loop device is closed on the way out, which the full path does
inline at step 7. Default stays full, so the direct arm is unchanged.
The version-source resolver orders upstream versions by semver. A pin that
is not one, such as `latest` or a branch name, has no depth, so no candidate
ever matches it and the declaration never moves anything. Nothing reported
that: the watched-pins lint only looks at values that already are a semver.
test_version_keys now fails on an `update:` block whose pinned key does not
hold a semver. A monitored addon the resolver does not read is held to the
same rule as soon as it pins a `version`, because the catalog check skips a
non-semver pin just as silently. An addon without a `version` keeps tracking
the app default and is left alone.
The Stalwart README no longer asks for `webui_version` to be bumped by hand
next to the image tag. Its `update:` block names the upstream tags, the
server itself follows `releases/latest` of the bundle, and the resolver
plans v1.0.5 to v1.0.11 for it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The prose explaining why offsets are reused and how the hub keeps index
zero is recorded in requirement 037. A reader of plan.py needs the signature,
not the history.
No conflicts. Two files were touched by both sides and both kept each
side: the Makefile, where upstream retires the mcp-audit target and this
branch pins the throwaway cluster's vpn axis, and requirement 035, where
the role table keeps the single svc-net-wireguard row.
Incoming carries the fixes for two applications this branch's last run
failed on -- penpot probing its frontend without curl, and mobilizon's
postgres 18 migrations -- plus dependency bumps and a lint repair.
Gates on the merged tree: unit 6202, lint 523, integration 330,
ansible-lint clean.
The wait job picks the newest privileged run on the head commit of the
PR. Two fork PRs that share a head repository, branch and commit but
target different bases each start such a run, and the commit alone cannot
tell them apart. A failed run of the other PR would then end the wait of
this one while its images are still being built.
The run name of the workflow carries the PR number, so the match now
requires it next to the head commit. A run without that name matches
nothing, and the wait does what it does whenever no run is found: it
keeps polling for the images until they exist or the wait times out.
Raised by the CodeRabbit review of this PR.
Refs #784
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>