A selection token may pin the mesh state as &vpn or &direct, but check_pins
had no arm for it. pinned_vpn_states intersects the pin with the states the
mode allows and only swarm meshes, so a token like @compose+clearnet&vpn left
an empty state list: the row produced no job at all and the run reported green
for a combination that never deployed. That is the outcome _reject exists to
prevent, and every sibling axis already refuses an impossible pin.
The new arm asks whether any candidate mode keeps a state rather than whether
the pin is reachable, so a held vpn axis still overrides the pin instead of
being refused. That behaviour has its own test and is unchanged.
An earlier check was removed with the compose-mesh revert of 2026-10-16
because it rested on meshable() and smallest_variant(), which that revert
deleted. This one reuses pinned_vpn_states, applies to the swarm-only axis the
revert restored, and covers both mesh states instead of only &vpn.
pin_vpn and vpn_mode now reach check_pins from all three callers. The run's
vpn axis reaches the validation job through call-orchestrator and
call-validation, which passed every other axis but this one. The priority
procedure and its skill state all eight axes a token carries, not six:
architecture and mesh were pinnable and, for architecture, already validated,
yet neither was documented.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five conflicts, all resolved by combining both sides rather than taking one. main moved Moodle cURL egress and the debug level out of environment variables into site settings applied through cfg.php, and that restructure met three places where this branch had changed the same lines.
vars/main.yml carried the only resolution with a real decision in it. main expects MOODLE_EGRESS_ALLOWED_PORTS to be a list and joins it; this branch had turned that name into a comma-joined string to feed the environment variable main is removing, so keeping both sides verbatim would have evaluated string plus list. The settings block now reads the list this branch introduced, MOODLE_EGRESS_WEB_PORTS, and the string is dropped with the variable it fed. MOODLE_PROXY_EXCLUDED_HOSTS and the objectfs block stay, since neither belongs to the egress restructure.
env.j2 loses the egress and debug lines and keeps the SOCKS proxy block, which is this branch and which main never had. config.php loses the debug block and keeps the objectfs block for the same reason; the grep that settles it is that main carries no reference to MOODLE_EGRESS, MOODLE_DEBUG or OBJECTFS at all. 01_manager_ops.yml takes the new settings task. The cheatsheet conflict was additive on both sides, so all three rows stay.
Checked after resolving, because both are documented traps of this merge: no duplicate YAML key across the seven merged YAML files, and no nocheck marker lost, with the counts matching both parents in every resolved file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sys-front-inj-design now renders templates/css/app.css.j2 ahead of the
role's stylesheet and templates/js/design.js.j2 ahead of its script. The
branding URLs, the slot dimensions and the logo guard live there instead of
being repeated per role: the guard that stood in 41 sheets is one condition
in the composing template, and a sheet reads branding through
--design-url-* / --design-slot-* custom properties.
29 stylesheets and 43 design scripts lose their Jinja and move to static
files. Roles whose sheet carries app-specific URLs declare them as
design_css_urls and read them as --design-url-app-*.
The script prelude is prepended only when the role script references it,
and it carries urls only while a logo is configured, so every role that
needs neither is emitted byte for byte as before.
Five checks keep the mechanism honest: a composed sheet may only read
design custom properties that app.css.j2 declares for its role, a template
may not carry Jinja expression text outside its delimiters, a design script
may not quote its config object, and the injected-script parse check now
covers the design channel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The workspace jobs pulled every image anonymously from Docker Hub and
two of them failed on its rate limit in run 39059128678 (#990): the
fedora swarm drill on busybox:1.38 on all three nodes, the centos
compose drill while building tor-smtp_custom. The regular deploy jobs
pull through the GHCR mirror, but the mirror was tied to being a real
GitHub runner: Profile.image_mirror_enabled() returned
runs_on_github(), and the swarm routine generated mirrors.yml only when
cli.meta.runtime reported github, which is act inside make
swarm-zombie.
The mirror is now its own capability, INFINITO_GHCR_MIRROR (next to
INFINITO_GHCR_MIRROR_PREFIX), written as a run fact like
INFINITO_CACHE_STACK: true on a real GitHub runner,
false everywhere else, and a non-empty exported value wins for the run.
Profile.image_mirror_enabled() reads the declaration and keeps
runs_on_github() as the fallback, and 02_provision_inventory.sh gates
on the same value instead of the runtime name. make swarm-zombie
forwards it into act through ACT_ENV, since act ignores the host env.
call-test-workspace.yml declares INFINITO_GHCR_MIRROR=true and passes
INFINITO_GHCR_MIRROR_PREFIX into the workspace container. Nothing else
of the workspace changes: is_ci, runs_on_github, the cache stack and
the docker root stay local-like. The step that loads the rescue paths
now removes the .env it generated on the runner, because on debian
rows load.sh sourced it inside the container and the run inherited
INFINITO_RUNNING_ON_GITHUB=true.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With sync=role the palette check runs the app role twice around the
spec, and one role sync takes up to thirteen minutes, so the 900 second
cap cut a restore to the base color in the middle. The check with
sync=role now gets 2700 seconds; 900 stays for sync=design.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On the operator's order several role passes now run at the same time,
each by its own agent, on the one compose stack. The design loop page
gets a section for it: one baseline deploy per batch, every stack, test
and lint command behind /tmp/design-pass.lock with its own timeout, a
bounded ready wait that is proven at one viewport first, downloads
outside the lock, findings in another role's in-flight files named
instead of fixed, and one quality gate over the batch.
The queue rule no longer demands a single role in progress when such
an order exists; a single compose stack stays mandatory.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A gallery run took every view twice, once with the injected snippets
stripped. The comparison is only needed when the operator asks for it
for one role, so captureDesignGallery now captures the "after" side
alone and adds the plain side when PLAYWRIGHT_GALLERY_BEFORE is true.
make design-gallery passes it through as before=true; gallery.sh
refuses any other value, and rerun-spec.sh hands the variable into the
Playwright container. The deploy-driven run sets no such switch, so it
captures the designed side only.
The design loop page and the requirement describe the gallery as the
designed state and name before=true for a comparison.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The role loop changed how it gates and closes a pass; the guide still
described a full gate and a second deploy per role.
- Gates: a pass runs `make test` and the lint target of every file
type it touched. `make quality-high` runs once per two or three
passes, at a merge of the main branch and before a role is offered
for commit.
- The baseline deploy starts as soon as the previous pass reported and
is the only full deploy of a pass. A pass closes with a role sync and
the complete spec, with two syncs when it changed provisioning.
- Single views through `views=`, views that exist only with the
injected snippets through `afterOnly: true`.
- The gallery, its README link and the overview entry are published in
the step that verifies the pass.
- Stylesheet rule: every `url()` carries the origin of the app,
because the sheet is served from the CDN.
- Traps: the plain side of the gallery, a deploy that hangs in the
stack-wide CSP health unit because of an earlier role, apps that walk
`document.styleSheets`, and a negative control without a sync.
The verification item of the requirement states the same gate and
closing rule.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A generated lockup drew its title in the primary color with an
on-primary outline. On a header or sidebar that a role maps onto the
frame token, that put primary-colored text on a primary-toned ground.
A slot now takes `frame: true` in the role's design entry. The asset
action receives a second color pair from the palette, the on-frame
color as fill and the frame color as outline, and the renderer picks
the pair per slot. Slots without the flag render as before.
Keycloak calls the asset action directly and already rendered its
brand slot in the on-frame color, so it passes that color for both
pairs and keeps its output.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
make design-palette renders a deployed app with another base color, runs
its design assertions and renders the base of the inventory again through
an EXIT trap, also when a render or an assertion fails. sync=design
re-renders the static design files, sync=role re-runs the app role for
values it bakes at deploy time.
make design-gallery takes pw= to replace the default pattern. Such a run
adds its screenshots to the existing ones, so a single view is recaptured
without a full run.
The role pass now runs the gate once after its last edit, checks the
palette with blue only, repeats the full deploy at its end only when it
changed provisioning and has a wall-clock budget per class. The
verification decision of the requirement records the same rule.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The design loop keeps apps deployed between roles and purges only when
the next deploy does not fit, by memory, storage or a published host
port.
A role pass applies the palette through the app's own configuration
first: a theming configuration, then an in-house theme or custom CSS
option, and the injected role stylesheet only for what neither reaches.
It sizes one logo slot per shape, iterates through design-sync,
compose-role-sync and compose-playwright instead of a second full
deploy, asserts with assertToken, assertLightAndDark and
assertReadable, provisions an account for the signed-in interface, and
checks the palette with a blue, a red and a green base color.
The requirement follows: Bootstrap mapping on opt-in, in-house
configuration first, compose-role-sync for a changed script, task or
template. web-svc-mirror serves no page of its own and leaves the queue
and the role list.
The README of web-svc-design links the overview of the review
galleries.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
The lint accepted a link that resolved to a file or a directory. myst
builds a document reference only when the target is_file(), so a link to
a directory without a README is a bare file listing on GitHub and a dead
cross-reference in the documentation build. It now reports which of the
two reasons applies.
The tightened rule found 41 links. Where a README backs the directory,
bd15eae9fa's resolver carries the link and it stays. Where none exists,
the link becomes a code span rather than an invented README: 15 of them
point at a role's meta/addons/, whose addon table already sits below the
sentence, and the rest at code directories such as utils/ or
cli/administration/deploy/runner/.
The changed sentences are msgids, so these entries return to the
translator on the next extract.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The router now prints one line before it routes:
=== compose-deploy: mode=reinstall apps=svc-ai-litellm bundles= variant= \
full_cycle=false purge=false disable= playwright_keep=
Rewriting a deploy command is where its other arguments get lost. Switching
apps= from svc-ai-agent-broker to svc-ai-litellm dropped
disable="matomo,dashboard,prometheus,email,css" and
INFINITO_PLAYWRIGHT_KEEP=true in the same edit, after three deploys that had
carried both, and the run went on to deploy matomo and dashboard. That matters
beyond the wasted minutes: test-e2e-cli runs in the destructor stage, after
every role, so the providers are paid for before the CLI test the deploy exists
for even starts.
main.sh is the single entry every compose-deploy passes through, so the line
cannot be skipped and does not depend on remembering to look. It reads the
knobs and exports nothing; 8e293bd921 is on record for what happens when these
same variables reach places that persist them.
compose.md loses the checklist this first carried. The rules were already
stated twice in that file and a third statement is not what was missing; the
misses were not caused by not knowing them. It keeps the two facts that carry
information: why dropping disable= delays the evidence rather than merely
costing resources, and that full_cycle=true is the expensive form rather than
the complete one, since it adds pass 2, credential rotation and the recovery
drill while the CLI tests gate on RUNTIME instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI judges every row on a clean host but hands back a log hours later; the local stack answers in minutes and can be exec'd into, but it is one host with one slot and never covers five distros. Running them together is not an optimisation, it is how the two blind spots cancel: the disagreement between the lanes is the finding, and the runbook says what each direction of disagreement usually means.
The CI lane is dispatched by invoking i8-ci-trigger-priority, never by assembling a line here. Two skills with their own idea of what a verification row must pin is exactly how a run comes back green for a combination nobody chose.
The parallelism is between machines, never inside the local lane, where iteration/compose.md requires deploys to stay serial. The loop interval is 30 minutes because a deploy row takes 47 in compose and 84 in swarm at the median, so a shorter tick mostly re-reads in-progress jobs.
The trap it names first: entry-manual-steer.yml declares the branch's concurrency group with cancel-in-progress, so a second dispatch while the triage loop is armed cancels the very run being triaged, and the loop then reports every unfinished row as a failure that never reached a verdict.
The local lane is pointed at what CI cannot settle: a fix that landed after the source run, a pin that came from a pool default, and failures about container state rather than placement. Placement, overlay networking and replica counts do not reproduce in compose.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The runbook let an axis fall back to the rotation whenever the measurement could not carry it, and that is exactly what happened on its first real run: distro and filesystem had one measured value each, so they went in as run-level inputs instead of into the tokens. A row the rotation may still move is not a verification row, because the combination that comes back is not the one the question was asked about.
The measurement now decides which value an axis takes, never whether it takes one. Too few samples moves the choice one rung down instead of opening the axis: the value the row failed in, then the only value seen at all, then the pool default. Which rung a pin came from belongs in the report, because a pool-default pin is a guess wearing a token.
The run-level axis inputs are gone with it. A pool of exactly one refuses every token naming the other value, so a line that verifies a swarm question and a compose question at once needs auto at run level and a pin per token. Proven: agent-broker compose+clearnet, agent-broker swarm+clearnet and matomo swarm+tor, all six axes pinned, validate clean in one line against --modes auto --tor auto.
Nothing enforces this mechanically yet. A token without %distro/filesystem still validates, because the validator checks existence, not completeness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Skills load when they fire; docs/agents loads always. The mandatory full walk therefore made the on-demand mechanism point at the always-loaded store, and every session paid for seventeen runbooks of which at most one concerned the task. Measured on 2026-09-23: 11478 words under docs/agents, 9046 of them under action/.
The exception names both ways in: an i8- skill that routes to a runbook, and an agent performing the action without one. Without the second, docs/agents/action/debug/ci.md would have become unreachable, since it is the one runbook with no skill of its own.
Nothing moves out of action/. The split earns its keep twice over: the runbooks stay readable on GitHub without running make install-skills, and they stay inside markdownlint's scope, which excludes skills/** entirely. A procedure that lives only in a SKILL.md is unlinted prose, as i8-quality-high and i8-merge-sibling already are.
No lint holds this rule. A runbook added with neither a skill nor a cheatsheet row would be unreachable and nothing would say so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The measurement and the dispatch flags landed in the two previous commits and are useless without the order they go in. This is that order: collect at most 20 candidates, run verdicts first and open root causes after; measure the axes instead of asserting them; pin the cheap value only where the axis is not part of the question; validate; dispatch with the workspace test and the chunk gate off; then report which question the run will not answer.
The filesystem gets its own step because it is the only axis with a host condition: resolve.sh serves a kind only when the host kernel carries it, while the distro images carry just the userland, and a pool or token of exactly one kind is read as a demand, so the row fails instead of substituting. It is pinned only when the samples support it and no fallback was reported.
Measured on this branch on 2026-09-23: compose 47m against swarm 83m, clearnet 52m against tor 79m, over six runs. Those numbers live here rather than in the policy files, where nobody would recompute them.
The validation step is written as mandatory because it earned it: run against a 20-token line it caught 'web-svc-openresty', which does not exist under that name, and separated it from 'web-app-minio', which warns because its lifecycle is eol. A dispatch would have lost the first candidate silently and still reported green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both places still derived the instance count from `ansible_processor_vcpus`,
the fact whose loss took the hardware collector down with it. The default has
read `RESOURCE_HOST_CPUS` since the resource lookup landed
(roles/svc-runner/defaults/main.yml:5).
The agent doc additionally used lowercase names that never existed as
variables, so it pointed a reader at both the wrong source and the wrong
identifier.
A test blocks the fragile fact in the three consumer files; documentation is
outside its reach, which is how an agent reading the iteration guide could
reintroduce the dereference by hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
834 commits from the Tor onion-service branch against 299 of ours, from merge
base 7b646bdece: 127 conflicted files, 3506 files changed.
The two sides are largely orthogonal. Theirs adds svc-net-tor and a `tor` service
across the role tree, plus two sweeping refactors: shipped scripts sorted by
language (files/ruby/, files/python/, files/shell/, scripts/lint/*.sh) and the
unit suite moved under tests/unit/python/. Ours is requirement 036, the native AI
gateway, plus the MCP role work. Most conflicts were both sides appending to the
same service map, resolved as unions.
Resolutions that were not unions:
Credentials moved namespace. Theirs renamed meta/schema.yml to meta/secrets.yml
and re-addressed credentials as secrets.credentials.<name>. Git carried our
litellm_api_key blocks through the rename, but every consumer we added still read
'credentials.<name>', which no longer resolves. All 45 call sites are migrated.
plugins/filter/container_volumes.py is deleted by theirs and relocated to
plugins/lookup/container_volumes.py, where it carries the same tmpfs defect our
f85996b43 had just fixed: a hard-coded {"size": ...} that silently drops a per
mount `mode`. The fix is re-applied in the relocated file, as that commit's merge
note anticipated.
compose.yml keeps only INFINITO_PLAYWRIGHT_REPORTS_BASE_DIR. Theirs also forwards
INFINITO_PLAYWRIGHT_STAGE_BASE_DIR; that line is deliberately dropped, per the
analysis recorded in 45804e9ad -- STAGE has a drift-proof role-vars fallback,
while forwarding it promotes a setdefault-pinnable .env value above that SPOT.
tasks/stages/01_constructor.yml takes their include, ./tasks/utils/setup/group.yml,
because their refactor removed tasks/groups/*-roles.yml, leaving our include
target nonexistent. Our task name is kept, since svc-virt is in the loop.
Makefile swarm-zombie is a union: their validate plus teardown, then our
lab_subnet cleanup, which their side dropped while leaving lab_subnet.sh as the
only orphaned reference in the file.
01_bootstrap.sh keeps our deterministic --ip pinning, which distinguishes an
already-attached node from a real failure, over their `|| true`.
Their language-sorted layout orphaned references our side still held: zammad's
two scripts/apply/*.rb, jenkins' java-with-ca-trust.sh, and the notify topics
their handlers renamed to hyphenated form. All corrected.
roles/sys-svc-compose/files/shell/swarm/stack_ready.sh gains our latched-update
block: paused, rollback_paused, rollback_started and rollback_completed exit 2
rather than polling a state that cannot resolve without another deploy.
config_from_run is unioned to (title, logged, jobs); the production caller in
trigger/__main__.py now passes jobs, without which our suite suppression would
have been inert.
READMEs are regenerated rather than hand-merged: 15 LiteLLM rows and 91 Tor rows
now derive from the merged metadata, which is itself evidence the meta merge is
coherent.
Verified: 5161 unit tests pass. The single failure, test_query.py TestSortSpec,
also fails on fork/feature/svc-net-tor at b51d2f9919, measured in a baseline
worktree -- so this merge introduces no new unit-test failure. Two modules are
excluded from that run because `baudolo`, a dependency their branch introduces,
is not installed on this host.
Not verified: no deploy has run against the merged tree, and make quality has not
been run end to end here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The workspace suite diverges from a runner in two ways that make whole classes of defect unreproducible locally. INFINITO_DOCKER_VOLUME is a named volume locally and /mnt/docker on GHA (utils/env/handlers/infinito/docker_volume.py); compose down -v removes the former and cannot remove the latter, so nested Docker state is destroyed between tracks locally and carried forward in CI. And act sets ACT=true, so detect_gha_act resolves INFINITO_RUNNING_ON_GITHUB=false where a runner resolves true.
Both facts were already documented in isolation; their consequence was not, and it is the one that matters when someone reports a fix as verified because the local loop went green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One boolean answered five unrelated questions: whether the pull-through cache stack runs, whether image pulls take the GHCR mirror, whether the docker data root may be wiped, plus runtime naming and inventory resolution. The workspace is the environment where those answers diverge — it simulates a developer machine on a real runner — so getting one of them meant misreporting where the suite runs and silently paying for the other four.
is_ci() and runs_on_github() stay facts about the machine. cache_stack_enabled, image_mirror_enabled and docker_root_ephemeral become decisions that default off those facts and can be declared explicitly, so an environment states what it wants. Every default reproduces the previous condition exactly, including the distinction the old mirror and wipe gates drew between a real runner and act.
73ae40585 got the cache stack by flipping the identity flag and lost the docker-root wipe with it; the workspace now declares all three capabilities instead. Pairs with 4c068d1cf, which makes the resulting stray bind sources harmless rather than fatal.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every script a role ships now lives under `files/<language>/`, and the unit
suite is split the same way: `tests/unit/python/` beside new sibling trees for
the other languages. `tests/lint/filesystem/test_role_files_layout.py` keeps it
that way; directories whose shape is dictated from outside carry a `.nocheck`
naming the constraint (Docker build contexts, WordPress mu-plugins, the Joomla
extension, Odoo addons, Playwright suites, the `files/test/test.sh` harness
that `test-e2e-cli` resolves generically).
This cannot be applied in halves. Moving a file and repointing what names it
are the same change: 14 references resolve only at deploy time, so the tree is
green either before or after, never between.
Those 14 were found by static resolution over every reference class, not by the
test suite -- a full `make quality-high` was green while all of them were
broken:
* 7 `src:` values built from a role variable holding a bare filename
(svc-bkp-remote-2-local, decidim, espocrm, friendica, joomla, pixelfed x2).
The variable doubles as the destination name inside the container, so the
language segment belongs at the call site, not in the variable.
* `sys-service/tasks/04_base.yml` resolved every consumer's systemd script
through `first_found(..., errors='strict')` over `files/script.{sh,py}`.
18 roles ship exactly that and have no template fallback: the play would
have died on each.
* `sys-front-inj-javascript` fell back to `<role>/files/javascript.js`; the
interface now reads `files/javascript/javascript.js`.
* `sys-front-inj-logout/templates/head_sub.j2` built the cache-busting mtime
path through `path_absolute(... 'files', ...)`.
* 4 `ansible.builtin.script:` invocations naming a bare command
(pull.py, prune.sh, set_postgres_superuser_password.sh, ganesha-serve.sh).
* `dev-python/files/shell/install.sh` pointed at a sibling role's script.
Depth-dependent paths inside the moved trees were bumped accordingly: 59
`parents[N]` computations, 12 relative climbs, and the per-role recover CLI,
whose path `cli/administration/recover/recoverers.py` still assembled from the
pre-move location.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
run_one.yml stats roles/<role>/files/test/test.sh and fails when it is absent, but the runner guide and the nfs-server TODO both said files/test.sh. The TODO was the costlier of the two: following it would have produced a script the asset check never finds. It now also names templates/test.env.j2, without which discover_cli_roles never picks the role up at all and the test stays silently unrun.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
meta/schema.yml held credential definitions and their runtime values, but the
name said 'schema' and read like structural metadata next to services.yml and
volumes.yml. Nothing in the file is a schema in the sense the other meta files
use the word, and a contributor had to open it to learn it holds secrets.
62 role files renamed. ROLE_FILE_META_SCHEMA becomes ROLE_FILE_META_SECRETS and
keeps its role as the SPOT the path is built from.
Two readers bypassed that constant and spelled the name themselves:
utils/cache/applications.py built meta_dir / 'schema.yml' twice, and
plugins/lookup/application_gid.py carries the filename in the marker list it
detects application roles by. Left behind, the rename would not have raised —
credentials would have resolved empty and the role would have stopped counting
as an application.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The workflow directory mixed entry points, reusable stages and scheduled jobs into one flat list sorted by topic, so the first question about any file - what starts it - was the one the name did not answer. Every workflow now carries one of three prefixes, derived from its on: block: cron- for a schedule trigger, call- for one only reachable through workflow_call, entry- for everything a repository event or a human starts. Scheduled workflows additionally carry an alarm clock as the first character of their name.
Two lints keep it that way. One derives the expected prefix from the triggers and compares it against the file name; it flagged all 17 offenders before the rename and none after. The other resolves every .github/workflows/<file> string in the repository against the directory, which is what a rename silently breaks in Markdown links, Python constants and shell API URLs alike.
Renaming pulled 69 pre-existing comments into the comment lint's changed-file scope. The vendored CodeQL template got the nocheck marker its scorecard sibling already carried, two workflow headers moved above name: where they count as file headers, and the remaining banners were deleted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The lab subnet is pinned per worktree slot while lab network names carry
the cluster id, so a cluster kept from one app blocked the next run of
another with 'invalid pool request: Pool overlaps'. The pre-run teardown
only cleared the same cluster id, which never matched. Matching on the
IPAM subnet instead clears exactly the blocker and leaves a sibling
worktree's cluster alone; swarm-clean stays the host-wide tool.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
test-environment.yml and test-guide.yml become test-workspace.yml and
test-instructions.yml. Orchestrator job ids, the distros-SPOT matrix
lint, the act example, the diagnostics slug and the rescue artefact
names follow.
Replace the roles_only input with per-job workspace/instructions
tri-state inputs (auto|true|false). 'auto' derives the former
roles_only skip straight from whitelist: both jobs run on a global
scope (whitelist empty or __ALL__) and are skipped on a role-scoped
run, which keeps the PR behaviour identical because the diff resolver
never emits an empty whitelist. entry-manual exposes both as choice
dropdowns defaulting to false, so a manual run opts in explicitly.
The now redundant roles_only output is dropped from
pr_affected_roles.sh and subset_roles.py.
Branch-protection required-status-check names change along with the
job renames.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The five images in test-environment.yml were a verbatim copy of the
dev_runtime_image field of every distro in meta/distros.yml. A prepare
job now reads them through utils.distros and publishes a JSON array that
the matrix consumes with fromJson, so adding or dropping a distro no
longer needs a workflow edit. The resolver runs on the bare interpreter
because utils/distros.py is stdlib-only.
An empty list aborts the resolver: GitHub skips a job with an empty
matrix silently, which would leave the run green with nothing tested.
The lint could no longer compare literals, so it walks the wiring
instead: axis expression to needs edge to job output to step id, then
runs that step body the way the runner does and compares what it writes
to GITHUB_OUTPUT against the SPOT. The Act example in the iteration docs
was the last remaining literal and now reads the SPOT too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both iteration guides and the nfs_flavor lookup docstring still described
the flavor as RUNTIME conditional, selecting ganesha only for dev and act
and kernel everywhere else. Since the per-variant switching was dropped,
meta/services.yml pins ganesha for every runtime and kernel is a
per-inventory override.
State that, name meta/services.yml as the single point of truth, and
record which os families ship ganesha.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The runner command joins its siblings (dedicated, development) under
cli/administration/deploy; cli/deploy is gone. Makefile, docs and the
requirement links now point at the new path, PROJECT_ROOT climbs one
level further, and the unit tests moved along.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the i8* terminal aliases (make install-alias) and ai8* agent
conversation shortcuts (docs/.../agents/alias.md), move skill
distribution to the external INFINITO_SKILLS_REPOSITORY (make
install-skills), and add make cheat, make alias and make onboard. Introduce
custom.env operator overrides (win over defaults and handlers) and pin
INFINITO_ALIAS_MD / INFINITO_ALIAS_REPOSITORY / INFINITO_SKILLS_REPOSITORY
in default.env. Drop the in-repo skills-lock.json and its update-skills CI
job. AGENTS.md instructs agents to suggest the matching cli/prompt alias
when an operator writes something an alias covers. Lints enforce alias
sorting, i8/ai8 prefixes, no consonant clusters, full make-target
coverage, and terminal/agent shortcut non-overlap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Before every redeploy that follows a failed deploy, require one explicit
line: either verified-in-container or a named post-deploy-state-exception.
Forbid treating the redeploy itself as the verification, and reject
state-reset friction, logical inference from a prior deploy, and
"looks correct" as reasons to skip. Playwright-spec failures must reset
any polluted state and be green via make compose-playwright first.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A multi-variant app expanded into N swarm matrix entries, and
swarm-zombie ran them all on one host under identical SWARM_NAME
cluster names, so every deploy after the first aborted with
'network ... already exists'. Pin the act matrix to variant 0
(default, overridable via variant=) so a multi-variant app validates
a single cluster, in parity with the compose step's variant=0.
workflow.sh now splits ACT_MATRIX on ';' into multiple --matrix
filters so apps and variant can be pinned together.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the 'meta roles applications complexity' report:
- --sort takes comma-separated multi-key clauses with per-key direction (e.g. 'desc embeds, asc weight'); replaces --order.
- --variant lists one row per meta/variants.yml variant; --format adds yaml.
- New columns: weight (was total), random, id, row, variants, bundles, jobs, lifecycle, covered_by, compose, swarm.
- covered_by marks the first green (1-based id) different-role predecessor that embeds the row; variant>0 never covered.
- bundles is the compose bundle/job count (whole-role) or 1 (per variant); jobs is the running sum.
- compose/swarm reflect the CI test-deploy skip logic (invokable + tested lifecycle + per-mode skip + swarm deployable variant).
- --filter is a boolean expression DSL (%% == \!= < > <= >=, and/or/xor/not, parens, set literals) over row fields.
Add shared SPOTs: variant_bundles.compose_bundle_counts and variant_status.deployable_variant_indices, reused by the swarm/compose matrix. Update roundtrip.sh/md to --sort "desc weight".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the scripts/tests/environment deploy_* steps (07/08/09) to compose_*; add 12_swarm.sh (svc-db-mariadb via the swarm-* targets) and 13_roundtrip.sh (svc-db-postgres via make roundtrip), with MARIADB_APP/POSTGRES_APP in common.sh. Rename every make act-swarm-* target to make swarm-* (drop the act- prefix) across the Makefile (moved to the alphabetical slot), the deploy/act, deploy/swarm and e2e helper scripts, and the agent iteration docs. Strip mid-code narration comments from the touched scripts per the comment policy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>