Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9ca08e9
Add a test-matrix Input and Install Hook to the Validate Task's pytes…
ptr727 Oct 4, 2026
22678ec
Merge remote-tracking branch 'origin/develop' into feature/validate-d…
ptr727 Oct 4, 2026
2640fcc
Check the test-matrix Input Before the Matrix Expands and Document th…
ptr727 Oct 4, 2026
c37c62d
Merge remote-tracking branch 'origin/develop' into feature/validate-d…
ptr727 Oct 4, 2026
31567e2
Reject Multiple JSON Values in the Matrix Checks and Pin the Install …
ptr727 Oct 4, 2026
624f116
Guard python-versions on the test-matrix Path and Tighten the Install…
ptr727 Oct 4, 2026
003b24a
Merge remote-tracking branch 'origin/develop' into feature/validate-d…
ptr727 Oct 4, 2026
5c34eb5
Merge remote-tracking branch 'origin/develop' into feature/validate-d…
ptr727 Oct 4, 2026
15cefbe
Make the Lint Job's python-versions Guard Reject Concatenated Values Too
ptr727 Oct 4, 2026
da750c3
Scope the python-versions Guard Comment to the unit-test Job
ptr727 Oct 4, 2026
2152989
Merge remote-tracking branch 'origin/develop' into feature/validate-d…
ptr727 Oct 4, 2026
c1496d5
Build the Unit-Test Matrix From the Check Job's Output
ptr727 Oct 4, 2026
1f48548
Merge remote-tracking branch 'origin/develop' into feature/validate-d…
ptr727 Oct 4, 2026
c2914eb
Name and Validate python-versions in the Matrix-Building Job
ptr727 Oct 4, 2026
7cf9411
Drop the Duplicate python-versions Check From the unit-test Job
ptr727 Oct 4, 2026
ec156ba
Carry the python-versions Check Rationale to the Matrix Job
ptr727 Oct 4, 2026
cc27d8f
Split and Correct the Moved python-versions Check Comments
ptr727 Oct 4, 2026
05b368b
Name Both Raw Readers and the First Failure Point in the Comments
ptr727 Oct 4, 2026
762afeb
Stop Enumerating Failure Points in the python-versions Check Comment
ptr727 Oct 4, 2026
e7bbacc
Give the python-versions Check Comment a Fitting Antecedent
ptr727 Oct 4, 2026
5385e02
Drop the Counterfactual Lines From the python-versions Check Comment
ptr727 Oct 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output
### D1 - PR Fast-Feedback (Smoke)

