Data model: CI runtime stabilisation

The mission's "data" is CI configuration and evidence. Entities, their single home, and invariants:

EntityHome (single source)Key fieldsInvariants
Battery base selection.github/ci-module-registry.yml → special_tiers.architectural.basepaths, marker expression, whole-file deselectsDeselects equal the union of the always-on lanes' pytest paths (C-002); identical arguments in every battery part
Fast-gate rosterregistry → special_tiers.architectural.fast_gate.roster[]path, budget_seconds, reasonEach measured duration ≤ its budget; Σ ≤ max_total_measured_seconds; every entry is a file of the base selection
Battery shard partitionderived by scripts/ci/shard_select.py (file granularity) from registry shard_count + timingspart → filesfast ∪ S1 ∪ S2 = base; pairwise disjoint; no file in two parts
Shard timings.github/ci-shard-timings.jsonmodule → per-test durations; battery → per-file durations; capture provenanceCaptured with the SAME marker expression its consumer uses — module rows not performance and not stress; battery base not performance and not stress and not timing; battery seed provenance per D-36; length mismatch is loud
Module shard rowregistry module rowsname, paths, shard_countconsolidation shard_count: 2; leaves _MISMATCH_ALLOWLIST (shrink-only, _BASELINE_ALLOWLIST_COUNT 20 → 18)
Path groupci-router.yml filter block (+ job if: gates)name, globsci_config gates only the battery; ci gates no router job (#4386 pin); HEAVY_BATTERY_NON_SRC_GROUPS = {architectural, ci_config}
Per-change suite-job ledgertests/architectural/test_no_duplicate_suite_execution.py AUTHORIZED_PER_CHANGE_SUITE_JOBSjob → reasonAdds architectural-fast and tests (corpus-blocking) (D-22); removes router cli/status/consolidation/corpus; counts per matrix leg
Overlap allowlisttests/architectural/test_same_tier_uniqueness.py(job A, job B, node-id pattern) → reasonReasoned, shrink-only, ≤ 10 entries (expected 4); tiers keyed on OS family
Tested keyworkflow artifact ci-tested-key-pr<N>-base-<sha>workflow, PR, head SHA, base SHA (merge first parent)Written only by executing runs; skip runs write ci-green-match-run-<id>-attempt-<n> and are never matched
Green-match decisionscripts/ci/green_match.py outputsskip, reason, marker, matched run id/attempt/urlSkip only on pull_request+ready_for_review+attempt 1+exact key; any error → run
Measurement evidencekitty-specs/ci-runtime-stabilisation-01M3TZH6/evidence/ci-measurements.mdrun id, mode, job, duration, workers seen, peak memory≥ 3 runs per NFR; every number cites a run id (C-011)

State transitions

  • Ready-for-review run: pull_request(ready_for_review) → green_match.decide → Skip(matched run) (path filters skipped, required gate green, Aggregate re-points) or Run(reason) (normal execution, tested key recorded).
  • Battery on a PR: changes job → (architectural-fast always) + (architectural-heavy legs 1/2, 2/2 iff code-scoped groups ∪ ci_config ∧ ¬prose_only) → router gate.
  • Nightly: schedule → architectural-backstop (full base, no partition) → nightly-summary → release nightly gate.