Repository navigation
ci: run only affected specs -- the selection rule as specs/ci/affected.t27 #6604
Description
Item E of epic #6595: run only the specs a pull request affects.
The t27b lab already keys its reference cache by spec bytes + the use closure + t27c + zig (#6535); its last run served 1271 specs from the cache and ran 6. CI and the local tri test / t27c suite still run everything.
Measured read-only on 2026-10-06 (gh run view --json jobs) on four merged spec-only PRs (#6582, #6578, #6557, #6556; one .t27 file each), job wall time in minutes:
| Job | #6582 | #6578 | #6557 | #6556 | Over the whole corpus? |
|---|---|---|---|---|---|
| Emit Bit-Exact Gate (CI-02) | 5.1 | 5.2 | 8.5 | 8.4 | No: fixed GF-T topologies (specs/ternary/**, specs/igla/race/*); it fires on every PR because its paths: has specs/**. Its parse/generate steps (6-9 s) walk the corpus |
| Corpus Ratchet | 2.5 | 3.3 | 3.4 | 3.3 | Yes: t27c suite --ratchet --corpus-only, 129 s of it |
| Seal Coverage | 1.5 | 1.9 | 2.0 | 1.8 | Yes: "Every seal still describes its spec", 35 s |
| Spec Parse Ratchet | 1.0 | 1.2 | 1.2 | 1.3 | Yes, but the walk is seconds; 48 s is the t27c build |
| Spec Guards | 1.3 | 1.2 | 1.1 | 0.8 | Yes: seal currency, 8 s |
| Verilog Widths | 0.9 | 1.1 | 1.1 | 1.0 | Yes: corpus scan, 8 s |
Every one of these also builds t27c (45-60 s); that is item C, not this one.
The use graph on master 1f9b370 (path-keyed, resolved like bootstrap/src/use_resolve.rs): 1287 specs, 812 edges, 58 imported specs. A changed spec's reverse closure is 1 spec for 1229 of 1287 (median 1, p90 1, p99 7); the widest are specs/base/types.t27 396, specs/math/constants.t27 274, specs/numeric/gf16.t27 29.
Boundary
specs/ci/affected.t27 and its generated gen/c/ci/affected.c. No workflow, no tri command, no script: the CI wiring is hand-written glue and is listed below as an owner decision.
User Scenarios & Testing
- A spec-only PR changes one leaf spec; CI runs that spec, not 1287.
- A PR changes
specs/base/types.t27; CI runs it and the 395 specs that import it, transitively. - A PR deletes a spec; the specs that imported it run (their
usenow resolves to nothing), the deleted one does not. - Negative controls: a change to
bootstrap/,cli/,tools/,.github/or a non-spec, non-prose file underspecs/means run everything; a spec's imports are not selected by it; an unreadable diff line, a missing section marker, or a working set that does not fit means run everything.
Requirements
- The rule in
.t27: anything outsidespecs/-> run all; a changed.t27/.triunderspecs/-> it plus every spec that transitivelyuses it; prose underspecs/-> nothing; deleted and renamed specs handled through the union of the base and headusegraphs; fail closed on anything unreadable. - The same input shape as
specs/policy/own_language.t27(one buffer:git diff --name-status, then--graphedges, then--corpus). - Only t27 plus generated code.
Success Criteria
t27c test-report specs/ci/affected.t27on the t27c lab: FAIL 0; reversing the edge direction in the closure, or classifying an outside path as a seed, fails tests.- The generated C, fed the real master graph, selects 396 for
specs/base/types.t27, 29 forspecs/numeric/gf16.t27and 1 for a leaf spec, in under 50 ms. - validate, check-linked-issue, parse-ratchet, own-language and Corpus ratchet green on the PR.
Still missing (owner decision, not in this issue's PR)
t27chas no command that prints the path-keyedusegraph.t27c graph --format jsonkeys nodes by module name (124 specs share 24 names) and drops specs that fail to parse (1211 of 1287);t27c dependsprints one file's direct imports. The resolver exists twice in Rust (bootstrap/src/use_resolve.rsuse_targets,cli/t27b/src/blockers.rsuse_closure) and neither is reachable from the CLI.t27c suitehas no option to take a list of specs.
Activity
gHashTag commentedon Oct 5, 2026
Glue still needed to put
specs/ci/affected.t27to work. All of it is hand-written workflow/Rust/Python, so it is an owner decision (owner-approved-foreign); nothing below is in #6605. Minutes are the measured jobs in the issue body (spec-only PRs #6582, #6578, #6557, #6556).usegraph from t27c (prerequisite for 2-4). A subcommand that printsimporter<TAB>importedper resolveduse, viabootstrap/src/use_resolve.rsuse_targets(the resolver t27b's cache key already mirrors incli/t27b/src/blockers.rsuse_closure). CI runs it on the base and the head checkout and feeds the union. Rust edit inbootstrap/src/main.rs..github/workflows/emit-bitexact-gate.yml:paths:containsspecs/**, so every spec PR runs it, but its heavy steps read a fixed set of specs (specs/ternary/**,specs/igla/race/ternary_mac.t27,systolic_ternary.t27,gft_smul.t27,gft_sadd.t27). Add a first step that runsgen/c/ci/affected.cand anif:on "Exhaustive cross-target agreement" (103-195 s), "Generated RTL is bit-exact..." (81-137 s), "GF-T primitives agree..." (23-37 s), the trainer, microcode (11-20 s) and fuzz steps: run them only on RUN_ALL or when the affected set meets that list. Saves about 4-7.5 min per spec-only PR, and it is the critical-path job (5.1-8.5 min)..github/workflows/corpus-ratchet.yml, step "Run the corpus ratchet": runt27c suite --ratcheton the affected list instead of--corpus-only. Needs a newt27c suiteoption that takes a list of specs (Rust,bootstrap/src/main.rsSuite). Saves about 2 min per PR (the 129 s step drops to seconds for a leaf spec)..github/workflows/seal-coverage.yml, step "Every seal still describes its spec" (35 s): check only the affected specs' seals. Python edit intools/check_seal_coverage.py. Saves about 0.5 min.tri test --affected(scripts/tri, bash).Total: about 6.5-10 runner-minutes per spec-only PR whose affected set does not reach the GF-T specs. Time to green on such a PR drops from 5-8.5 min (emit-bitexact) to about 2 min (the remaining t27c builds). A PR touching
specs/base/types.t27(396 specs) or anything outsidespecs/still runs everything.fix(ci): affected.t27 imports has_prefix and is_letter_of from own_la…
Merge pull request #6605 from gHashTag/ci-affected-specs
spec(policy): line_end and text_end live once, in text_lines.t27 (Refs …
policy: the L2 GENERATION rule lives in specs/policy/l2_generation.t2…
t27b: tail expressions and discarded calls as the reference prints th…
ci(spec-guards): published figures are red only for a defect of the c…