INFINITO_DISCOVERY_SORT is the single source of truth for the discovery
order, but 'asc clone' lived in sort_spec() and was concatenated in front of
it, so the declared value never showed the order a run actually takes.
Declare the key where the rest of the order is declared and let sort_spec
return the value verbatim. test_clones_sort_last_ahead_of_the_declared_spec
now guards the declaration instead of the concatenation, and covered rows
sort directly behind the clone key so they land at the tail of the ranking.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
env_setting returns a non-empty process value, else the default.env value,
else KeyError -- an empty process override falls back through the strip check
in parser.py. INFINITO_DISCOVERY_SORT is declared non-empty, so the spec the
ternary guarded against could never be falsy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two shell aliases rebuilt the discovery order by hand, and both got it wrong
in the same two ways: they passed INFINITO_DISCOVERY_SORT straight through,
dropping the clones-last prefix that keeps one representative per dna cluster
ahead of the budget cut, and they filtered on `compose` -- what a role can do
-- where discovery filters on `test_compose`, what CI actually tests. Measured
against the real query that is 13 rows of difference and a divergence from row
two onward.
The query module already owned both pieces. It now also renders the whole-role
view (--matrix --roles), which is the only thing the hand-rolled version could
do that this one could not, so nothing outside this module has a reason to
assemble a --sort or a --filter again.
A unit test pins the contract: the role view may drop the --variant flag and
nothing else, and the filter reads the tested column.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One discovery query per run replaces one per mode, so the row basis, the
ranking and the budget cut all speak the same role#variant unit. The
result is deployed in serial chunk blocks rather than three parallel
mode matrices.
Chunk size is derived, not pinned: GitHub cancels a job queued for
INFINITO_CI_QUEUE_HOURS, so with INFINITO_CI_CONCURRENCY runners free a
chunk holds concurrency * floor(queue_hours / the deploy job's
timeout-minutes) rows. Assuming every job burns its full timeout is
deliberately pessimistic -- no job can outlast it -- so the drain
estimate can only overshoot.
The run job cap bounds the sweep as a whole; rows beyond it roll into
the next sweep, whose regular line starts at an offset so consecutive
sweeps walk the whole list instead of re-testing the same head. Priority
rows lead every sweep, never move with that offset, and force a chunk
boundary at the seam so a chunk is either all priority or all regular.
sequencing.py goes with the mode lines it ordered: chunking restarts the
queue clock at a controlled size, which is what serialising the modes
was approximating with three buckets of arbitrary size.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI deploy jobs were titled with the raw role id, so a run page read as a
column of dash-joined prefixes. They now carry a display name: the category
path as hanzi labels, then the role's README heading or the part of the id
the categories do not already say, whichever is shorter.
utils/roles/display.py owns both directions. Encoding happens once, at
discovery time, as a `label` key on every matrix entry; the workflows render
`<glyph> ${{ matrix.label }}` and keep the glyph as a literal because the
retrigger reads the deploy mode off it.
Decoding is what makes the retrigger keep working. It accepts a display name
with or without its variant shard and a bare role id unchanged, so job names
from runs that predate this still parse and a hand-typed dispatch never has
to spell hanzi. The parser therefore stopped matching an id-shaped tail and
asks the codec instead; a job the codec does not recognise is skipped rather
than counted as a role.
The role selections go out as display names too, so the run title reads like
the jobs it spawns. Four consumers decode them back, each the single place
the value is actually used: the whitelist resolver before it checks the role
directory, the discovery query on its env, and the sequencing and plan CLIs
on their arguments.
Every fragment is joined by `·`, including before the title, and the heading
loses its whitespace and any emoji variation selector. A display name holding
a space would make the space-separated whitelist and priority inputs
unparseable with more than one role.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
variant_bundle_limits.py next to variant_bundles.py formed the prefix group
'variant', which the structure lint reads as a folder in disguise. It stayed
green locally because that lint asks git ls-files: the new file was untracked
through every quality run and only became visible once committed. CI saw it
immediately - the sole failure of run 30675284663.
Both move to utils/github/variant/ as bundles.py and limits.py, and the test
mirrors along.
The BLE001 per-file ignore is removed rather than repointed. parse_size raises
humanfriendly.InvalidSize for a malformed string and TypeError only for a
non-string, and the value reaching it is always str - so the catch-all was
never needed. Measured, not assumed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The compose and host discovery sorts already push clones (redundant
same-service-set variants) behind the budget cut so one representative
per dna cluster stays covered. Extend the same 'asc clone' prefix to the
swarm mode so its per-variant matrix drops redundant clone variants first
when the job budget is tight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a run deploys both single-node modes, the host discovery redeployed roles the compose line already covers (e.g. web-svc-html in both matrices). The single-node workflow now signals a co-running compose line via INFINITO_HOST_EXCLUDE_COMPOSE (a run-context registry key like INFINITO_DEPLOY_MODE), and the host query excludes the compose line's actually-triggered selection before its own budget cut, so freed host slots go to roles compose does not cover. Priority and regular lines each filter against their own compose counterpart through the identical whitelist/blacklist pass-through; host-only runs stay unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both the priority and the regular deploy line received the full per-mode slot budget, so a run with a priority input budgeted up to twice the mode share against the 256-job run limit. max_jobs now subtracts the jobs the priority line actually occupies - the regular line's blacklist is the priority whitelist, and the inner discover runs under the identical cap, so the deduction mirrors the real consumption including the priority line's own cut. A fully consumed budget short-circuits discover to an empty selection without invoking the complexity CLI (which rejects --max-jobs < 1), and the plan summary renders the reduced regular budget.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plan job renders under a map emoji with plan wording across the CLI,
docs and aliases; the guide workflow, its orchestrator wiring, the
dependency diagrams and the workflows doc follow the test-guide name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The discovery query read every INFINITO_* knob with a silent .get default, which the env lint forbids: defaults live in default.env, and an unset key must fail loudly instead of masking a broken environment. All five accesses go through os.environ[...] now; the documented empty-value branches (sort spec falling back to default.env, auto job slots) stay explicit logic. The complexity model's random tie-breaker suppression carries its required justification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The discovery SPOT (cli.meta.ci.query) now runs one query shape per mode:
compose and host stay whole-role with variants packed into bundles, swarm
ranks and budget-cuts on per-variant rows and emits role#variant tokens
through apps.sh into variant_bundles, so the cut can pick a subset of a
role's variants and the --matrix view is the selection priority.
Replace the --unique flag with visible dna/clone columns (heaviest role
per dna cluster stays ahead of the cut), add the integrated column and
sort, count host bundles like compose, and give the deploy plan an id
counter, per-variant weights and display-width-aware alignment matching
the matrix renderer. Add i8ma*/i8pa* inspection aliases per mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cli.meta.ci.query is the single source of truth for the app-discovery
query (filter, coverage-first sort, lifecycle envelope, compose
--unique, INFINITO_MAX_JOBS cap with 'auto'); apps.sh shrinks to one
call through it. cli.meta.ci.plan renders one section per deploy mode
in the query's own order (priority line starred, capped regular line,
then the candidates behind the cut), one row per variant, glossary
symbols in the header, with --cli for fixed-width terminal tables.
New glossary symbols: skip, priority, distros, unavailable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>