- **D1.1 Only changed targets build.** Input: a PR touching some targets. Output: the paths-filter marks exactly those targets and only their smoke builds run. Unchanged targets skip. A repo's own targets MUST each have a filter entry (so a touched target is never silently skipped), and that entry lists paths rather than negating them, so a change matching no entry marks nothing and every smoke build skips. *Prevents: rebuilding everything, and a changed target slipping through unbuilt.*
- **D1.2 A validation job always runs.** Input: any PR. Output: a validation job runs unconditionally and the aggregator `needs:` it. That job is the caller's own job reaching the reusable validator, named `validate` in every shipped stub, and that name is what the aggregator's `needs:` carries. The validator's internal jobs (`lint`, `unit-test` and `validate` in the hub's `validate-task.yml`) are not addressable from a caller, so a `validate` in a caller's `needs:` list always names the caller's own job rather than the validator's internal one of the same name. The validator detects the tree rather than the repo's language, running the doc and repo gates everywhere, the `dotnet test` path only where a `*Tests*.csproj` project exists, and the Python path in each directory the caller declares in its `python-directories` input, or at the root where it declares none and a root `pyproject.toml` is tracked, so a non-.NET repo calls the same one rather than replacing it. At that undeclared root the test leg runs only where a `tests/` suite sits beside a `uv.lock` or `requirements*.txt`, while a declared directory without `tests/` fails (D1.6). The declaration rather than discovery decides what is gated, since a stray or vendored `.py` file would otherwise change the gate silently, and the validator warns about a tracked `.py` file no declared directory covers. It checks out the triggering commit, `github.sha`, with no `ref` input, because a reusable workflow reads its caller's `github` context, so a publisher's call validates the same commit its publish job pins. A repo whose tests take another shape can run them from its own `.github/actions/validate/action.yml` hook, which the validator runs where that file exists. That hook receives no secret, so a repo whose other-shape tests owe D1.6's coverage upload is one whose validation the validator cannot express. A repo whose validation it cannot express **replaces** the call (not deletes it) with its own validator and re-points the aggregator's `needs:` to the replacement. `smoke-build` `needs:` the `changes` job rather than the validation job, so no second `needs:` moves with it. *Prevents: a PR merging with no validation, or a dangling `needs:` that stops the whole workflow from loading.*
- **D1.2 A validation job always runs.** Input: any PR. Output: a validation job runs unconditionally and the aggregator `needs:` it. That job is the caller's own job reaching the reusable validator, named `validate` in every shipped stub, and that name is what the aggregator's `needs:` carries. The validator's internal jobs (`test-matrix`, `lint`, `unit-test` and `validate` in the hub's `validate-task.yml`) are not addressable from a caller. So a `validate` in a caller's `needs:` list always names the caller's own job, never the validator's internal one. The validator detects the tree rather than the repo's language, running the doc and repo gates everywhere, the `dotnet test` path only where a `*Tests*.csproj` project exists, and the Python path in each directory the caller declares in its `python-directories` input, or at the root where it declares none and a root `pyproject.toml` is tracked, so a non-.NET repo calls the same one rather than replacing it. At that undeclared root the test leg runs only where a `tests/` suite sits beside a `uv.lock` or `requirements*.txt`, while a declared directory without `tests/` fails (D1.6). The declaration rather than discovery decides what is gated, since a stray or vendored `.py` file would otherwise change the gate silently, and the validator warns about a tracked `.py` file no declared directory covers. It checks out the triggering commit, `github.sha`, with no `ref` input, because a reusable workflow reads its caller's `github` context, so a publisher's call validates the same commit its publish job pins. A repo whose tests take another shape can run them from its own `.github/actions/validate/action.yml` hook, which the validator runs where that file exists. That hook receives no secret, so a repo whose other-shape tests owe D1.6's coverage upload is one whose validation the validator cannot express. A repo whose validation it cannot express **replaces** the call (not deletes it) with its own validator and re-points the aggregator's `needs:` to the replacement. `smoke-build` `needs:` the `changes` job rather than the validation job, so no second `needs:` moves with it. *Prevents: a PR merging with no validation, or a dangling `needs:` that stops the whole workflow from loading.*
- **D1.3 Smoke never publishes and never uploads.** Input: `smoke: true`. Output: full compile/lint/test, but no registry/image push, no release, and **no** artifact uploads (every `upload-artifact`, including any aggregation job, is gated on smoke being false, written `!inputs.smoke` at the workflow layer and `inputs.smoke != 'true'` in a composite action, whose inputs are strings). *Prevents: a PR publishing, and orphaned artifacts churning the storage quota.*
- **D1.4 Workflow-file changes are not smoke-built.** Input: a PR changing only `.github/workflows/**`. Output: the paths-filter marks no target, so smoke-build skips. An inclusion list satisfying D1.1 reaches this by leaving workflow paths out of every target's entry. *Implication: a workflow-only change is not smoke-built, but actionlint still validates it in CI.*
- **D1.5 One required aggregator gates merge.** Input: any PR. Output: a single aggregator job must **succeed**, run under `if: always()` so a failed or skipped dependency cannot skip the gate itself, `needs:` the validation job, and the `changes` and `smoke-build` jobs too wherever the repo has a smoke build, treat a **skipped** smoke build as pass, and **block** on `failure`/`cancelled`. Its name is ruleset-bound: the job `name:` and the ruleset `context:` are the same string and MUST be renamed together, never independently. *Prevents: a paths-filter error letting a target-changing PR merge unbuilt.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
74f129557b5d1ec1
ab8df16df4f49971
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output
### D1 - PR Fast-Feedback (Smoke)

