@@ -131,20 +131,31 @@ How CI allocates Riot environments
131131Semantic test ownership remains in ``tests/suitespec.py `` and the distributed
132132``suitespec.yml `` files. CI resolves every selected suite to its Riot environment
133133hashes, then assigns those hashes to physical GitLab shards. A Riot environment is
134- the smallest allocation unit, so duration-based balancing cannot change its command,
135- Python version, services, environment, retry policy, or timeout.
134+ normally the smallest allocation unit, so duration-based balancing never changes its
135+ command, Python version, services, environment, retry policy, or timeout. For a
136+ measured long-running pytest command, CI may refine one hash into runtime execution
137+ units such as ``107d2ec@1/3 ``. Pytest still collects the suite-authored command, then
138+ a repository plugin deterministically selects one disjoint slice of the collected
139+ node IDs in each physical job. No item-level partition appears in ``suitespec.yml ``.
136140
137141``scripts/gen_gitlab_config.py `` writes ``.gitlab/ci-allocation-plan.json `` with the
138142current round-robin plan and a duration-balanced plan. Generation fails unless both
139143plans contain the exact same Riot hash set, with no duplicates or empty shards, and
140- the execution metadata is represented by the same digest. The plan is retained as a
141- CI artifact for review.
144+ the execution metadata is represented by the same digest. Runtime slices must also
145+ contain every index from one through their declared total. The plan is retained as a
146+ CI artifact for review. The generator embeds every balanced assignment into the
147+ suite's GitLab ``parallel `` job, and each physical job selects its assignment by
148+ ``CI_NODE_INDEX ``. The semantic suite and compact matrix representation therefore
149+ remain stable even when an assignment contains sub-hash execution units.
142150
143151The active strategy and promotion thresholds are in
144152``ci/ci-allocation-policy.json ``. ``legacy `` is the rollback-safe default. The
145153duration estimates in ``ci/ci-allocation-runtime-model.json `` are generated from
146- Datadog Test Visibility session exports joined by
147- ``test.configuration.riot_hash ``. The model uses a time-decayed p90, conservative
154+ Datadog Test Visibility session exports joined by semantic suite and
155+ ``test.configuration.riot_hash ``. Riot hashes identify environments rather than
156+ commands and can be shared by semantically different suites, so suite-scoped
157+ estimates override global hash estimates for those collisions. The model uses a
158+ time-decayed p90, conservative
148159fallbacks for sparse hashes, and a recent holdout that is not used for fitting.
149160CI job events are joined by pipeline and job identity. Riot setup and activation
150161time outside the Test Visibility session is distributed over the atomic hashes in
@@ -155,6 +166,13 @@ model must remain below the size limit in the allocation policy. Failed and canc
155166observations are retained as censored reliability evidence but are not treated as
156167normal durations.
157168
169+ Sub-hash expansion is fail-closed. A command is eligible only when its command
170+ fingerprint has real Test Visibility item-count evidence and the fitted runtime
171+ exceeds the target. Fallback estimates never create slices because they may describe
172+ a one-test or non-pytest command. Each slice writes a compact collection inventory.
173+ Across all slice artifacts, verification requires the same collection digest, an
174+ exact disjoint union, and no empty slice.
175+
158176The balanced strategy targets five minutes of modeled work per Riot shard. Promotion
159177requires at least a 50 percent reduction in the median Riot critical path over paired
160178live shadow runs. This objective covers the generated Riot child pipeline, not the
@@ -165,8 +183,10 @@ Balanced sizing is constrained to the legacy topology's total job count. When th
165183duration target requests more jobs, the planner removes shards with the smallest
166184modeled critical-path penalty, allowing capacity to move between semantic suites
167185without increasing the job budget.
168- If one atomic Riot environment exceeds the promotion target, the allocator must fail
169- the gate; meeting that target requires a separately validated finer execution unit.
186+ Live critical-path measurement uses the actual interval from the first Riot job start
187+ to the last Riot job completion. It does not add semantic-stage maxima because the
188+ generated ``needs `` DAG lets those stages overlap. Modeled maxima are planning scores,
189+ not measured CI runtimes; promotion uses completed same-head shadow runs.
170190
171191Use the allocation helper to normalize an export, build a candidate model, and
172192replay it against the untouched holdout:
@@ -221,6 +241,16 @@ filename as a fallback for parallel runs that omit test suite properties:
221241 --balanced balanced/test-results/junit* .xml \
222242 --output junit-parity.json
223243
244+ For every sub-hash command, also download the runtime inventory artifacts and prove
245+ that all collected pytest items were executed exactly once:
246+
247+ .. code-block :: bash
248+
249+ $ scripts/ci_allocation_cli.py verify-runtime-shards \
250+ --plan ci-allocation-plan.json \
251+ --manifests balanced/test-results/ci-test-shard-inventory.* .json \
252+ --output runtime-shard-parity.json
253+
224254 Promotion requires the checked thresholds for sample count, median improvement,
225255p75, p90, runner time, clean-success rate, and retry rate. A scheduled retuning task
226256may propose a new model, but it must not change the active strategy automatically.
0 commit comments