Automate repository conventions and stage static analysis enforcement - #16025
Automate repository conventions and stage static analysis enforcement#16025jamesfredley wants to merge 4 commits into
Conversation
Turn recurring objective review feedback into deterministic Gradle checks, pin executable workflow dependencies, and enforce the clean PMD baseline. Preserve report-only analysis for the quantified legacy backlog and document the remaining cleanup through focused follow-up issues. Assisted-by: opencode:gpt-5.6-sol
There was a problem hiding this comment.
Pull request overview
This PR adds deterministic build-time enforcement for previously manual repository hygiene checks (repository conventions, immutable GitHub Actions/Docker pins, message key uniqueness) and introduces staged, allowlist-based static analysis enforcement (PMD now blocking only where clean), without changing Grails runtime behavior.
Changes:
- Introduces
validateRepositoryConventions(RAT + skills/AGENTS/workflow/action YAML + container pins + messages key uniqueness) and wires it into aggregation tasks for the canonical root. - Refactors violation aggregation to use task-owned report outputs via root-relative marker files, cleaning scheduled outputs and failing on genuinely missing reports.
- Stages static-analysis enforcement via per-project PMD/SpotBugs allowlists and updates CI workflows + agent guidance accordingly.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| grails-spring-security/ui/plugin/grails-app/i18n/messages.spring-security-ui.properties | Removes a duplicate message key entry to satisfy uniqueness validation. |
| gradle/rat-root-config.gradle | Aligns RAT excludes with agent-local state/config files so provenance checks remain consistent. |
| gradle.properties | Adds the staged PMD enforcement allowlist for currently clean modules. |
| build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/RepositoryConventionsTaskSpec.groovy | Adds TestKit coverage for repository conventions validation scenarios. |
| build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsViolationAggregationPluginSpec.groovy | Expands TestKit coverage for marker-based aggregation, allowlists, and disabled-tool reporting. |
| build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsCodeStylePluginSpec.groovy | Verifies code style plugin also registers code analysis surface. |
| build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsCodeAnalysisPluginSpec.groovy | Adds tests for allowlists and build-dir exclusions in PMD configuration. |
| build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/RepositoryConventionsTask.groovy | Implements the repository conventions validator (skills, actions, container pins, messages keys). |
| build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsViolationAggregationPlugin.groovy | Reworks style/analysis aggregation to use markers, staged allowlists, and deterministic report lifecycle. |
| build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy | Emits marker files for style reports and applies code analysis plugin alongside style. |
| build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeAnalysisPlugin.groovy | Adds per-project allowlists, marker emission, and build-dir source exclusion for PMD. |
| build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GradleUtils.groovy | Adds project-path encoding for collision-free report naming + marker helpers. |
| build-logic/plugins/build.gradle | Adds SnakeYAML dependency to support safe YAML parsing in repository conventions checks. |
| AGENTS.md | Documents validateRepositoryConventions and updates skill inventory/guidance for staged analysis behavior. |
| .github/workflows/vulnerability-scan.yml | Pins external actions to immutable SHAs. |
| .github/workflows/release.yml | Pins reusable actions to SHAs, disables checkout credential persistence, and aligns ZIP excludes with RAT. |
| .github/workflows/release-publish-docs.yml | Pins reusable actions to immutable SHAs. |
| .github/workflows/release-close.yml | Pins reusable actions to immutable SHAs. |
| .github/workflows/groovy-joint-workflow.yml | Pins Mongo service image to an immutable digest. |
| .github/workflows/gradle.yml | Pins cache and deploy actions to immutable SHAs. |
| .github/workflows/codeanalysis.yml | Switches to staged enforcement + adds SpotBugs advisory passes with distinct artifacts. |
| .agents/skills/violation-fixer/SKILL.md | Updates guidance to reflect staged analysis and new conventions task/report semantics. |
| .agents/skills/mono-repo-integration/SKILL.md | Moves skill YAML front matter to the top so it conforms to the new validator. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 8.0.x #16025 +/- ##
===================================================
- Coverage 51.4404% 19.4467% -31.9937%
+ Complexity 17728 315 -17413
===================================================
Files 2039 66 -1973
Lines 95497 3723 -91774
Branches 16564 644 -15920
===================================================
- Hits 49124 724 -48400
+ Misses 39074 2855 -36219
+ Partials 7299 144 -7155 🚀 New features to boost your workflow:
|
|
@jdaugherty @matrei @borinquenkid @sbglasius this plus the newly created issues, should get us closer to PRs automatically following the expected syntax and passing all of the automated checkers. This PR was the next logical step. Should save us immense time on PR reviews, once we get this gradually in place. |
This is a good beginning. In a follow up start adding SKILL.md for automating the clean up of some of these issues. The CodeNarc script is broken. |
|
I'd like to review this before merging, but am currently traveling. I can take a look at this by Friday. |
Sanitize convention violations only at output boundaries and decode message property bundles explicitly as UTF-8. Add TestKit coverage for both behaviors. Assisted-by: opencode:gpt-5.6-sol
|
@jdaugherty no rush and you will find this somewhat incomplete, with several things carved out to issues. I am sure it needs some polish somewhere. @borinquenkid Agreed, this adds two skills.md files, but more will be better. |
jdaugherty
left a comment
There was a problem hiding this comment.
I started to work through the feedback on this and then realized I disagree with a central premise of this PR:
I do not agree with pinning our own actions - those actions exist to serve our own project so it only makes us have to create unnecessary PRs to update them. As for the github actions, it's just more maintenance and they're already trusted by the ASF by default. There hasn't been a precedence for security here so I don't see the benefit.
To recap:
- SHA-pin by commit: every third-party GitHub action (e.g. softprops/action-gh-release, gradle/actions, advanced-security/*) — agreed policy, the PR is right to do this.
- Version tag, never SHA: the actions/* namespace (checkout, cache, setup-java, upload-artifact — GitHub-official, ASF-approved upstream) and the project's own apache/grails-github-actions.
- Docker digests: immutable pinning agreed everywhere.
I'm going ahead and submitting this review now and will follow-up with more tomorrow.
jdaugherty
left a comment
There was a problem hiding this comment.
Below is a tweaked AI review, previous comments still stand:
Deterministic convention checks and a staged analysis ratchet are the right direction, and most of the machinery is solid. Two policy-level items and a convention suggestion up front; the technical findings are inline.
Action pinning scope. The project previously agreed to reference third-party actions by commit SHA, but to keep actions/* and our own apache/grails-github-actions on version references (aligned with the upstream ASF approvals — and we never pin our own actions). This PR pins both namespaces, and the new validator then enforces 40-hex SHAs for every external uses:, codifying the opposite of that decision. Please revert actions/* and apache/grails-github-actions to version references and give RepositoryConventionsTask matching namespace exemptions; the third-party SHA pins and the Docker digest rules are good and should stay. The maintenance cost shows up in this very diff: the cache pin (v4.2.3) was already behind what @v4 resolves to (v4.3.0), checkout v6.0.2 is behind v6.1.0, the setup-java swap silently crossed a major version (v4 → v5.2.0) inside the release-reproducibility steps, and the # asf comments describe a branch that has already moved two commits past the pin. For the record, I resolved every introduced SHA against its upstream tag and the mongo digest against the registry: all authentic, and the mongo pin is correctly the multi-arch manifest-list digest — the concern is policy and maintenance, not integrity.
Standard worktree location. Since this PR establishes the repository-conventions gate, let's also standardize a location for git worktrees created by agents/tooling (e.g. .worktrees/): gitignore it, exclude it from the conventions fileTree scans, RAT, and the source zip, and document it in AGENTS.md. Today a worktree nested inside the checkout is swept by the new whole-tree scans and by the RAT run that aggregateViolations now triggers locally.
Headline inline items. The aggregation rewiring has three related issues that together disable Gradle task avoidance and can leave misleading aggregate reports (eager withType().all, the global cleanup task, and the finalizer rewriting reports from partial data). The AGENTS.md skill-index enforcement collides with open #15977 and needs a decision before either merges. SpotBugs and Checkstyle aggregation lost (or never had) test coverage exactly where behavior changed. Details inline.
Action pinning is reverted to project policy: SHA-pin third-party actions only. The GitHub-official actions/* namespace and the project's own apache/grails-github-actions go back to version and branch references, so gradle.yml, vulnerability-scan.yml, release-close.yml and release-publish-docs.yml return to a zero diff against 8.0.x, undoing an unintended actions/setup-java v4 to v5 major-version crossing inside two release reproducibility pins. RepositoryConventionsTask now exempts those two owner namespaces from the 40-hex rule and from the cross-file SHA consistency check, while still requiring a non-empty ref. The AGENTS.md skill index is no longer required. The validator only rejects a skill path AGENTS.md references that does not exist, so both an explicit index and the directory-based discovery proposed in #15977 pass. PMD and SpotBugs enablement moves from a hardcoded list in gradle.properties to a per-project grailsCodeAnalysis extension opt-in, with the -P properties kept as overrides. The root aggregator derives the enabled set from applied plugins instead of a list. Aggregation restores configuration avoidance with configureEach and collection-based dependsOn and mustRunAfter, drops the cleanup dependency that erased every module's reports and defeated up-to-date checks, uses a realization-free rat lookup ordered with mustRunAfter, and removes the dead ignoreFailures input and the redundant style-to-conventions edge. Aggregate reports gained a Modules analyzed header, and the analyzer finalizers are gone so only the aggregate lane writes the Markdown and a partial run can no longer replace an authoritative report with stale marker data. Because the conventions gate was consequently unreachable from CI, the codestyle core job now invokes validateRepositoryConventions explicitly. Validator correctness: UTF-8 on every read and the report write, BOM stripping, specific front-matter diagnostics instead of phantom missing-key violations, an explicit skill directory-name violation, reported malformed properties lines, a single canonical repository root, and an explanatory rejection for non-literal container images. Message-key scanning widens to grails-app/i18n/**/*.properties and the build-output excludes are anchored to each project's configured build directory. Release provenance is corrected: the agent files are tracked symlinks to AGENTS.md and .agents/skills, so they ship in the source zip again, RAT exclusions narrow to what genuinely cannot carry a header, .asf.yaml is excluded to match its comment, and symlink timestamps are normalized with touch -h. .worktrees/ becomes the documented standard worktree location. SnakeYAML moves to a managed snakeyamlVersion property at 2.6. Test coverage grows from 77 to 85, adding SpotBugs and Checkstyle end-to-end paths, the style-side missing report, aggregate report immutability under a direct analyzer run, CI wiring in both directions, and the new validator branches. Fixtures take tool versions from the root gradle.properties instead of hardcoded literals. Assisted-by: claude-code:claude-5-opus
|
Thanks for the very thorough pass, @jdaugherty. All 69 threads are answered individually and pushed in 7a0aceb. The description is rewritten, because several claims in it were no longer true. Summary of what changed and the three places I did something other than what was literally asked. The pinning reversalEvery Rather than leave the validator encoding the opposite of the policy, Three places I did not do exactly what was asked
Two things the final review caught that your comments led toBoth came out of re-reviewing the finished diff, and both were caused by fixes to your feedback rather than by the original code. The conventions gate would have run in zero CI jobs. Removing the redundant The Other notable changes
Verification
No runtime module behavior changes, so CI will re-verify the full suite on the updated branch. |
|
@jdaugherty @matrei we should pin all or nearly all of the actions. It is consistent and the most secure, if GitHub or and apache repo get hacked. |
For the github actions, the ASF does not require a pin so this isn't a concern. There hasn't been a case for this previously, and until the ASF reverses policy here we shouldn't try to complicate our setup more. As for our own actions (grails-github-actions), we already have problems doing reviews and we don't want to have to update everything + get reviews for our own action changes. I do not want to have the extra work here and we have to update them more often now that we're pinning docker images in those actions. I 100% disagree with pinning versions of our own code since that code has to be used already across all of our releases. For anything else, I 100% agree since they're external. If the platform is hacked, we have larger problems than the action itself. |
.asf.yaml is the root marker used by findRootGrailsCoreDir. Excluding it from the signed source ZIP broke unpacked source builds. Keep it in the archive, continue excluding it from RAT only, terminate root search at the filesystem root, and cover both paths in GradleUtilsSpec. Assisted-by: opencode:xai/grok-4.5
|
Thanks for the detailed review. Addressed on chore/automate-project-conventions: 7a0aceb - main review feedback
9a8fbee - source-root marker
Open inline threads have been resolved against the current head. Happy to follow up on anything still unclear. |
|
@jdaugherty I am fine leaving the Apache Grails actions unpinned. |
🚨 TestLens detected 12 failed tests 🚨Here is what you can do:
Test SummaryCI - Groovy Joint Validation Build / build_grails > :grails-core:test
CI / Build Grails-Core (macos-latest, 21) > :grails-core:test
CI / Build Grails-Core (ubuntu-latest, 21) > :grails-core:test
CI / Build Grails-Core (ubuntu-latest, 25) > :grails-core:test
CI / Build Grails-Core (windows-latest, 25) > :grails-core:test
CI / Build Grails-Core Rerunning all Tasks (ubuntu-latest, 21) > :grails-core:test
🏷️ Commit: 9a8fbee Test Failures (first 10 of 12)GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (macos-latest, 21))GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (macos-latest, 21))GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 21))GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 21))GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 25))GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 25))GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (windows-latest, 25))GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (windows-latest, 25))GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core Rerunning all Tasks (ubuntu-latest, 21))GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core Rerunning all Tasks (ubuntu-latest, 21))Muted TestsSelect tests to mute in this pull request:
Reuse successful test results:
Click the checkbox to trigger a rerun:
Learn more about TestLens at testlens.app. |
|
The TestLens failures for Fix: #16067 (replaces the |
Description
This PR turns recurring, objective review feedback into deterministic repository checks while leaving engineering judgment in review. It introduces a staged quality-analysis ratchet, a repository-conventions gate, trustworthy aggregate reports, and aligned release provenance.
No Grails runtime API or application behavior changes are included.
What this moves forward
Staged PMD and report aggregation
PMD is blocking for the four modules with clean, report-producing baselines:
:grails-data-graphql-core:grails-data-mongodb-spring-data:grails-datasource:grails-testing-support-coreEach module opts in through
grailsCodeAnalysis { pmdEnabled = true }.GrailsCodeAnalysisExtensionnow exposes lazy PMD and SpotBugs properties, and plugin configuration is deferred toafterEvaluate. The existing-Pflags remain all-project or selected-project overrides, with unknown-path validation limited to those override lists. The root aggregator discovers enabled modules from applied PMD and SpotBugs plugins instead of a root property list.Aggregation preserves configuration avoidance: task collections use
configureEach, with collection-baseddependsOnandmustRunAfter. Per-task cleanup is no longer a quality-task dependency, so it cannot erase reports or defeat up-to-date checks. Aggregate cleanup runs only in the aggregate lane and removes stale markers; individual tasks already remove their own reports. The RAT lookup is realization-free, and the redundant style-to-conventions edge is removed.Only the aggregate lane writes the Markdown reports. The per-analyzer finalizers are removed, so running an analyzer directly produces just that task's own XML report and cannot replace an authoritative full-repository report with one rebuilt from another run's markers. Each Markdown report also starts with
Modules analyzed:so scope is explicit. Because that made the conventions gate unreachable from CI, which enters throughaggregateStyleViolationsrather thanaggregateViolations, thecodestyle.ymlcore job now invokesvalidateRepositoryConventionsexplicitly and publishes its report.--continueis required to get reports when an analyzer fails, which the documented commands and every workflow already pass.Repository conventions
./gradlew validateRepositoryConventionswritesbuild/reports/violations/REPOSITORY_CONVENTIONS.mdand is included throughaggregateViolations. It validates:name,description, andlicensefields, matching valid directory names and unique names.AGENTS.mdexists. It does not requireAGENTS.mdto be a complete skill index.actions/*andapache/*, includingapache/grails-github-actions, may use version or branch references but must retain a non-empty@ref.name@sha256:<digest>values. Expressions receive a specific failure because their immutability cannot be verified.**/grails-app/i18n/**/*.properties, including escaped separators and continuation rules.A 40-hex action reference can identify either a commit SHA or an annotated-tag-object SHA. Both are immutable and accepted because the validator cannot distinguish them.
The validator now uses UTF-8 for every read and report write, strips a leading BOM, reports malformed or misplaced front matter precisely, shares the directory-name pattern between discovery and
AGENTS.mdreferences, reports malformed properties lines, and carries one canonical repository root through all helpers. Both convention file trees share exclusions for VCS and Gradle state. Build-output exclusions use each project's configured build directory, so checked-in paths such as.github/actions/build/action.ymlremain in scope.Action policy and CI advisory reporting
The action-pinning policy is intentionally limited to third-party actions. Pins for GitHub-official
actions/*and first-partyapache/*actions introduced by this PR were reverted.gradle.yml,vulnerability-scan.yml,release-close.yml, andrelease-publish-docs.ymlhave no diff from8.0.x. The Mongo service image remains pinned by digest.SpotBugs advisory run, upload, and summary steps use
if: success() || failure()so cancelled jobs do not retain runners. Advisory artifacts contain onlySPOTBUGS_VIOLATIONS.md, each run has an ID, and its outcome is written to the job summary. The advisory pass no longer modifies a central PMD list because no such list exists.Release provenance and dependency hygiene
Tracked agent files such as
CLAUDE.md,GEMINI.md,.clinerules,.cursorrules,.windsurfrules, and.claude/skills/*/SKILL.mdare symlinks to files that ship in the source ZIP. The release ZIP again stores those symlinks withzip -y, includes their targets, and normalizes symlink timestamps withtouch -h. Source checkout credentials remain disabled withpersist-credentials: false.RAT exclusions are limited to
.omo/**,.worktrees/**, and.claude/settings.json..asf.yamlis excluded from the source ZIP, matching the RAT comment..worktrees/is the documented standard for agent and tooling worktrees because nested worktrees elsewhere are scanned by repository conventions and by the RAT run reached throughaggregateViolations.SnakeYAML now uses
snakeyamlVersion=2.6from the root build-tooling properties rather than a hardcoded dependency version.Documentation and tests
AGENTS.mdand the violation-fixer skill document the final pre-commit gate, per-project analysis opt-in, third-party action policy, and.worktrees/. The five review-only concerns are now actionable one-line checks that point to in-repository guidance and remain intentionally non-mechanized.TestKit coverage now includes SpotBugs and Checkstyle end-to-end paths, missing reports, local-action recursion, malformed references and properties, continuation handling, tool-enable combinations, front-matter errors, invalid skill directory names, UTF-8 input, container expressions, marker resolution, and report content. Test fixtures receive PMD and CodeNarc versions from root properties rather than hardcoding them.
Why the rollout is staged
Enabling every analyzer across every module would combine this build-policy change with a large, unrelated cleanup. This PR makes the known clean PMD modules blocking, keeps PMD and SpotBugs overrides available for baseline runs, and retains SpotBugs as a separate advisory CI pass until its broader baseline is ready.
Disabled tools report their status instead of being presented as clean. Architecture, compatibility, semantic documentation, test sufficiency, and style judgment remain review responsibilities rather than brittle mechanical rules.
Remaining work and follow-up issues
grails-gradle-modelThe conflict with open PR #15977 was removed by relaxing the
AGENTS.mdcheck. Either an explicit index or directory-based discovery now passes, while every new skill must still provide stringname,description, andlicensefront matter.Suggested next steps
grails-gradle-modelcleanup in Resolve existing PMD findings in grails-gradle-model #16016.Verification
build-logicplugin suite: frombuild-logic,../gradlew :build-logic:testpasses and covers every behavior described above. Result: 85 tests, 0 failures, 0 errors, 0 skipped, up from 77 on the previous revision../gradlew ratpasses on the narrowed exclusion list. Dropping the RAT exclusion for the agent symlinks was tried and reverted: RAT does not follow them portably, because on a platform without symlink support git materializes each one as a plain text file containing the link target.git diff origin/8.0.x -- .github/workflows/confirms thatgradle.yml,vulnerability-scan.yml,release-close.yml, andrelease-publish-docs.ymlhave zero diff after the action-pinning revert.This PR changes no runtime module behavior. CI will re-verify the full suite on the updated branch.
Contributor Checklist
Issue and Scope
8.0.x.Code Quality
build-logicplugin suite passes. No runtime module behavior changed, so the full suite will be re-verified by CI on the updated branch rather than relying on the stale earlier run.Licensing and Attribution
Documentation
AGENTS.mdand the violation-fixer skill document the commands, reports, policy, opt-in model, and remediation paths.