- **D1.1 Only changed targets build.** Input: a PR touching some targets. Output: the paths-filter marks exactly those targets and only their smoke builds run. Unchanged targets skip. A repo's own targets MUST each have a filter entry (so a touched target is never silently skipped), and that entry lists paths rather than negating them, so a change matching no entry marks nothing and every smoke build skips. *Prevents: rebuilding everything, and a changed target slipping through unbuilt.*
- **D1.2 A validation job always runs.** Input: any PR. Output: a validation job runs unconditionally and the aggregator `needs:` it. That job is the caller's own job reaching the reusable validator, named `validate` in every shipped stub, and that name is what the aggregator's `needs:` carries. The validator's internal jobs (`lint`, `unit-test` and `validate` in the hub's `validate-task.yml`) are not addressable from a caller, so a `validate` in a caller's `needs:` list always names the caller's own job rather than the validator's internal one of the same name. The validator detects the tree rather than the repo's language, running the doc and repo gates everywhere, the `dotnet test` path only where a `*Tests*.csproj` project exists, and the Python path in each directory the caller declares in its `python-directories` input, or at the root where it declares none and a root `pyproject.toml` is tracked, so a non-.NET repo calls the same one rather than replacing it. At that undeclared root the test leg runs only where a `tests/` suite sits beside a `uv.lock` or `requirements*.txt`, while a declared directory without `tests/` fails (D1.6). The declaration rather than discovery decides what is gated, since a stray or vendored `.py` file would otherwise change the gate silently, and the validator warns about a tracked `.py` file no declared directory covers. It checks out the triggering commit, `github.sha`, with no `ref` input, because a reusable workflow reads its caller's `github` context, so a publisher's call validates the same commit its publish job pins. A repo whose tests take another shape can run them from its own `.github/actions/validate/action.yml` hook, which the validator runs where that file exists. That hook receives no secret, so a repo whose other-shape tests owe D1.6's coverage upload is one whose validation the validator cannot express. A repo whose validation it cannot express **replaces** the call (not deletes it) with its own validator and re-points the aggregator's `needs:` to the replacement. `smoke-build` `needs:` the `changes` job rather than the validation job, so no second `needs:` moves with it. *Prevents: a PR merging with no validation, or a dangling `needs:` that stops the whole workflow from loading.*
- **D1.2 A validation job always runs.** Input: any PR. Output: a validation job runs unconditionally and the aggregator `needs:` it. That job is the caller's own job reaching the reusable validator, named `validate` in every shipped stub, and that name is what the aggregator's `needs:` carries. The validator's internal jobs (`test-matrix`, `lint`, `unit-test` and `validate` in the hub's `validate-task.yml`) are not addressable from a caller. So a `validate` in a caller's `needs:` list always names the caller's own job, never the validator's internal one. The validator detects the tree rather than the repo's language, running the doc and repo gates everywhere, the `dotnet test` path only where a `*Tests*.csproj` project exists, and the Python path in each directory the caller declares in its `python-directories` input, or at the root where it declares none and a root `pyproject.toml` is tracked, so a non-.NET repo calls the same one rather than replacing it. At that undeclared root the test leg runs only where a `tests/` suite sits beside a `uv.lock` or `requirements*.txt`, while a declared directory without `tests/` fails (D1.6). The declaration rather than discovery decides what is gated, since a stray or vendored `.py` file would otherwise change the gate silently, and the validator warns about a tracked `.py` file no declared directory covers. It checks out the triggering commit, `github.sha`, with no `ref` input, because a reusable workflow reads its caller's `github` context, so a publisher's call validates the same commit its publish job pins. A repo whose tests take another shape can run them from its own `.github/actions/validate/action.yml` hook, which the validator runs where that file exists. That hook receives no secret, so a repo whose other-shape tests owe D1.6's coverage upload is one whose validation the validator cannot express. A repo whose validation it cannot express **replaces** the call (not deletes it) with its own validator and re-points the aggregator's `needs:` to the replacement. `smoke-build` `needs:` the `changes` job rather than the validation job, so no second `needs:` moves with it. *Prevents: a PR merging with no validation, or a dangling `needs:` that stops the whole workflow from loading.*
- **D1.3 Smoke never publishes and never uploads.** Input: `smoke: true`. Output: full compile/lint/test, but no registry/image push, no release, and **no** artifact uploads (every `upload-artifact`, including any aggregation job, is gated on smoke being false, written `!inputs.smoke` at the workflow layer and `inputs.smoke != 'true'` in a composite action, whose inputs are strings). *Prevents: a PR publishing, and orphaned artifacts churning the storage quota.*
- **D1.4 Workflow-file changes are not smoke-built.** Input: a PR changing only `.github/workflows/**`. Output: the paths-filter marks no target, so smoke-build skips. An inclusion list satisfying D1.1 reaches this by leaving workflow paths out of every target's entry. *Implication: a workflow-only change is not smoke-built, but actionlint still validates it in CI.*
- **D1.5 One required aggregator gates merge.** Input: any PR. Output: a single aggregator job must **succeed**, run under `if: always()` so a failed or skipped dependency cannot skip the gate itself, `needs:` the validation job, and the `changes` and `smoke-build` jobs too wherever the repo has a smoke build, treat a **skipped** smoke build as pass, and **block** on `failure`/`cancelled`. Its name is ruleset-bound: the job `name:` and the ruleset `context:` are the same string and MUST be renamed together, never independently. *Prevents: a paths-filter error letting a target-changing PR merge unbuilt.*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output
### D1 - PR Fast-Feedback (Smoke)

