Repository navigation
Gate the Required Check Name, Latest Flag, Lint Interpreter, and .NET Workspaces - #3796
Conversation
… Workspaces Give the pull request aggregator an expression name that yields the ruleset-bound context only on a pull_request run, so a dispatch or push run on a pull request's head cannot mask its required check, and teach the audit's interface check that shape. Drop the base-branch filter that stranded a retargeted pull request, and document what validate-task expects of an adopting repository. Decide make_latest against the current Latest release so a re-run of an older release cannot take the flag, run the lint job under the first python-versions entry, and run dotnet format and dotnet test once per tracked workspace where the root holds none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lint job's Python steps now take the caller's first python-versions entry through a step-scoped UV_PYTHON, so the prose and repo gates keep running under the interpreter floor the hub's own uv scripts declare. The audit reads a workflow's triggers through one helper, and the release guard tests reuse spec/git_env.py's location-free environment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tubs The branch.operational.prtriggers assertion now names an unfiltered pull_request trigger, and the fidelity model says the ruleset-bound check name is given only on a pull_request run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Resolve lint Python version step now runs only where a Python step reads its output. The aggregator-name matcher reuses the audit's unquoted-space normalizer, and the new release and lint tests read step keys through workflow_steps.py, asserting the latest decision and the release create share one condition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
|
A recorded local strict-review pass covers head |
There was a problem hiding this comment.
🟡 Changes recommended
Several moderate active findings remain, including unreliable project discovery and workflow parsing plus a Windows test invocation issue, so changes are needed before approval.
5 open issues
What changed in this PR
This pull request hardens pull-request validation and release workflows, updates workflow contracts and documentation, aligns linting with the caller’s Python version, and adds workspace-aware .NET checks with regression coverage.
Changes:
- Gates the required check name to pull-request runs and removes base-branch filters.
- Adds version-aware release
Latestselection and lint interpreter resolution. - Supports .NET formatting and testing across repository workspaces.
- Updates workflow audits, contracts, documentation, generated guidance, and tests.
- Unresolved findings remain around workspace detection, test-project filtering, pull-request retarget triggers, Windows test execution, and related documentation/audit handling.
This pull request requires changes.
| File | Description |
|---|---|
WORKFLOW.md |
Documents event-specific required-check names and operational trigger behavior. |
tests/test_release_guards.py |
Tests release Latest selection, trigger filters, lint interpreter selection, and .NET workspace execution. |
tests/test_audit_helpers.py |
Tests required-check expressions and shipped pull-request stub conformance. |
spec/project-types.json |
Updates the operational pull-request trigger contract. |
spec/fidelity-model.md |
Documents the conditional required-check interface. |
spec/audit.py |
Recognizes conditional aggregator names and validates workflow events. |
docs/reusable-workflows.md |
Updates adoption guidance, trigger behavior, interpreter behavior, and task preconditions. |
catalog/snippets/workflows/test-pull-request.yml |
Removes the base filter and uses an event-specific aggregator name. |
catalog/snippets/workflows/test-pull-request-smoke.yml |
Removes the base filter and uses an event-specific aggregator name. |
.github/workflows/validate-task.yml |
Resolves the lint interpreter and runs .NET checks against discovered workspaces. |
.github/workflows/test-pull-request.yml |
Updates the hub pull-request workflow trigger and aggregator name. |
.github/workflows/build-release-task.yml |
Computes and passes the release make_latest decision. |
.github/skills/workflow-ci-contract/references/d-guarantees.md |
Regenerated workflow-contract guidance. |
.claude-plugin/fleet-skills/skills/workflow-ci-contract/references/d-guarantees.md |
Regenerated Claude workflow-contract guidance. |
.claude-plugin/fleet-skills/.source-digests/workflow-ci-contract |
Updates the generated skill source digest. |
.agents/skills/workflow-ci-contract/references/d-guarantees.md |
Updates the authored workflow-contract guidance. |
Review effort: Lite
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #3796 +/- ##
========================================
Coverage 82.34% 82.35%
========================================
Files 28 28
Lines 9454 9462 +8
========================================
+ Hits 7785 7792 +7
- Misses 1669 1670 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…D1.6 The lint interpreter test hands its jq script to bash as a file, since Git Bash collapses the filter's doubled backslash in a -c argument. D1.6 names the per-workspace dotnet test form, the interface check reuses the workflow it already composed to read triggers, and its missing-name message names both accepted forms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A solution holding no test project fails dotnet test under the testing platform with no test projects found, measured on SDK 10 with the coverage flags. So where the root holds no workspace, the test step names each tracked test project, while the style step still names each solution. The lint job's comment and docs now say only its Python steps take the caller's first interpreter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
…ings With an Acknowledgment Record, and Mark Direct-Push Closures (#3797) Promotes the changes merged into `develop` since promotion #3778 to `main`, the second round of a backlog burndown. - [#3784](#3784) has the write guard read a push option attached to `-o` as that option's value, as git does, rather than as more short flags. It also walks a short-option cluster carrying a digit, such as `-4f`. - [#3796](#3796) fixes five pull-request-gate and release-task defects: - Each aggregator's `name:` gives the ruleset-bound required check name only on a `pull_request` run, so a dispatch run cannot mask it. `spec/audit.py` `check_interface` reads that expression. - The stubs drop the `pull_request` base-branch filter, and "Adopting the Gates" states the task's preconditions. - A re-run release takes the Latest flag only where its version sorts at or above the current Latest release. - The `lint` job runs its Python steps under the first `python-versions` entry. - `dotnet format` and `dotnet test` run per tracked workspace where the root holds no solution or project. - [#3793](#3793) has `pr_review.py merge` read an acknowledgment record, posted with `comment --ack KEY=OUTCOME`, for findings no thread carries and for a verdict naming no counted finding. Every `merge` run ends on one `next=` line the skills follow, and the digest reads the excluded-files refusal as `refusal=EXCLUDED`. - [#3792](#3792) has a direct push to `develop` on an operational-model repository name the issues it fixes in its commit message's on-promotion line. `backlog-burndown` assembles the promotion body from the pull request that merged each commit, falling back to a direct push's own message. Its claim lookup matches only claim comments, and `git-commit-conventions` names the abandon-discard exception. - [#3798](#3798) has `has_prose` read an inline token's text children outside code, so an inline-code span alone no longer counts as prose. It answers this promotion's first review round. - [#3799](#3799) has `check_interface` read each job by its key on a workflow a non-PR event triggers. A job whose off-PR name is the required check gets its own DRIFT. It answers this promotion's first review round. ## Downstream effect From #3796: once this reaches `main`, every downstream `test-pull-request.yml` whose aggregator carries a literal `name:` shows `check_interface` DRIFT. It shows that until its next resync. A resync clears it by replacing the aggregator's `name:` line with the expression. No ruleset changes, since the `context:` a pull request run posts is unchanged. Fixes #3720 Fixes #3334 Fixes #1516 Fixes #3487 Fixes #1485 Fixes #1938 Fixes #3718 Fixes #3724 Fixes #3777 Fixes #3773 Fixes #1276 Fixes #1307 Fixes #3780 🤖 Generated with [Claude Code](https://claude.com/claude-code)

Summary
Fixes five pull-request-gate and release-task defects, each the way its recorded maintainer answer chose.
name:is now an expression that gives the ruleset-boundCheck pull request workflow status jobonly whengithub.event_name == 'pull_request'. Any other run reportsCheck workflow status job. That applies to both catalog stubs, the docs example, and the hub's owntest-pull-request.yml.spec/audit.pycheck_interfacenow accepts that expression as naming the required check. It reports a literal name in a workflow that any other event also triggers. WORKFLOW.md D1.5 andspec/fidelity-model.mdstate the rule. The rulesetcontext:does not change, so no live ruleset needs editing.pull_requestbase-branch filter is gone from both stubs, the docs example, and the hub's own stub. A retarget firesedited, so the filter left a retargeted stacked pull request with no run. "Adopting the Gates" now says what the task expects of the repo it gates: a mypy target (files), one type checker with thevalidatehook as the route to a second, and apull_requesttrigger for the prose gate. The operational walkthrough in WORKFLOW.md section 6 and thebranch.operational.prtriggersassertion inspec/project-types.jsonare updated to match.Decide latest release steppassesmake_latest. A main release takes the flag only where its version sorts at or above the current Latest release's tag. A prerelease never takes it. A 404 (no Latest release yet) lets the release take it, and any other read failure fails the step with a named cause.lintjob's Python steps now run under the firstpython-versionsentry. A newResolve lint Python version stepreads it with a named-cause refusal. The Sync, Lint, Format, and Type check steps each setUV_PYTHONto that version, so auv.lockwhose floor is above 3.13 syncs. setup-uv stays at 3.13, the floor the hub's own uv scripts declare, so the prose and repo gates still run under it. The pip path's separateUV_PYTHONoverride and its check are removed, since these four steps now carry the interpreter.dotnet formatanddotnet testat a root with no workspace. Both steps keep the bare call where the root tracks a.sln,.slnx, or.csproj. Otherwise the style step runs once per tracked solution, or once per.csprojwhere the tree has no solution. The test step runs once per tracked*Tests*.csproj, the glob its ownhashFilesguard uses. It names no solution, becausedotnet test --projecton a solution or project holding no test project exits 1 ("No test projects were found"), measured on SDK 10 under the testing platform with the coverage flags.Closes on promotion: #3334, #1516, #3487, #1485, #1938
Verification
spec/audit.py --selftest,spec/validate.py,scripts/build_dist.py --check, ruff format and check, and mypy all pass locally.docker_lint.pypasses for actionlint, markdownlint, cspell, and editorconfig-checker.make_latestdecision covers newer, older, numeric-order, refresh,v-prefixed, 404, prerelease, and 502 cases.check_interfacecases cover the expression form, a literal name under a dispatch trigger, the list-formon:, an expression that names the check on every event, and one gated on the wrong event. A test also confirms every shipped stub meets the carried contract inspec/files.json.dotnet format style <solution|project>was measured on SDK 10 against a subdirectory-only tree, and so wasdotnet test --projecton a solution and on a project that hold no test project.Downstream effect
Every downstream
test-pull-request.ymlwhose aggregator still has the literal name will showcheck_interfaceDRIFT until its next resync. The audit now reports a literal name in any stub thatworkflow_dispatchorpushalso triggers, and that is every adopted stub today. A resync clears it by replacing the aggregator'sname:line with the expression from the snippet. No ruleset changes, since thecontext:string a pull request run posts is unchanged.Introduced findings from the local passes, and their outcomes
tests/test_release_guards.pytest_lint_runs_under_the_first_named_interpreter, introduced: Git Bash collapses the doubled backslash in the jq filter in abash -cargument. Fixed in f6fd08a, which runs the script from a file..github/workflows/validate-task.ymlRun unit tests step, introduced: a solution holding no test project failsdotnet test. Fixed in 4ecb00d, which names test projects instead..github/workflows/validate-task.ymlRun unit tests step, introduced: in a tree with no root workspace, a non-test helper project whose name containsTestsis passed todotnet test --project, which exits 1. Declined under GOVERNANCE.md "Trust Boundaries and Hardening Effort". The layout is the maintainer's own and the failure is loud rather than silent. The step's ownhashFiles('**/*Tests*.csproj')guard already treats that name as a test project.spec/audit.pycheck_interface, introduced: the function re-parsed the text to readon:. Fixed in f6fd08a, which reads the composed tree.spec/audit.pyPR_ONLY_NAME, introduced: only the shipped operand order is accepted. Declined: the carried snippet's form is what a resync delivers, and recognizing every boolean rewrite answers no requirement. f6fd08a makes the DRIFT message name both accepted forms.WORKFLOW.mdD1.6, introduced: the per-workspace--projectform was not mentioned. Fixed in f6fd08a and 4ecb00d..github/workflows/validate-task.ymlCheck C# style step, introduced: where the root holds no workspace and the tree tracks a solution, a.csprojno tracked solution references is not style-checked. Declined: the validate-task Runs dotnet format at the Root Where No Project or Solution Sits There #1938 maintainer answer chose "once per tracked .sln/.slnx, else per .csproj", and this is that rule's stated behavior.Pre-existing findings, filed
.github/workflows/validate-task.ymlCheck C# style stepandRun unit tests step, correctness: a root holding more than one solution or project keeps the bare call, which fails with MSB1011. Filed as Name Each Root Workspace Where the Root Holds More Than One .NET Solution or Project #3795.Out-of-claim findings, filed
GOVERNANCE.md:275, contract drift: the job-name bullet describes the aggregator name as a literal and does not mention the expression form. That file is held by another change. Filed as Name the Expression Form of the Aggregator in GOVERNANCE.md's Job Name Rule #3790..github/workflows/validate-task.ymlCheck C# style stepandRun unit tests step, design: thedotnet_workspacesresolver is copied into both jobs. A test asserts the two copies are identical. Moving it into a composite action is filed as Resolve .NET Workspaces Once in a validate-task Composite Action #3791.python-codestyleprofiles do not name the mypyfilestarget that Adopting the Gates omits three preconditions of validate-task, and each fails silently or confusingly #1516 item 1 documents in "Adopting the Gates". Filed as Name the mypy Target Key in the python-codestyle Profiles #3787.🤖 Generated with Claude Code