Skip to content

A change runs the gates its paths can affect, decided by one model of what affects what, and a change to the pipeline itself or a path the model does not know runs every gate

A pull request does not run every gate because a gate exists; it runs the gates a change under its paths can reach, and the decision is not a guess. .ai/repo/ci/gates.yaml declares the gates and the path classes with the gates each class can affect, and scripts/ci-plan computes the selection from that model and the changed paths. The always gates (the shell lint, the structural checks) are in every plan. A path that no class knows, or a path in the pipeline itself, escalates to the full plan, and the plan names the path that did.

declared statusdeclared status: guaranteed Declared deterministic and blocking, implemented, and naming the behavioural test that is to prove it. Whether a recorded run supports it is the evidence verdict, shown beside it.

recorded evidencerecorded evidence: not run Declared guaranteed, and not supported by a recorded run: the test is named, and no passing run of the current inputs is recorded. how evidence is recorded

The gates that always run are in every plan; a changed path selects the gates of every class it matches, as a union; a class may escalate; a gate brings what it implies and what it requires; the plan is deterministic and says for every gate why it ran or did not. The workflow reads the plan's outputs and carries no path list of its own; test/cases/26_ci_wiring.sh proves every gate's job exists, is gated on the plan and is needed by the verdict.

What it means

A pull request does not run every gate because a gate exists; it runs the gates a change under its paths can reach, and the decision is not a guess. .ai/repo/ci/gates.yaml declares the gates and the path classes with the gates each class can affect, and scripts/ci-plan computes the selection from that model and the changed paths. The always gates (the shell lint, the structural checks) are in every plan. A path that no class knows, or a path in the pipeline itself, escalates to the full plan, and the plan names the path that did.

How it works

The planner flattens the model, matches every changed path against every class pattern (** crosses directories, * does not), takes the union of the matched classes' gates, adds what a selected gate implies and requires, and writes a plan: the mode, the reason, every gate with its job and whether and why it was selected. The plan job of .github/workflows/validate.yml runs it against the pull request's diff from the base it will merge into and turns the plan into job outputs the other jobs are gated on; the workflow carries no path list of its own. test/cases/94_ci_plan.sh drives the planner with listed paths and asserts the relationships that matter: Rust, site, documents, the distribution, the pipeline, an unknown path, an inert path, the union, the implied and required gates, and that a model with a dangling reference is refused.

How to see it

just ci-plan                                   # this working tree against master
printf 'docs/DESIGN.md\n' | scripts/ci-plan --files - --format text
printf 'apps/majordomus-cli/src/lib.rs\n' | scripts/ci-plan --files - | jq .selected
scripts/ci-plan --check                        # the model resolves
just test-shell 94_ci_plan

What it does not cover

The model is a statement about what reads what, written by people and checked by the cases that name representative paths; a new consumer of a path that nobody added to the model is a gap the full plan on master closes after the merge, not before. The planner decides which gates run, never how they pass: each gate is its own script with its own evidence.

Why it exists

Before the model, every push and every pull request ran two operating-system matrices of every gate over every commit, and a separate Pages workflow ran the suite and the browser probe again over the same commit; a documentation change waited for the Rust coverage build and a Rust change for the browser. The measurements are in .ai/repo/ci/baseline.json (scripts/ci-baseline --table). Majordomus prefers data-driven behaviour, so the dependency map lives once, as data the tests read, rather than as path filters scattered through workflow files, which are also what leaves a required check pending forever when a filtered workflow never starts.

Detail rendered from docs/claims/ci-planned-gates.md.

From the statement to the proof

  1. 1 · the public statement

    A change runs the gates its paths can affect, decided by one model of what affects what, and a change to the pipeline itself or a path the model does not know runs every gate

    Made on CI.

  2. 2 · the canonical claim

    ci-planned-gatesdeclared status: guaranteed in docs/CLAIMS.yaml

    Defined in .ai/repo/ci/gates.yaml

  3. 3 · the implementation

    scripts/ci-plan

  4. 4 · the test and the scenarios

    Test: test/cases/94_ci_plan.sh

  5. 5 · the recorded runs

    recorded evidence: not run how evidence is recorded

Verify it yourself

The test runs in a disposable temporary repository and asserts the behaviour, not a string in the source.

from a clone of the repository
bash test/run.sh 94_ci_plan

Searches the titles and descriptions of every page this site publishes.