- **D1.1 Only changed targets build.** Input: a PR touching some targets. Output: the paths-filter marks exactly those targets and only their smoke builds run. Unchanged targets skip. A repo's own targets MUST each have a filter entry (so a touched target is never silently skipped), and that entry lists paths rather than negating them, so a change matching no entry marks nothing and every smoke build skips. *Prevents: rebuilding everything, and a changed target slipping through unbuilt.*
- **D1.2 A validation job always runs.** Input: any PR. Output: a validation job runs unconditionally and the aggregator `needs:` it. That job is the caller's own job reaching the reusable validator, named `validate` in every shipped stub, and that name is what the aggregator's `needs:` carries. The validator's internal jobs (`lint`, `unit-test` and `validate` in the hub's `validate-task.yml`) are not addressable from a caller, so a `validate` in a caller's `needs:` list always names the caller's own job rather than the validator's internal one of the same name. The validator detects the tree rather than the repo's language, running the doc and repo gates everywhere, the `dotnet test` path only where a `*Tests*.csproj` project exists, and the Python path in each directory the caller declares in its `python-directories` input, or at the root where it declares none and a root `pyproject.toml` is tracked, so a non-.NET repo calls the same one rather than replacing it. At that undeclared root the test leg runs only where a `tests/` suite sits beside a `uv.lock` or `requirements*.txt`, while a declared directory without `tests/` fails (D1.6). The declaration rather than discovery decides what is gated, since a stray or vendored `.py` file would otherwise change the gate silently, and the validator warns about a tracked `.py` file no declared directory covers. It checks out the triggering commit, `github.sha`, with no `ref` input, because a reusable workflow reads its caller's `github` context, so a publisher's call validates the same commit its publish job pins. A repo whose tests take another shape can run them from its own `.github/actions/validate/action.yml` hook, which the validator runs where that file exists. That hook receives no secret, so a repo whose other-shape tests owe D1.6's coverage upload is one whose validation the validator cannot express. A repo whose validation it cannot express **replaces** the call (not deletes it) with its own validator and re-points the aggregator's `needs:` to the replacement. `smoke-build` `needs:` the `changes` job rather than the validation job, so no second `needs:` moves with it. *Prevents: a PR merging with no validation, or a dangling `needs:` that stops the whole workflow from loading.*
- **D1.2 A validation job always runs.** Input: any PR. Output: a validation job runs unconditionally and the aggregator `needs:` it. That job is the caller's own job reaching the reusable validator, named `validate` in every shipped stub, and that name is what the aggregator's `needs:` carries. The validator's internal jobs (`test-matrix`, `lint`, `unit-test` and `validate` in the hub's `validate-task.yml`) are not addressable from a caller. So a `validate` in a caller's `needs:` list always names the caller's own job, never the validator's internal one. The validator detects the tree rather than the repo's language, running the doc and repo gates everywhere, the `dotnet test` path only where a `*Tests*.csproj` project exists, and the Python path in each directory the caller declares in its `python-directories` input, or at the root where it declares none and a root `pyproject.toml` is tracked, so a non-.NET repo calls the same one rather than replacing it. At that undeclared root the test leg runs only where a `tests/` suite sits beside a `uv.lock` or `requirements*.txt`, while a declared directory without `tests/` fails (D1.6). The declaration rather than discovery decides what is gated, since a stray or vendored `.py` file would otherwise change the gate silently, and the validator warns about a tracked `.py` file no declared directory covers. It checks out the triggering commit, `github.sha`, with no `ref` input, because a reusable workflow reads its caller's `github` context, so a publisher's call validates the same commit its publish job pins. A repo whose tests take another shape can run them from its own `.github/actions/validate/action.yml` hook, which the validator runs where that file exists. That hook receives no secret, so a repo whose other-shape tests owe D1.6's coverage upload is one whose validation the validator cannot express. A repo whose validation it cannot express **replaces** the call (not deletes it) with its own validator and re-points the aggregator's `needs:` to the replacement. `smoke-build` `needs:` the `changes` job rather than the validation job, so no second `needs:` moves with it. *Prevents: a PR merging with no validation, or a dangling `needs:` that stops the whole workflow from loading.*
- **D1.3 Smoke never publishes and never uploads.** Input: `smoke: true`. Output: full compile/lint/test, but no registry/image push, no release, and **no** artifact uploads (every `upload-artifact`, including any aggregation job, is gated on smoke being false, written `!inputs.smoke` at the workflow layer and `inputs.smoke != 'true'` in a composite action, whose inputs are strings). *Prevents: a PR publishing, and orphaned artifacts churning the storage quota.*
- **D1.4 Workflow-file changes are not smoke-built.** Input: a PR changing only `.github/workflows/**`. Output: the paths-filter marks no target, so smoke-build skips. An inclusion list satisfying D1.1 reaches this by leaving workflow paths out of every target's entry. *Implication: a workflow-only change is not smoke-built, but actionlint still validates it in CI.*
- **D1.5 One required aggregator gates merge.** Input: any PR. Output: a single aggregator job must **succeed**, run under `if: always()` so a failed or skipped dependency cannot skip the gate itself, `needs:` the validation job, and the `changes` and `smoke-build` jobs too wherever the repo has a smoke build, treat a **skipped** smoke build as pass, and **block** on `failure`/`cancelled`. Its name is ruleset-bound: the job `name:` and the ruleset `context:` are the same string and MUST be renamed together, never independently. *Prevents: a paths-filter error letting a target-changing PR merge unbuilt.*
Expand Down
Loading
Loading